From af2cd0d51c78056218fdaed61711fed45f0682c1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Queru Date: Thu, 12 Nov 2009 18:45:21 -0800 Subject: [PATCH 1/2] eclair snapshot --- .gitignore | 83 + AUTHORS | 46 + Android.mk | 20 + COPYING | 340 ++ COPYING.LIB | 504 +++ ChangeLog | 1230 +++++++ INSTALL | 236 ++ Makefile.am | 20 + NEWS | 0 README | 38 + acinclude.m4 | 380 +++ audio/Android.mk | 74 + audio/Makefile.am | 87 + audio/a2dp.c | 1626 ++++++++++ audio/a2dp.h | 149 + audio/audio.conf | 43 + audio/avdtp.c | 3510 ++++++++++++++++++++ audio/avdtp.h | 298 ++ audio/bluetooth.conf | 36 + audio/control.c | 1160 +++++++ audio/control.h | 50 + audio/ctl_bluetooth.c | 384 +++ audio/device.c | 799 +++++ audio/device.h | 87 + audio/gateway.c | 1223 +++++++ audio/gateway.h | 47 + audio/gsta2dpsink.c | 703 ++++ audio/gsta2dpsink.h | 84 + audio/gstavdtpsink.c | 1434 +++++++++ audio/gstavdtpsink.h | 101 + audio/gstbluetooth.c | 106 + audio/gstrtpsbcpay.c | 351 ++ audio/gstrtpsbcpay.h | 66 + audio/gstsbcdec.c | 222 ++ audio/gstsbcdec.h | 66 + audio/gstsbcenc.c | 602 ++++ audio/gstsbcenc.h | 75 + audio/gstsbcparse.c | 219 ++ audio/gstsbcparse.h | 69 + audio/gstsbcutil.c | 521 +++ audio/gstsbcutil.h | 75 + audio/headset.c | 2850 +++++++++++++++++ audio/headset.h | 102 + audio/ipc.c | 133 + audio/ipc.h | 349 ++ audio/ipctest-a2dp-easy.test | 8 + audio/ipctest-a2dp-resume-fast.test | 82 + audio/ipctest-hsp-a2dp-switch.test | 50 + audio/ipctest-hsp-easy.test | 7 + audio/ipctest-init-shutdown.test | 59 + audio/ipctest.c | 1131 +++++++ audio/liba2dp.c | 1204 +++++++ audio/liba2dp.h | 39 + audio/main.c | 203 ++ audio/manager.c | 1276 ++++++++ audio/manager.h | 50 + audio/module-bluetooth-sink.c | 39 + audio/pcm_bluetooth.c | 1778 +++++++++++ audio/rtp.h | 76 + audio/sink.c | 940 ++++++ audio/sink.h | 49 + audio/source.c | 800 +++++ audio/source.h | 50 + audio/telephony-dummy.c | 417 +++ audio/telephony-maemo.c | 2061 ++++++++++++ audio/telephony-ofono.c | 1110 +++++++ audio/telephony.h | 234 ++ audio/unix.c | 1772 +++++++++++ audio/unix.h | 28 + bluez.m4 | 40 + bluez.pc.in | 10 + bootstrap | 7 + bootstrap-configure | 32 + client/Makefile.am | 2 + common/Android.mk | 34 + common/Makefile.am | 15 + common/android_bluez.c | 174 + common/btio.c | 1274 ++++++++ common/btio.h | 94 + common/glib-helper.c | 784 +++++ common/glib-helper.h | 46 + common/logging.c | 108 + common/logging.h | 38 + common/oui.c | 109 + common/oui.h | 25 + common/ppoll.h | 16 + common/sdp-xml.c | 799 +++++ common/sdp-xml.h | 59 + common/test_textfile.c | 188 ++ common/textfile.c | 469 +++ common/textfile.h | 42 + common/uinput.h | 724 +++++ compat/Makefile.am | 47 + compat/bnep.c | 323 ++ compat/dun.c | 331 ++ compat/dund.1 | 72 + compat/dund.c | 638 ++++ compat/dund.h | 40 + compat/fakehid.c | 669 ++++ compat/fakehid.txt | 134 + compat/hidd.1 | 41 + compat/hidd.c | 861 +++++ compat/hidd.h | 30 + compat/lib.h | 86 + compat/msdun.c | 153 + compat/pand.1 | 77 + compat/pand.c | 800 +++++ compat/pand.h | 36 + compat/sdp.c | 719 +++++ compat/sdp.h | 39 + configure.ac | 74 + cups/Makefile.am | 16 + cups/cups.h | 38 + cups/hcrp.c | 353 ++ cups/main.c | 786 +++++ cups/sdp.c | 119 + cups/spp.c | 118 + doc/Makefile.am | 31 + doc/adapter-api.txt | 270 ++ doc/agent-api.txt | 102 + doc/audio-api.txt | 458 +++ doc/bluez-docs.xml | 97 + doc/control-api.txt | 142 + doc/device-api.txt | 188 ++ doc/gtk-doc.make | 173 + doc/input-api.txt | 44 + doc/manager-api.txt | 72 + doc/network-api.txt | 86 + doc/node-api.txt | 28 + doc/serial-api.txt | 41 + doc/service-api.txt | 62 + doc/version.xml.in | 1 + gdbus/Android.mk | 15 + gdbus/Makefile.am | 8 + gdbus/gdbus.h | 139 + gdbus/mainloop.c | 348 ++ gdbus/object.c | 658 ++++ gdbus/watch.c | 422 +++ include/bluetooth/Makefile.am | 14 + include/bluetooth/bluetooth.h | 160 + include/bluetooth/bnep.h | 153 + include/bluetooth/cmtp.h | 69 + include/bluetooth/hci.h | 1823 +++++++++++ include/bluetooth/hci_lib.h | 210 ++ include/bluetooth/hidp.h | 85 + include/bluetooth/l2cap.h | 205 ++ include/bluetooth/rfcomm.h | 99 + include/bluetooth/sco.h | 62 + include/bluetooth/sdp.h | 504 +++ include/bluetooth/sdp_lib.h | 616 ++++ input/Android.mk | 38 + input/Makefile.am | 24 + input/device.c | 1293 ++++++++ input/device.h | 55 + input/fakehid.c | 387 +++ input/fakehid.h | 39 + input/input.conf | 9 + input/main.c | 86 + input/manager.c | 207 ++ input/manager.h | 25 + input/server.c | 235 ++ input/server.h | 25 + input/sixpair.c | 299 ++ lib/Android.mk | 16 + lib/Makefile.am | 9 + lib/bluetooth.c | 470 +++ lib/hci.c | 2498 +++++++++++++++ lib/sdp.c | 4603 +++++++++++++++++++++++++++ network/Makefile.am | 24 + network/bridge.c | 148 + network/bridge.h | 30 + network/common.c | 373 +++ network/common.h | 42 + network/connection.c | 658 ++++ network/connection.h | 28 + network/main.c | 59 + network/manager.c | 393 +++ network/manager.h | 25 + network/network.conf | 33 + network/server.c | 918 ++++++ network/server.h | 34 + plugins/Android.mk | 38 + plugins/Makefile.am | 71 + plugins/builtin.h | 26 + plugins/echo.c | 167 + plugins/hal.c | 162 + plugins/hciops.c | 773 +++++ plugins/netlink.c | 127 + plugins/service.c | 865 +++++ plugins/storage.c | 43 + rfcomm/Android.mk | 29 + rfcomm/Makefile.am | 34 + rfcomm/kword.c | 65 + rfcomm/kword.h | 46 + rfcomm/lexer.c | 1763 ++++++++++ rfcomm/lexer.l | 120 + rfcomm/main.c | 845 +++++ rfcomm/parser.c | 595 ++++ rfcomm/parser.h | 88 + rfcomm/parser.y | 171 + rfcomm/rfcomm.1 | 137 + rfcomm/rfcomm.conf | 17 + sbc/Makefile.am | 33 + sbc/formats.h | 55 + sbc/sbc.c | 1258 ++++++++ sbc/sbc.h | 112 + sbc/sbc_math.h | 60 + sbc/sbc_primitives.c | 470 +++ sbc/sbc_primitives.h | 75 + sbc/sbc_primitives_mmx.c | 320 ++ sbc/sbc_primitives_mmx.h | 40 + sbc/sbc_primitives_neon.c | 246 ++ sbc/sbc_primitives_neon.h | 40 + sbc/sbc_tables.h | 659 ++++ sbc/sbcdec.c | 295 ++ sbc/sbcenc.c | 307 ++ sbc/sbcinfo.c | 321 ++ sbc/sbctester.c | 357 +++ scripts/Makefile.am | 35 + scripts/bluetooth-hid2hci.rules | 36 + scripts/bluetooth-serial.rules | 35 + scripts/bluetooth.rules.in | 3 + scripts/bluetooth_serial | 39 + serial/Makefile.am | 24 + serial/main.c | 59 + serial/manager.c | 183 ++ serial/manager.h | 25 + serial/port.c | 644 ++++ serial/port.h | 29 + serial/proxy.c | 1311 ++++++++ serial/proxy.h | 25 + serial/serial.conf | 10 + src/Android.mk | 72 + src/Makefile.am | 47 + src/adapter.c | 3175 ++++++++++++++++++ src/adapter.h | 187 ++ src/agent.c | 860 +++++ src/agent.h | 77 + src/bluetooth.conf | 24 + src/bluetoothd.8.in | 91 + src/dbus-common.c | 316 ++ src/dbus-common.h | 46 + src/dbus-hci.c | 1006 ++++++ src/dbus-hci.h | 53 + src/device.c | 2377 ++++++++++++++ src/device.h | 109 + src/error.c | 50 + src/error.h | 31 + src/hcid.conf | 57 + src/hcid.conf.5.in | 227 ++ src/hcid.h | 100 + src/main.c | 494 +++ src/main.conf | 52 + src/manager.c | 534 ++++ src/manager.h | 48 + src/plugin.c | 221 ++ src/plugin.h | 47 + src/rfkill.c | 172 + src/sdpd-database.c | 304 ++ src/sdpd-request.c | 1070 +++++++ src/sdpd-server.c | 296 ++ src/sdpd-service.c | 695 ++++ src/sdpd.h | 98 + src/security.c | 1171 +++++++ src/storage.c | 1143 +++++++ src/storage.h | 79 + test/Android.mk | 328 ++ test/Makefile.am | 55 + test/agent.c | 647 ++++ test/apitest | 448 +++ test/attest.c | 183 ++ test/avtest.c | 31 + test/bdaddr.8 | 68 + test/bdaddr.c | 460 +++ test/btiotest.c | 555 ++++ test/dbusdef.py | 16 + test/hciemu.1 | 31 + test/hciemu.c | 1354 ++++++++ test/hsmicro | 20 + test/hsplay | 22 + test/hstest.c | 308 ++ test/l2test.c | 1286 ++++++++ test/list-devices | 84 + test/lmptest.c | 175 + test/monitor-bluetooth | 56 + test/rctest.1 | 90 + test/rctest.c | 781 +++++ test/scotest.c | 434 +++ test/sdptest.c | 146 + test/service-did.xml | 33 + test/service-ftp.xml | 37 + test/service-opp.xml | 50 + test/service-record.dtd | 66 + test/service-spp.xml | 25 + test/simple-agent | 116 + test/simple-service | 127 + test/test-adapter | 106 + test/test-device | 128 + test/test-discovery | 44 + test/test-manager | 38 + test/test-network | 43 + test/test-serial | 43 + test/test-service | 33 + test/test-telephony | 160 + tools/Android.mk | 164 + tools/Makefile.am | 106 + tools/avctrl.8 | 39 + tools/avctrl.c | 248 ++ tools/avinfo.c | 672 ++++ tools/bccmd.8 | 130 + tools/bccmd.c | 1219 +++++++ tools/ciptool.1 | 68 + tools/ciptool.c | 498 +++ tools/csr.c | 2853 +++++++++++++++++ tools/csr.h | 552 ++++ tools/csr_3wire.c | 62 + tools/csr_bcsp.c | 255 ++ tools/csr_h4.c | 165 + tools/csr_hci.c | 160 + tools/csr_usb.c | 180 ++ tools/dfu.c | 168 + tools/dfu.h | 107 + tools/dfubabel.1 | 38 + tools/dfubabel.c | 211 ++ tools/dfutool.1 | 53 + tools/dfutool.c | 791 +++++ tools/example.psr | 12 + tools/hciattach.8 | 122 + tools/hciattach.c | 1361 ++++++++ tools/hciattach.h | 47 + tools/hciattach_st.c | 278 ++ tools/hciattach_ti.c | 522 +++ tools/hciattach_tialt.c | 271 ++ tools/hciconfig.8 | 276 ++ tools/hciconfig.c | 1761 ++++++++++ tools/hcieventmask.c | 124 + tools/hcisecfilter.c | 155 + tools/hcitool.1 | 209 ++ tools/hcitool.c | 2452 ++++++++++++++ tools/hid2hci.8 | 51 + tools/hid2hci.c | 375 +++ tools/l2ping.8 | 76 + tools/l2ping.c | 322 ++ tools/ppporc.c | 271 ++ tools/sdptool.1 | 130 + tools/sdptool.c | 4214 ++++++++++++++++++++++++ tools/ubcsp.c | 1180 +++++++ tools/ubcsp.h | 208 ++ 348 files changed, 126618 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100755 Android.mk create mode 100644 COPYING create mode 100644 COPYING.LIB create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 acinclude.m4 create mode 100755 audio/Android.mk create mode 100644 audio/Makefile.am create mode 100644 audio/a2dp.c create mode 100644 audio/a2dp.h create mode 100644 audio/audio.conf create mode 100644 audio/avdtp.c create mode 100644 audio/avdtp.h create mode 100644 audio/bluetooth.conf create mode 100644 audio/control.c create mode 100644 audio/control.h create mode 100644 audio/ctl_bluetooth.c create mode 100644 audio/device.c create mode 100644 audio/device.h create mode 100644 audio/gateway.c create mode 100644 audio/gateway.h create mode 100644 audio/gsta2dpsink.c create mode 100644 audio/gsta2dpsink.h create mode 100644 audio/gstavdtpsink.c create mode 100644 audio/gstavdtpsink.h create mode 100644 audio/gstbluetooth.c create mode 100644 audio/gstrtpsbcpay.c create mode 100644 audio/gstrtpsbcpay.h create mode 100644 audio/gstsbcdec.c create mode 100644 audio/gstsbcdec.h create mode 100644 audio/gstsbcenc.c create mode 100644 audio/gstsbcenc.h create mode 100644 audio/gstsbcparse.c create mode 100644 audio/gstsbcparse.h create mode 100644 audio/gstsbcutil.c create mode 100644 audio/gstsbcutil.h create mode 100644 audio/headset.c create mode 100644 audio/headset.h create mode 100644 audio/ipc.c create mode 100644 audio/ipc.h create mode 100644 audio/ipctest-a2dp-easy.test create mode 100644 audio/ipctest-a2dp-resume-fast.test create mode 100644 audio/ipctest-hsp-a2dp-switch.test create mode 100644 audio/ipctest-hsp-easy.test create mode 100644 audio/ipctest-init-shutdown.test create mode 100644 audio/ipctest.c create mode 100755 audio/liba2dp.c create mode 100644 audio/liba2dp.h create mode 100644 audio/main.c create mode 100644 audio/manager.c create mode 100644 audio/manager.h create mode 100644 audio/module-bluetooth-sink.c create mode 100644 audio/pcm_bluetooth.c create mode 100644 audio/rtp.h create mode 100644 audio/sink.c create mode 100644 audio/sink.h create mode 100644 audio/source.c create mode 100644 audio/source.h create mode 100644 audio/telephony-dummy.c create mode 100644 audio/telephony-maemo.c create mode 100644 audio/telephony-ofono.c create mode 100644 audio/telephony.h create mode 100644 audio/unix.c create mode 100644 audio/unix.h create mode 100644 bluez.m4 create mode 100644 bluez.pc.in create mode 100755 bootstrap create mode 100755 bootstrap-configure create mode 100644 client/Makefile.am create mode 100755 common/Android.mk create mode 100644 common/Makefile.am create mode 100644 common/android_bluez.c create mode 100644 common/btio.c create mode 100644 common/btio.h create mode 100644 common/glib-helper.c create mode 100644 common/glib-helper.h create mode 100644 common/logging.c create mode 100644 common/logging.h create mode 100644 common/oui.c create mode 100644 common/oui.h create mode 100644 common/ppoll.h create mode 100644 common/sdp-xml.c create mode 100644 common/sdp-xml.h create mode 100644 common/test_textfile.c create mode 100644 common/textfile.c create mode 100644 common/textfile.h create mode 100644 common/uinput.h create mode 100644 compat/Makefile.am create mode 100644 compat/bnep.c create mode 100644 compat/dun.c create mode 100644 compat/dund.1 create mode 100644 compat/dund.c create mode 100644 compat/dund.h create mode 100644 compat/fakehid.c create mode 100644 compat/fakehid.txt create mode 100644 compat/hidd.1 create mode 100644 compat/hidd.c create mode 100644 compat/hidd.h create mode 100644 compat/lib.h create mode 100644 compat/msdun.c create mode 100644 compat/pand.1 create mode 100644 compat/pand.c create mode 100644 compat/pand.h create mode 100644 compat/sdp.c create mode 100644 compat/sdp.h create mode 100644 configure.ac create mode 100644 cups/Makefile.am create mode 100644 cups/cups.h create mode 100644 cups/hcrp.c create mode 100644 cups/main.c create mode 100644 cups/sdp.c create mode 100644 cups/spp.c create mode 100644 doc/Makefile.am create mode 100644 doc/adapter-api.txt create mode 100644 doc/agent-api.txt create mode 100644 doc/audio-api.txt create mode 100644 doc/bluez-docs.xml create mode 100644 doc/control-api.txt create mode 100644 doc/device-api.txt create mode 100644 doc/gtk-doc.make create mode 100644 doc/input-api.txt create mode 100644 doc/manager-api.txt create mode 100644 doc/network-api.txt create mode 100644 doc/node-api.txt create mode 100644 doc/serial-api.txt create mode 100644 doc/service-api.txt create mode 100644 doc/version.xml.in create mode 100755 gdbus/Android.mk create mode 100644 gdbus/Makefile.am create mode 100644 gdbus/gdbus.h create mode 100644 gdbus/mainloop.c create mode 100644 gdbus/object.c create mode 100644 gdbus/watch.c create mode 100644 include/bluetooth/Makefile.am create mode 100644 include/bluetooth/bluetooth.h create mode 100644 include/bluetooth/bnep.h create mode 100644 include/bluetooth/cmtp.h create mode 100644 include/bluetooth/hci.h create mode 100644 include/bluetooth/hci_lib.h create mode 100644 include/bluetooth/hidp.h create mode 100644 include/bluetooth/l2cap.h create mode 100644 include/bluetooth/rfcomm.h create mode 100644 include/bluetooth/sco.h create mode 100644 include/bluetooth/sdp.h create mode 100644 include/bluetooth/sdp_lib.h create mode 100755 input/Android.mk create mode 100644 input/Makefile.am create mode 100644 input/device.c create mode 100644 input/device.h create mode 100644 input/fakehid.c create mode 100644 input/fakehid.h create mode 100644 input/input.conf create mode 100644 input/main.c create mode 100644 input/manager.c create mode 100644 input/manager.h create mode 100644 input/server.c create mode 100644 input/server.h create mode 100644 input/sixpair.c create mode 100755 lib/Android.mk create mode 100644 lib/Makefile.am create mode 100644 lib/bluetooth.c create mode 100644 lib/hci.c create mode 100644 lib/sdp.c create mode 100644 network/Makefile.am create mode 100644 network/bridge.c create mode 100644 network/bridge.h create mode 100644 network/common.c create mode 100644 network/common.h create mode 100644 network/connection.c create mode 100644 network/connection.h create mode 100644 network/main.c create mode 100644 network/manager.c create mode 100644 network/manager.h create mode 100644 network/network.conf create mode 100644 network/server.c create mode 100644 network/server.h create mode 100755 plugins/Android.mk create mode 100644 plugins/Makefile.am create mode 100644 plugins/builtin.h create mode 100644 plugins/echo.c create mode 100644 plugins/hal.c create mode 100644 plugins/hciops.c create mode 100644 plugins/netlink.c create mode 100644 plugins/service.c create mode 100644 plugins/storage.c create mode 100755 rfcomm/Android.mk create mode 100644 rfcomm/Makefile.am create mode 100644 rfcomm/kword.c create mode 100644 rfcomm/kword.h create mode 100644 rfcomm/lexer.c create mode 100644 rfcomm/lexer.l create mode 100644 rfcomm/main.c create mode 100644 rfcomm/parser.c create mode 100644 rfcomm/parser.h create mode 100644 rfcomm/parser.y create mode 100644 rfcomm/rfcomm.1 create mode 100644 rfcomm/rfcomm.conf create mode 100644 sbc/Makefile.am create mode 100644 sbc/formats.h create mode 100644 sbc/sbc.c create mode 100644 sbc/sbc.h create mode 100644 sbc/sbc_math.h create mode 100644 sbc/sbc_primitives.c create mode 100644 sbc/sbc_primitives.h create mode 100644 sbc/sbc_primitives_mmx.c create mode 100644 sbc/sbc_primitives_mmx.h create mode 100644 sbc/sbc_primitives_neon.c create mode 100644 sbc/sbc_primitives_neon.h create mode 100644 sbc/sbc_tables.h create mode 100644 sbc/sbcdec.c create mode 100644 sbc/sbcenc.c create mode 100644 sbc/sbcinfo.c create mode 100644 sbc/sbctester.c create mode 100644 scripts/Makefile.am create mode 100644 scripts/bluetooth-hid2hci.rules create mode 100644 scripts/bluetooth-serial.rules create mode 100644 scripts/bluetooth.rules.in create mode 100644 scripts/bluetooth_serial create mode 100644 serial/Makefile.am create mode 100644 serial/main.c create mode 100644 serial/manager.c create mode 100644 serial/manager.h create mode 100644 serial/port.c create mode 100644 serial/port.h create mode 100644 serial/proxy.c create mode 100644 serial/proxy.h create mode 100644 serial/serial.conf create mode 100755 src/Android.mk create mode 100644 src/Makefile.am create mode 100644 src/adapter.c create mode 100644 src/adapter.h create mode 100644 src/agent.c create mode 100644 src/agent.h create mode 100644 src/bluetooth.conf create mode 100644 src/bluetoothd.8.in create mode 100644 src/dbus-common.c create mode 100644 src/dbus-common.h create mode 100644 src/dbus-hci.c create mode 100644 src/dbus-hci.h create mode 100644 src/device.c create mode 100644 src/device.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/hcid.conf create mode 100644 src/hcid.conf.5.in create mode 100644 src/hcid.h create mode 100644 src/main.c create mode 100644 src/main.conf create mode 100644 src/manager.c create mode 100644 src/manager.h create mode 100644 src/plugin.c create mode 100644 src/plugin.h create mode 100644 src/rfkill.c create mode 100644 src/sdpd-database.c create mode 100644 src/sdpd-request.c create mode 100644 src/sdpd-server.c create mode 100644 src/sdpd-service.c create mode 100644 src/sdpd.h create mode 100644 src/security.c create mode 100644 src/storage.c create mode 100644 src/storage.h create mode 100755 test/Android.mk create mode 100644 test/Makefile.am create mode 100644 test/agent.c create mode 100755 test/apitest create mode 100644 test/attest.c create mode 100644 test/avtest.c create mode 100644 test/bdaddr.8 create mode 100644 test/bdaddr.c create mode 100644 test/btiotest.c create mode 100644 test/dbusdef.py create mode 100644 test/hciemu.1 create mode 100644 test/hciemu.c create mode 100755 test/hsmicro create mode 100755 test/hsplay create mode 100644 test/hstest.c create mode 100644 test/l2test.c create mode 100755 test/list-devices create mode 100644 test/lmptest.c create mode 100755 test/monitor-bluetooth create mode 100644 test/rctest.1 create mode 100644 test/rctest.c create mode 100644 test/scotest.c create mode 100644 test/sdptest.c create mode 100644 test/service-did.xml create mode 100644 test/service-ftp.xml create mode 100644 test/service-opp.xml create mode 100644 test/service-record.dtd create mode 100644 test/service-spp.xml create mode 100755 test/simple-agent create mode 100755 test/simple-service create mode 100755 test/test-adapter create mode 100755 test/test-device create mode 100755 test/test-discovery create mode 100755 test/test-manager create mode 100755 test/test-network create mode 100755 test/test-serial create mode 100755 test/test-service create mode 100755 test/test-telephony create mode 100755 tools/Android.mk create mode 100644 tools/Makefile.am create mode 100644 tools/avctrl.8 create mode 100644 tools/avctrl.c create mode 100644 tools/avinfo.c create mode 100644 tools/bccmd.8 create mode 100644 tools/bccmd.c create mode 100644 tools/ciptool.1 create mode 100644 tools/ciptool.c create mode 100644 tools/csr.c create mode 100644 tools/csr.h create mode 100644 tools/csr_3wire.c create mode 100644 tools/csr_bcsp.c create mode 100644 tools/csr_h4.c create mode 100644 tools/csr_hci.c create mode 100644 tools/csr_usb.c create mode 100644 tools/dfu.c create mode 100644 tools/dfu.h create mode 100644 tools/dfubabel.1 create mode 100644 tools/dfubabel.c create mode 100644 tools/dfutool.1 create mode 100644 tools/dfutool.c create mode 100644 tools/example.psr create mode 100644 tools/hciattach.8 create mode 100644 tools/hciattach.c create mode 100644 tools/hciattach.h create mode 100644 tools/hciattach_st.c create mode 100644 tools/hciattach_ti.c create mode 100644 tools/hciattach_tialt.c create mode 100644 tools/hciconfig.8 create mode 100644 tools/hciconfig.c create mode 100644 tools/hcieventmask.c create mode 100644 tools/hcisecfilter.c create mode 100644 tools/hcitool.1 create mode 100644 tools/hcitool.c create mode 100644 tools/hid2hci.8 create mode 100644 tools/hid2hci.c create mode 100644 tools/l2ping.8 create mode 100644 tools/l2ping.c create mode 100644 tools/ppporc.c create mode 100644 tools/sdptool.1 create mode 100644 tools/sdptool.c create mode 100644 tools/ubcsp.c create mode 100644 tools/ubcsp.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d5b85d30e --- /dev/null +++ b/.gitignore @@ -0,0 +1,83 @@ +*.o +*.a +*.lo +*.la +*.so +.deps +.libs +Makefile +Makefile.in +aclocal.m4 +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +depcomp +compile +install-sh +libtool +ltmain.sh +missing +stamp-h1 +autom4te.cache + +ylwrap +bluez.pc +include/bluetooth +src/bluetoothd +audio/telephony.c +scripts/bluetooth.rules + +sbc/sbcdec +sbc/sbcenc +sbc/sbcinfo +sbc/sbctester + +tools/avctrl +tools/avinfo +tools/bccmd +tools/ciptool +tools/dfubabel +tools/dfutool +tools/hciattach +tools/hciconfig +tools/hcieventmask +tools/hcisecfilter +tools/hcitool +tools/hid2hci +tools/l2ping +tools/ppporc +tools/sdptool +audio/ipctest +cups/bluetooth +test/agent +test/bdaddr +test/hciemu +test/attest +test/hstest +test/avtest +test/l2test +test/rctest +test/scotest +test/sdptest +test/lmptest +test/btiotest +rfcomm/rfcomm +compat/dund +compat/hidd +compat/pand +common/test_textfile + +doc/*.bak +doc/*.stamp +doc/bluez.* +doc/bluez-*.txt +doc/*.sgml +doc/version.xml +doc/xml +doc/html +src/bluetoothd.8 +src/hcid.conf.5 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..cd0189e4f --- /dev/null +++ b/AUTHORS @@ -0,0 +1,46 @@ +Maxim Krasnyansky +Marcel Holtmann +Stephen Crane +Jean Tourrilhes +Jan Beutel +Ilguiz Latypov +Thomas Moser +Nils Faerber +Martin Leopold +Wolfgang Heidrich +Fabrizio Gennari +Brad Midgley +Henryk Ploetz +Philip Blundell +Johan Hedberg +Claudio Takahasi +Eduardo Rocha +Denis Kenzior +Frederic Dalleau +Frederic Danis +Luiz Augusto von Dentz +Fabien Chevalier +Ohad Ben-Cohen +Daniel Gollub +Tom Patzig +Kai Vehmanen +Vinicius Gomes +Alok Barsode +Bastien Nocera +Albert Huang +Glenn Durfee +David Woodhouse +Christian Hoene +Pekka Pessi +Siarhei Siamashka +Nick Pelly +Lennart Poettering +Gustavo F. Padovan +Marc-Andre Lureau +Bea Lam +Zygo Blaxell +Forrest Zhao +Scott Talbot +Ilya Rubtsov +Mario Limonciello +Filippo Giunchedi diff --git a/Android.mk b/Android.mk new file mode 100755 index 000000000..456efddcd --- /dev/null +++ b/Android.mk @@ -0,0 +1,20 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ifneq ($(TARGET_SIMULATOR),true) +ifeq ($(BOARD_HAVE_BLUETOOTH),true) + include $(all-subdir-makefiles) +endif +endif diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..6d45519c8 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + 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. diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 000000000..1f7c8cc65 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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 library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..e7066b85c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1230 @@ +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. diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..56b077d6a --- /dev/null +++ b/INSTALL @@ -0,0 +1,236 @@ +Installation 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. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..c4c63226d --- /dev/null +++ b/Makefile.am @@ -0,0 +1,20 @@ + +SUBDIRS = include lib sbc gdbus common plugins src client\ + network serial input audio tools \ + rfcomm compat cups test scripts doc + +EXTRA_DIST = bluez.m4 + +pkgconfigdir = $(libdir)/pkgconfig + +pkgconfig_DATA = bluez.pc + +DISTCHECK_CONFIGURE_FLAGS = --disable-gtk-doc \ + --disable-udevrules + +DISTCLEANFILES = $(pkgconfig_DATA) + +MAINTAINERCLEANFILES = Makefile.in \ + aclocal.m4 configure config.h.in config.sub config.guess \ + ltmain.sh depcomp compile missing install-sh mkinstalldirs ylwrap + diff --git a/NEWS b/NEWS new file mode 100644 index 000000000..e69de29bb diff --git a/README b/README new file mode 100644 index 000000000..3cc654726 --- /dev/null +++ b/README @@ -0,0 +1,38 @@ +BlueZ - Bluetooth protocol stack for Linux +****************************************** + +Copyright (C) 2000-2001 Qualcomm Incorporated +Copyright (C) 2002-2003 Maxim Krasnyansky +Copyright (C) 2002-2009 Marcel Holtmann + + +Compilation and installation +============================ + +In order to compile Bluetooth utilities you need following software packages: + - Linux Bluetooth protocol stack (BlueZ) + - GCC compiler + - D-Bus library + - GLib library + - USB library (optional) + - Lexical Analyzer (flex, lex) + - YACC (yacc, bison, byacc) + +To configure run: + ./configure --prefix=/usr --mandir=/usr/share/man \ + --sysconfdir=/etc --localstatedir=/var --libexecdir=/lib + +Configure automatically searches for all required components and packages. + +To compile and install run: + make && make install + + +Information +=========== + +Mailing lists: + linux-bluetooth@vger.kernel.org + +For additional information about the project visit BlueZ web site: + http://www.bluez.org diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 000000000..248fed33d --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,380 @@ +AC_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([COMPILER_FLAGS], [ + if (test "${CFLAGS}" = ""); then + CFLAGS="-Wall -O2" + fi + if (test "$USE_MAINTAINER_MODE" = "yes"); then + CFLAGS+=" -Werror -Wextra" + CFLAGS+=" -Wno-unused-parameter" + CFLAGS+=" -Wno-missing-field-initializers" + CFLAGS+=" -Wdeclaration-after-statement" + CFLAGS+=" -Wmissing-declarations" + CFLAGS+=" -Wredundant-decls" + CFLAGS+=" -Wcast-align" + fi +]) + +AC_DEFUN([GTK_DOC_CHECK], [ + AC_ARG_WITH([html-dir], + AS_HELP_STRING([--with-html-dir=PATH], [path to installed docs]),, + [with_html_dir='${datadir}/gtk-doc/html']) + HTML_DIR="$with_html_dir" + AC_SUBST([HTML_DIR]) + + AC_ARG_ENABLE([gtk-doc], + AS_HELP_STRING([--enable-gtk-doc], [use gtk-doc to build documentation [[default=no]]]),, + [enable_gtk_doc=no]) + + if test x$enable_gtk_doc = xyes; then + ifelse([$1],[], + [PKG_CHECK_EXISTS([gtk-doc],, + AC_MSG_ERROR([gtk-doc not installed and --enable-gtk-doc requested]))], + [PKG_CHECK_EXISTS([gtk-doc >= $1],, + AC_MSG_ERROR([You need to have gtk-doc >= $1 installed to build gtk-doc]))]) + fi + + AC_MSG_CHECKING([whether to build gtk-doc documentation]) + AC_MSG_RESULT($enable_gtk_doc) + + AC_PATH_PROGS(GTKDOC_CHECK,gtkdoc-check,) + + AM_CONDITIONAL([ENABLE_GTK_DOC], [test x$enable_gtk_doc = xyes]) + AM_CONDITIONAL([GTK_DOC_USE_LIBTOOL], [test -n "$LIBTOOL"]) +]) + +AC_DEFUN([AC_FUNC_PPOLL], [ + AC_CHECK_FUNC(ppoll, dummy=yes, AC_DEFINE(NEED_PPOLL, 1, + [Define to 1 if you need the ppoll() function.])) +]) + +AC_DEFUN([AC_INIT_BLUEZ], [ + AC_PREFIX_DEFAULT(/usr/local) + + if (test "${prefix}" = "NONE"); then + dnl no prefix and no sysconfdir, so default to /etc + if (test "$sysconfdir" = '${prefix}/etc'); then + AC_SUBST([sysconfdir], ['/etc']) + fi + + dnl no prefix and no localstatedir, so default to /var + if (test "$localstatedir" = '${prefix}/var'); then + AC_SUBST([localstatedir], ['/var']) + fi + + dnl no prefix and no libexecdir, so default to /lib + if (test "$libexecdir" = '${exec_prefix}/libexec'); then + AC_SUBST([libexecdir], ['/lib']) + fi + + dnl no prefix and no mandir, so use ${prefix}/share/man as default + if (test "$mandir" = '${prefix}/man'); then + AC_SUBST([mandir], ['${prefix}/share/man']) + fi + + prefix="${ac_default_prefix}" + fi + + if (test "${libdir}" = '${exec_prefix}/lib'); then + libdir="${prefix}/lib" + fi + + plugindir="${libdir}/bluetooth/plugins" + + if (test "$sysconfdir" = '${prefix}/etc'); then + configdir="${prefix}/etc/bluetooth" + else + configdir="${sysconfdir}/bluetooth" + fi + + if (test "$localstatedir" = '${prefix}/var'); then + storagedir="${prefix}/var/lib/bluetooth" + else + storagedir="${localstatedir}/lib/bluetooth" + fi + + AC_DEFINE_UNQUOTED(CONFIGDIR, "${configdir}", + [Directory for the configuration files]) + AC_DEFINE_UNQUOTED(STORAGEDIR, "${storagedir}", + [Directory for the storage files]) + + AC_SUBST(CONFIGDIR, "${configdir}") + AC_SUBST(STORAGEDIR, "${storagedir}") + + UDEV_DATADIR="`$PKG_CONFIG --variable=udevdir udev`" + if (test -z "${UDEV_DATADIR}"); then + UDEV_DATADIR="${sysconfdir}/udev/rules.d" + else + UDEV_DATADIR="${UDEV_DATADIR}/rules.d" + fi + AC_SUBST(UDEV_DATADIR) +]) + +AC_DEFUN([AC_PATH_DBUS], [ + PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.0, dummy=yes, + AC_MSG_ERROR(D-Bus library is required)) + AC_CHECK_LIB(dbus-1, dbus_watch_get_unix_fd, dummy=yes, + AC_DEFINE(NEED_DBUS_WATCH_GET_UNIX_FD, 1, + [Define to 1 if you need the dbus_watch_get_unix_fd() function.])) + AC_SUBST(DBUS_CFLAGS) + AC_SUBST(DBUS_LIBS) +]) + +AC_DEFUN([AC_PATH_GLIB], [ + PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.14, dummy=yes, + AC_MSG_ERROR(GLib library version 2.14 or later is required)) + AC_SUBST(GLIB_CFLAGS) + AC_SUBST(GLIB_LIBS) +]) + +AC_DEFUN([AC_PATH_GSTREAMER], [ + PKG_CHECK_MODULES(GSTREAMER, gstreamer-0.10 gstreamer-plugins-base-0.10, gstreamer_found=yes, gstreamer_found=no) + AC_SUBST(GSTREAMER_CFLAGS) + AC_SUBST(GSTREAMER_LIBS) + GSTREAMER_PLUGINSDIR=`$PKG_CONFIG --variable=pluginsdir gstreamer-0.10` + AC_SUBST(GSTREAMER_PLUGINSDIR) +]) + +AC_DEFUN([AC_PATH_PULSE], [ + PKG_CHECK_MODULES(PULSE, libpulse, pulse_found=yes, pulse_found=no) + AC_SUBST(PULSE_CFLAGS) + AC_SUBST(PULSE_LIBS) +]) + +AC_DEFUN([AC_PATH_ALSA], [ + PKG_CHECK_MODULES(ALSA, alsa, alsa_found=yes, alsa_found=no) + AC_CHECK_LIB(rt, clock_gettime, ALSA_LIBS="$ALSA_LIBS -lrt", alsa_found=no) + AC_SUBST(ALSA_CFLAGS) + AC_SUBST(ALSA_LIBS) +]) + +AC_DEFUN([AC_PATH_USB], [ + PKG_CHECK_MODULES(USB, libusb, usb_found=yes, usb_found=no) + AC_SUBST(USB_CFLAGS) + AC_SUBST(USB_LIBS) + AC_CHECK_LIB(usb, usb_get_busses, dummy=yes, + AC_DEFINE(NEED_USB_GET_BUSSES, 1, + [Define to 1 if you need the usb_get_busses() function.])) + AC_CHECK_LIB(usb, usb_interrupt_read, dummy=yes, + AC_DEFINE(NEED_USB_INTERRUPT_READ, 1, + [Define to 1 if you need the usb_interrupt_read() function.])) +]) + +AC_DEFUN([AC_PATH_NETLINK], [ + PKG_CHECK_MODULES(NETLINK, libnl-1, netlink_found=yes, netlink_found=no) + AC_SUBST(NETLINK_CFLAGS) + AC_SUBST(NETLINK_LIBS) +]) + +AC_DEFUN([AC_PATH_SNDFILE], [ + PKG_CHECK_MODULES(SNDFILE, sndfile, sndfile_found=yes, sndfile_found=no) + AC_SUBST(SNDFILE_CFLAGS) + AC_SUBST(SNDFILE_LIBS) +]) + +AC_DEFUN([AC_ARG_BLUEZ], [ + debug_enable=no + optimization_enable=yes + fortify_enable=yes + pie_enable=yes + sndfile_enable=${sndfile_found} + netlink_enable=no + hal_enable=${hal_found} + usb_enable=${usb_found} + alsa_enable=${alsa_found} + gstreamer_enable=${gstreamer_found} + audio_enable=yes + input_enable=yes + serial_enable=yes + network_enable=yes + service_enable=yes + tools_enable=yes + hidd_enable=no + pand_enable=no + dund_enable=no + cups_enable=no + test_enable=no + bccmd_enable=no + pcmcia_enable=no + hid2hci_enable=no + dfutool_enable=no + manpages_enable=yes + udevrules_enable=yes + configfiles_enable=yes + telephony_driver=dummy + + AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable code optimization]), [ + optimization_enable=${enableval} + ]) + + AC_ARG_ENABLE(fortify, AC_HELP_STRING([--disable-fortify], [disable compile time buffer checks]), [ + fortify_enable=${enableval} + ]) + + AC_ARG_ENABLE(pie, AC_HELP_STRING([--disable-pie], [disable position independent executables flag]), [ + pie_enable=${enableval} + ]) + + AC_ARG_ENABLE(network, AC_HELP_STRING([--disable-network], [disable network plugin]), [ + network_enable=${enableval} + ]) + + AC_ARG_ENABLE(serial, AC_HELP_STRING([--disable-serial], [disable serial plugin]), [ + serial_enable=${enableval} + ]) + + AC_ARG_ENABLE(input, AC_HELP_STRING([--disable-input], [disable input plugin]), [ + input_enable=${enableval} + ]) + + AC_ARG_ENABLE(audio, AC_HELP_STRING([--disable-audio], [disable audio plugin]), [ + audio_enable=${enableval} + ]) + + AC_ARG_ENABLE(service, AC_HELP_STRING([--disable-service], [disable service plugin]), [ + service_enable=${enableval} + ]) + + AC_ARG_ENABLE(gstreamer, AC_HELP_STRING([--enable-gstreamer], [enable GStreamer support]), [ + gstreamer_enable=${enableval} + ]) + + AC_ARG_ENABLE(alsa, AC_HELP_STRING([--enable-alsa], [enable ALSA support]), [ + alsa_enable=${enableval} + ]) + + AC_ARG_ENABLE(usb, AC_HELP_STRING([--enable-usb], [enable USB support]), [ + usb_enable=${enableval} + ]) + + AC_ARG_ENABLE(netlink, AC_HELP_STRING([--enable-netlink], [enable NETLINK support]), [ + netlink_enable=${enableval} + ]) + + AC_ARG_ENABLE(tools, AC_HELP_STRING([--enable-tools], [install Bluetooth utilities]), [ + tools_enable=${enableval} + ]) + + AC_ARG_ENABLE(bccmd, AC_HELP_STRING([--enable-bccmd], [install BCCMD interface utility]), [ + bccmd_enable=${enableval} + ]) + + AC_ARG_ENABLE(pcmcia, AC_HELP_STRING([--enable-pcmcia], [install PCMCIA serial script]), [ + pcmcia_enable=${enableval} + ]) + + AC_ARG_ENABLE(hid2hci, AC_HELP_STRING([--enable-hid2hci], [install HID mode switching utility]), [ + hid2hci_enable=${enableval} + ]) + + AC_ARG_ENABLE(dfutool, AC_HELP_STRING([--enable-dfutool], [install DFU firmware upgrade utility]), [ + dfutool_enable=${enableval} + ]) + + AC_ARG_ENABLE(hidd, AC_HELP_STRING([--enable-hidd], [install HID daemon]), [ + hidd_enable=${enableval} + ]) + + AC_ARG_ENABLE(pand, AC_HELP_STRING([--enable-pand], [install PAN daemon]), [ + pand_enable=${enableval} + ]) + + AC_ARG_ENABLE(dund, AC_HELP_STRING([--enable-dund], [install DUN daemon]), [ + dund_enable=${enableval} + ]) + + AC_ARG_ENABLE(cups, AC_HELP_STRING([--enable-cups], [install CUPS backend support]), [ + cups_enable=${enableval} + ]) + + AC_ARG_ENABLE(test, AC_HELP_STRING([--enable-test], [install test programs]), [ + test_enable=${enableval} + ]) + + AC_ARG_ENABLE(manpages, AC_HELP_STRING([--enable-manpages], [install Bluetooth manual pages]), [ + manpages_enable=${enableval} + ]) + + AC_ARG_ENABLE(udevrules, AC_HELP_STRING([--enable-udevrules], [install Bluetooth udev rules]), [ + udevrules_enable=${enableval} + ]) + + AC_ARG_ENABLE(configfiles, AC_HELP_STRING([--enable-configfiles], [install Bluetooth configuration files]), [ + configfiles_enable=${enableval} + ]) + + AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], [enable compiling with debugging information]), [ + debug_enable=${enableval} + ]) + + AC_ARG_WITH(telephony, AC_HELP_STRING([--with-telephony=DRIVER], [select telephony driver]), [ + telephony_driver=${withval} + ]) + + AC_SUBST([TELEPHONY_DRIVER], [telephony-${telephony_driver}.c]) + + if (test "${fortify_enable}" = "yes"); then + CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2" + fi + + if (test "${pie_enable}" = "yes" && test "${ac_cv_prog_cc_pie}" = "yes"); then + CFLAGS="$CFLAGS -fPIC" + LDFLAGS="$LDFLAGS -pie" + fi + + if (test "${debug_enable}" = "yes" && test "${ac_cv_prog_cc_g}" = "yes"); then + CFLAGS="$CFLAGS -g" + fi + + if (test "${optimization_enable}" = "no"); then + CFLAGS="$CFLAGS -O0" + fi + + if (test "${usb_enable}" = "yes" && test "${usb_found}" = "yes"); then + AC_DEFINE(HAVE_LIBUSB, 1, [Define to 1 if you have USB library.]) + fi + + AC_SUBST([BLUEZ_CFLAGS], ['-I$(top_builddir)/include']) + AC_SUBST([BLUEZ_LIBS], ['$(top_builddir)/lib/libbluetooth.la']) + + AC_SUBST([GDBUS_CFLAGS], ['-I$(top_srcdir)/gdbus']) + AC_SUBST([GDBUS_LIBS], ['$(top_builddir)/gdbus/libgdbus.la']) + + AC_SUBST([SBC_CFLAGS], ['-I$(top_srcdir)/sbc']) + AC_SUBST([SBC_LIBS], ['$(top_builddir)/sbc/libsbc.la']) + + AM_CONDITIONAL(SNDFILE, test "${sndfile_enable}" = "yes" && test "${sndfile_found}" = "yes") + AM_CONDITIONAL(NETLINK, test "${netlink_enable}" = "yes" && test "${netlink_found}" = "yes") + AM_CONDITIONAL(USB, test "${usb_enable}" = "yes" && test "${usb_found}" = "yes") + AM_CONDITIONAL(SBC, test "${alsa_enable}" = "yes" || test "${gstreamer_enable}" = "yes") + AM_CONDITIONAL(ALSA, test "${alsa_enable}" = "yes" && test "${alsa_found}" = "yes") + AM_CONDITIONAL(GSTREAMER, test "${gstreamer_enable}" = "yes" && test "${gstreamer_found}" = "yes") + AM_CONDITIONAL(AUDIOPLUGIN, test "${audio_enable}" = "yes") + AM_CONDITIONAL(INPUTPLUGIN, test "${input_enable}" = "yes") + AM_CONDITIONAL(SERIALPLUGIN, test "${serial_enable}" = "yes") + AM_CONDITIONAL(NETWORKPLUGIN, test "${network_enable}" = "yes") + AM_CONDITIONAL(SERVICEPLUGIN, test "${service_enable}" = "yes") + AM_CONDITIONAL(HIDD, test "${hidd_enable}" = "yes") + AM_CONDITIONAL(PAND, test "${pand_enable}" = "yes") + AM_CONDITIONAL(DUND, test "${dund_enable}" = "yes") + AM_CONDITIONAL(CUPS, test "${cups_enable}" = "yes") + AM_CONDITIONAL(TEST, test "${test_enable}" = "yes") + AM_CONDITIONAL(TOOLS, test "${tools_enable}" = "yes") + AM_CONDITIONAL(BCCMD, test "${bccmd_enable}" = "yes") + AM_CONDITIONAL(PCMCIA, test "${pcmcia_enable}" = "yes") + AM_CONDITIONAL(HID2HCI, test "${hid2hci_enable}" = "yes" && test "${usb_found}" = "yes") + AM_CONDITIONAL(DFUTOOL, test "${dfutool_enable}" = "yes" && test "${usb_found}" = "yes") + AM_CONDITIONAL(MANPAGES, test "${manpages_enable}" = "yes") + AM_CONDITIONAL(UDEVRULES, test "${udevrules_enable}" = "yes") + AM_CONDITIONAL(CONFIGFILES, test "${configfiles_enable}" = "yes") +]) diff --git a/audio/Android.mk b/audio/Android.mk new file mode 100755 index 000000000..9537e33c6 --- /dev/null +++ b/audio/Android.mk @@ -0,0 +1,74 @@ +LOCAL_PATH:= $(call my-dir) + +# A2DP plugin + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + a2dp.c \ + avdtp.c \ + control.c \ + device.c \ + gateway.c \ + headset.c \ + ipc.c \ + main.c \ + manager.c \ + module-bluetooth-sink.c \ + sink.c \ + source.c \ + telephony-dummy.c \ + unix.c + +LOCAL_CFLAGS:= \ + -DVERSION=\"4.47\" \ + -DSTORAGEDIR=\"/data/misc/bluetoothd\" \ + -DCONFIGDIR=\"/etc/bluez\" \ + -DANDROID \ + -D__S_IFREG=0100000 # missing from bionic stat.h + +LOCAL_C_INCLUDES:= \ + $(LOCAL_PATH)/../include \ + $(LOCAL_PATH)/../common \ + $(LOCAL_PATH)/../gdbus \ + $(LOCAL_PATH)/../src \ + $(call include-path-for, glib) \ + $(call include-path-for, dbus) + +LOCAL_SHARED_LIBRARIES := \ + libbluetooth \ + libbluetoothd \ + libdbus + + +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/bluez-plugin +LOCAL_UNSTRIPPED_PATH := $(TARGET_OUT_SHARED_LIBRARIES_UNSTRIPPED)/bluez-plugin +LOCAL_MODULE := audio + +include $(BUILD_SHARED_LIBRARY) + +# +# liba2dp +# This is linked to Audioflinger so **LGPL only** + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + liba2dp.c \ + ipc.c \ + ../sbc/sbc.c.arm \ + ../sbc/sbc_primitives.c \ + ../sbc/sbc_primitives_neon.c + +# to improve SBC performance +LOCAL_CFLAGS:= -funroll-loops + +LOCAL_C_INCLUDES:= \ + $(LOCAL_PATH)/../sbc \ + +LOCAL_SHARED_LIBRARIES := \ + libcutils + +LOCAL_MODULE := liba2dp + +include $(BUILD_SHARED_LIBRARY) diff --git a/audio/Makefile.am b/audio/Makefile.am new file mode 100644 index 000000000..52ad21280 --- /dev/null +++ b/audio/Makefile.am @@ -0,0 +1,87 @@ + +BUILT_SOURCES = telephony.c + +if AUDIOPLUGIN +plugindir = $(libdir)/bluetooth/plugins + +plugin_LTLIBRARIES = audio.la + +audio_la_SOURCES = main.c \ + ipc.h ipc.c unix.h unix.c manager.h manager.c telephony.h \ + device.h device.c headset.h headset.c gateway.h gateway.c \ + avdtp.h avdtp.c a2dp.h a2dp.c sink.h sink.c source.h source.c \ + control.h control.c + +nodist_audio_la_SOURCES = $(BUILT_SOURCES) + +audio_la_LDFLAGS = -module -avoid-version -no-undefined + +LDADD = $(top_builddir)/common/libhelper.a \ + @GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@ + +if ALSA +alsadir = $(libdir)/alsa-lib + +alsa_LTLIBRARIES = libasound_module_pcm_bluetooth.la libasound_module_ctl_bluetooth.la + +libasound_module_pcm_bluetooth_la_SOURCES = pcm_bluetooth.c rtp.h ipc.h ipc.c +libasound_module_pcm_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_pcm_.* +libasound_module_pcm_bluetooth_la_LIBADD = @SBC_LIBS@ @BLUEZ_LIBS@ @ALSA_LIBS@ +libasound_module_pcm_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ @BLUEZ_CFLAGS@ @SBC_CFLAGS@ + +libasound_module_ctl_bluetooth_la_SOURCES = ctl_bluetooth.c rtp.h ipc.h ipc.c +libasound_module_ctl_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_ctl_.* +libasound_module_ctl_bluetooth_la_LIBADD = @BLUEZ_LIBS@ @ALSA_LIBS@ +libasound_module_ctl_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ @BLUEZ_CFLAGS@ + +if CONFIGFILES +alsaconfdir = $(sysconfdir)/alsa + +alsaconf_DATA = bluetooth.conf +endif +endif + +if GSTREAMER +gstreamerdir = $(libdir)/gstreamer-0.10 + +gstreamer_LTLIBRARIES = libgstbluetooth.la + +libgstbluetooth_la_SOURCES = gstbluetooth.c \ + gstsbcenc.h gstsbcenc.c \ + gstsbcdec.h gstsbcdec.c \ + gstsbcparse.h gstsbcparse.c \ + gstavdtpsink.h gstavdtpsink.c \ + gsta2dpsink.h gsta2dpsink.c \ + gstsbcutil.h gstsbcutil.c \ + gstrtpsbcpay.h gstrtpsbcpay.c \ + rtp.h ipc.h ipc.c +libgstbluetooth_la_LDFLAGS = -module -avoid-version +libgstbluetooth_la_LIBADD = @SBC_LIBS@ @BLUEZ_LIBS@ @GSTREAMER_LIBS@ \ + -lgstaudio-0.10 -lgstrtp-0.10 +libgstbluetooth_la_CFLAGS = -fvisibility=hidden -fno-strict-aliasing \ + @GSTREAMER_CFLAGS@ @BLUEZ_CFLAGS@ @SBC_CFLAGS@ +endif +endif + +noinst_LTLIBRARIES = libipc.la + +libipc_la_SOURCES = ipc.h ipc.c + +noinst_PROGRAMS = ipctest + +ipctest_LDADD= libipc.la @SBC_LIBS@ @GLIB_LIBS@ + +AM_CFLAGS = -fvisibility=hidden @SBC_CFLAGS@ \ + @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@ + +CLEANFILES = $(BUILT_SOURCES) + +INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/src + +EXTRA_DIST = audio.conf telephony-dummy.c telephony-maemo.c telephony-ofono.c \ + bluetooth.conf + +MAINTAINERCLEANFILES = Makefile.in + +telephony.c: @TELEPHONY_DRIVER@ + @if [ ! -e $@ ] ; then $(LN_S) $< $@ ; fi diff --git a/audio/a2dp.c b/audio/a2dp.c new file mode 100644 index 000000000..45be5d4bf --- /dev/null +++ b/audio/a2dp.c @@ -0,0 +1,1626 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include + +#include "logging.h" +#include "device.h" +#include "manager.h" +#include "avdtp.h" +#include "sink.h" +#include "source.h" +#include "a2dp.h" +#include "sdpd.h" + +/* The duration that streams without users are allowed to stay in + * STREAMING state. */ +#define SUSPEND_TIMEOUT 5 +#define RECONFIGURE_TIMEOUT 500 + +#ifndef MIN +# define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef MAX +# define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +struct a2dp_sep { + uint8_t type; + uint8_t codec; + struct avdtp_local_sep *sep; + struct avdtp *session; + struct avdtp_stream *stream; + guint suspend_timer; + gboolean locked; + gboolean suspending; + gboolean starting; +}; + +struct a2dp_setup_cb { + a2dp_config_cb_t config_cb; + a2dp_stream_cb_t resume_cb; + a2dp_stream_cb_t suspend_cb; + void *user_data; + unsigned int id; +}; + +struct a2dp_setup { + struct audio_device *dev; + struct avdtp *session; + struct a2dp_sep *sep; + struct avdtp_stream *stream; + struct avdtp_error *err; + GSList *client_caps; + gboolean reconfigure; + gboolean canceled; + gboolean start; + GSList *cb; + int ref; +}; + +static DBusConnection *connection = NULL; + +struct a2dp_server { + bdaddr_t src; + GSList *sinks; + GSList *sources; + uint32_t source_record_id; + uint32_t sink_record_id; +}; + +static GSList *servers = NULL; +static GSList *setups = NULL; +static unsigned int cb_id = 0; + +static struct a2dp_setup *setup_ref(struct a2dp_setup *setup) +{ + setup->ref++; + + debug("setup_ref(%p): ref=%d", setup, setup->ref); + + return setup; +} + +static void setup_free(struct a2dp_setup *s) +{ + debug("setup_free(%p)", s); + setups = g_slist_remove(setups, s); + if (s->session) + avdtp_unref(s->session); + g_slist_foreach(s->cb, (GFunc) g_free, NULL); + g_slist_free(s->cb); + g_free(s); +} + +static void setup_unref(struct a2dp_setup *setup) +{ + setup->ref--; + + debug("setup_unref(%p): ref=%d", setup, setup->ref); + + if (setup->ref <= 0) + setup_free(setup); +} + +static struct audio_device *a2dp_get_dev(struct avdtp *session) +{ + bdaddr_t src, dst; + + avdtp_get_peers(session, &src, &dst); + + return manager_find_device(NULL, &src, &dst, NULL, FALSE); +} + +static gboolean finalize_config(struct a2dp_setup *s) +{ + GSList *l; + + setup_ref(s); + for (l = s->cb; l != NULL; l = l->next) { + struct a2dp_setup_cb *cb = l->data; + struct avdtp_stream *stream = s->err ? NULL : s->stream; + + if (!cb->config_cb) + continue; + + cb->config_cb(s->session, s->sep, stream, s->err, + cb->user_data); + cb->config_cb = NULL; + setup_unref(s); + } + + setup_unref(s); + return FALSE; +} + +static gboolean finalize_config_errno(struct a2dp_setup *s, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err); + s->err = err ? &avdtp_err : NULL; + + return finalize_config(s); +} + +static gboolean finalize_resume(struct a2dp_setup *s) +{ + GSList *l; + + setup_ref(s); + for (l = s->cb; l != NULL; l = l->next) { + struct a2dp_setup_cb *cb = l->data; + + if (cb && cb->resume_cb) { + cb->resume_cb(s->session, s->err, cb->user_data); + cb->resume_cb = NULL; + setup_unref(s); + } + } + + setup_unref(s); + return FALSE; +} + +static gboolean finalize_suspend(struct a2dp_setup *s) +{ + GSList *l; + + setup_ref(s); + for (l = s->cb; l != NULL; l = l->next) { + struct a2dp_setup_cb *cb = l->data; + + if (cb->suspend_cb) { + cb->suspend_cb(s->session, s->err, cb->user_data); + cb->suspend_cb = NULL; + setup_unref(s); + } + } + + setup_unref(s); + return FALSE; +} + +static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err); + s->err = err ? &avdtp_err : NULL; + + return finalize_suspend(s); +} + +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 *find_setup_by_dev(struct audio_device *dev) +{ + GSList *l; + + for (l = setups; l != NULL; l = l->next) { + struct a2dp_setup *setup = l->data; + + if (setup->dev == dev) + return setup; + } + + return NULL; +} + +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; + + if (new_state != AVDTP_STATE_IDLE) + return; + + if (sep->suspend_timer) { + g_source_remove(sep->suspend_timer); + sep->suspend_timer = 0; + } + + if (sep->session) { + avdtp_unref(sep->session); + sep->session = NULL; + } + + sep->stream = NULL; + +} + +static gboolean sbc_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + GSList *caps, uint8_t *err, + uint8_t *category, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct audio_device *dev; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap; + struct sbc_codec_cap *sbc_cap; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Set_Configuration_Ind", sep); + else + debug("Source %p: Set_Configuration_Ind", sep); + + dev = a2dp_get_dev(session); + if (!dev) { + *err = AVDTP_UNSUPPORTED_CONFIGURATION; + *category = 0x00; + return FALSE; + } + + /* Check bipool range */ + for (codec_cap = NULL; caps; caps = g_slist_next(caps)) { + cap = caps->data; + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + if (cap->length < sizeof(struct sbc_codec_cap)) + continue; + + codec_cap = (void *) cap->data; + + if (codec_cap->media_codec_type != A2DP_CODEC_SBC) + continue; + + sbc_cap = (void *) codec_cap; + + if (sbc_cap->min_bitpool < MIN_BITPOOL || + sbc_cap->max_bitpool > MAX_BITPOOL) { + *err = AVDTP_UNSUPPORTED_CONFIGURATION; + *category = AVDTP_MEDIA_CODEC; + return FALSE; + } + + break; + } + + avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep); + a2dp_sep->stream = stream; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE) + sink_new_stream(dev, session, stream); + + return TRUE; +} + +static gboolean sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep, + GSList **caps, uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Get_Capability_Ind", sep); + else + debug("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); + + memset(&sbc_cap, 0, sizeof(struct sbc_codec_cap)); + + sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC; + +#ifdef ANDROID + sbc_cap.frequency = SBC_SAMPLING_FREQ_44100; +#else + sbc_cap.frequency = ( SBC_SAMPLING_FREQ_48000 | + SBC_SAMPLING_FREQ_44100 | + SBC_SAMPLING_FREQ_32000 | + SBC_SAMPLING_FREQ_16000 ); +#endif + + sbc_cap.channel_mode = ( SBC_CHANNEL_MODE_JOINT_STEREO | + SBC_CHANNEL_MODE_STEREO | + SBC_CHANNEL_MODE_DUAL_CHANNEL | + SBC_CHANNEL_MODE_MONO ); + + sbc_cap.block_length = ( SBC_BLOCK_LENGTH_16 | + SBC_BLOCK_LENGTH_12 | + SBC_BLOCK_LENGTH_8 | + SBC_BLOCK_LENGTH_4 ); + + sbc_cap.subbands = ( SBC_SUBBANDS_8 | SBC_SUBBANDS_4 ); + + sbc_cap.allocation_method = ( SBC_ALLOCATION_LOUDNESS | + SBC_ALLOCATION_SNR ); + + sbc_cap.min_bitpool = MIN_BITPOOL; + sbc_cap.max_bitpool = MAX_BITPOOL; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + return TRUE; +} + +static gboolean mpeg_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + GSList *caps, uint8_t *err, + uint8_t *category, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct audio_device *dev; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Set_Configuration_Ind", sep); + else + debug("Source %p: Set_Configuration_Ind", sep); + + dev = a2dp_get_dev(session); + if (!dev) { + *err = AVDTP_UNSUPPORTED_CONFIGURATION; + *category = 0x00; + return FALSE; + } + + avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep); + a2dp_sep->stream = stream; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE) + sink_new_stream(dev, session, stream); + + return TRUE; +} + +static gboolean mpeg_getcap_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + GSList **caps, uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct mpeg_codec_cap mpeg_cap; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Get_Capability_Ind", sep); + else + debug("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); + + memset(&mpeg_cap, 0, sizeof(struct mpeg_codec_cap)); + + mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12; + + mpeg_cap.frequency = ( MPEG_SAMPLING_FREQ_48000 | + MPEG_SAMPLING_FREQ_44100 | + MPEG_SAMPLING_FREQ_32000 | + MPEG_SAMPLING_FREQ_24000 | + MPEG_SAMPLING_FREQ_22050 | + MPEG_SAMPLING_FREQ_16000 ); + + mpeg_cap.channel_mode = ( MPEG_CHANNEL_MODE_JOINT_STEREO | + MPEG_CHANNEL_MODE_STEREO | + MPEG_CHANNEL_MODE_DUAL_CHANNEL | + MPEG_CHANNEL_MODE_MONO ); + + mpeg_cap.layer = ( MPEG_LAYER_MP3 | MPEG_LAYER_MP2 | MPEG_LAYER_MP1 ); + + mpeg_cap.bitrate = 0xFFFF; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap, + sizeof(mpeg_cap)); + + *caps = g_slist_append(*caps, media_codec); + + return TRUE; +} + +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 audio_device *dev; + int ret; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Set_Configuration_Cfm", sep); + else + debug("Source %p: Set_Configuration_Cfm", sep); + + setup = find_setup_by_session(session); + + if (err) { + if (setup) { + setup->err = err; + finalize_config(setup); + } + return; + } + + avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep); + a2dp_sep->stream = stream; + + if (!setup) + return; + + dev = a2dp_get_dev(session); + + /* Notify D-Bus interface of the new stream */ + if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE) + sink_new_stream(dev, session, setup->stream); + else + source_new_stream(dev, session, setup->stream); + + ret = avdtp_open(session, stream); + if (ret < 0) { + error("Error on avdtp_open %s (%d)", strerror(-ret), -ret); + setup->stream = NULL; + finalize_config_errno(setup, ret); + } +} + +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) + debug("Sink %p: Get_Configuration_Ind", sep); + else + debug("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) + debug("Sink %p: Set_Configuration_Cfm", sep); + else + debug("Source %p: Set_Configuration_Cfm", sep); +} + +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; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Open_Ind", sep); + else + debug("Source %p: Open_Ind", sep); + 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) + debug("Sink %p: Open_Cfm", sep); + else + debug("Source %p: Open_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (setup->canceled) { + if (!err) + avdtp_close(session, stream); + setup_unref(setup); + return; + } + + if (setup->reconfigure) + setup->reconfigure = FALSE; + + if (err) { + setup->stream = NULL; + setup->err = err; + } + + finalize_config(setup); +} + +static gboolean suspend_timeout(struct a2dp_sep *sep) +{ + if (avdtp_suspend(sep->session, sep->stream) == 0) + sep->suspending = TRUE; + + sep->suspend_timer = 0; + + avdtp_unref(sep->session); + sep->session = NULL; + + 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_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Start_Ind", sep); + else + debug("Source %p: Start_Ind", sep); + + setup = find_setup_by_session(session); + if (setup) { + if (setup->canceled) + setup_unref(setup); + else + finalize_resume(setup); + } + + if (!a2dp_sep->locked) { + a2dp_sep->session = avdtp_ref(session); + a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT, + (GSourceFunc) suspend_timeout, + a2dp_sep); + } + + 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_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Start_Cfm", sep); + else + debug("Source %p: Start_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (setup->canceled) { + if (!err) + avdtp_close(session, stream); + setup_unref(setup); + return; + } + + if (err) { + setup->stream = NULL; + setup->err = 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; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Suspend_Ind", sep); + else + debug("Source %p: Suspend_Ind", sep); + + if (a2dp_sep->suspend_timer) { + g_source_remove(a2dp_sep->suspend_timer); + a2dp_sep->suspend_timer = 0; + avdtp_unref(a2dp_sep->session); + a2dp_sep->session = 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_setup *setup; + gboolean start; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Suspend_Cfm", sep); + else + debug("Source %p: Suspend_Cfm", sep); + + a2dp_sep->suspending = FALSE; + + setup = find_setup_by_session(session); + if (!setup) + return; + + start = setup->start; + setup->start = FALSE; + + if (err) { + setup->stream = NULL; + setup->err = err; + finalize_suspend(setup); + } + else + finalize_suspend_errno(setup, 0); + + if (!start) + return; + + if (err) { + setup->err = err; + finalize_suspend(setup); + } else if (avdtp_start(session, a2dp_sep->stream) < 0) { + struct avdtp_error start_err; + error("avdtp_start failed"); + avdtp_error_init(&start_err, AVDTP_ERROR_ERRNO, EIO); + setup->err = err; + finalize_suspend(setup); + } +} + +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; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Close_Ind", sep); + else + debug("Source %p: Close_Ind", sep); + + return TRUE; +} + +static gboolean a2dp_reconfigure(gpointer data) +{ + struct a2dp_setup *setup = data; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap = NULL; + GSList *l; + int posix_err; + + for (l = setup->client_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) { + error("Cannot find capabilities to reconfigure"); + posix_err = -EINVAL; + goto failed; + } + + posix_err = avdtp_get_seps(setup->session, AVDTP_SEP_TYPE_SINK, + codec_cap->media_type, + codec_cap->media_codec_type, + &lsep, &rsep); + if (posix_err < 0) { + error("No matching ACP and INT SEPs found"); + goto failed; + } + + posix_err = avdtp_set_configuration(setup->session, rsep, lsep, + setup->client_caps, + &setup->stream); + if (posix_err < 0) { + error("avdtp_set_configuration: %s", strerror(-posix_err)); + goto failed; + } + + return FALSE; + +failed: + finalize_config_errno(setup, posix_err); + return FALSE; +} + +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) + debug("Sink %p: Close_Cfm", sep); + else + debug("Source %p: Close_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (setup->canceled) { + setup_unref(setup); + return; + } + + if (err) { + setup->stream = NULL; + setup->err = err; + finalize_config(setup); + return; + } + + if (setup->reconfigure) + g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup); +} + +static gboolean 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; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Abort_Ind", sep); + else + debug("Source %p: Abort_Ind", sep); + + a2dp_sep->stream = NULL; + + return TRUE; +} + +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) + debug("Sink %p: Abort_Cfm", sep); + else + debug("Source %p: Abort_Cfm", sep); + + setup = find_setup_by_session(session); + if (!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) + debug("Sink %p: ReConfigure_Ind", sep); + else + debug("Source %p: ReConfigure_Ind", sep); + 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) + debug("Sink %p: ReConfigure_Cfm", sep); + else + debug("Source %p: ReConfigure_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (setup->canceled) { + if (!err) + avdtp_close(session, stream); + setup_unref(setup); + return; + } + + if (err) { + setup->stream = NULL; + setup->err = err; + } + + finalize_config(setup); +} + +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 +}; + +static struct avdtp_sep_ind sbc_ind = { + .get_capability = sbc_getcap_ind, + .set_configuration = sbc_setconf_ind, + .get_configuration = getconf_ind, + .open = open_ind, + .start = start_ind, + .suspend = suspend_ind, + .close = close_ind, + .abort = abort_ind, + .reconfigure = reconf_ind +}; + +static struct avdtp_sep_ind mpeg_ind = { + .get_capability = mpeg_getcap_ind, + .set_configuration = mpeg_setconf_ind, + .get_configuration = getconf_ind, + .open = open_ind, + .start = start_ind, + .suspend = suspend_ind, + .close = close_ind, + .abort = abort_ind, + .reconfigure = reconf_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, ver = 0x0100, 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 = 0x0100; + 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, &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_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type, + uint8_t codec) +{ + struct a2dp_sep *sep; + GSList **l; + uint32_t *record_id; + sdp_record_t *record; + struct avdtp_sep_ind *ind; + + sep = g_new0(struct a2dp_sep, 1); + + ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind; + sep->sep = avdtp_register_sep(&server->src, type, + AVDTP_MEDIA_TYPE_AUDIO, codec, ind, + &cfm, sep); + if (sep->sep == NULL) { + g_free(sep); + return NULL; + } + + sep->codec = codec; + sep->type = type; + + 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"); + avdtp_unregister_sep(sep->sep); + g_free(sep); + return NULL; + } + + if (add_record_to_server(&server->src, record) < 0) { + error("Unable to register A2DP service record");\ + sdp_record_free(record); + avdtp_unregister_sep(sep->sep); + g_free(sep); + return NULL; + } + *record_id = record->handle; + +add: + *l = g_slist_append(*l, sep); + + return sep; +} + +static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct a2dp_server *server = l->data; + + if (bacmp(&server->src, src) == 0) + return server; + } + + return NULL; +} + +int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) +{ + int sbc_srcs = 1, sbc_sinks = 1; + int mpeg12_srcs = 0, mpeg12_sinks = 0; + gboolean source = TRUE, sink = FALSE; + char *str; + GError *err = NULL; + int i; + struct a2dp_server *server; + + if (!config) + goto proceed; + + str = g_key_file_get_string(config, "General", "Enable", &err); + + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + if (strstr(str, "Sink")) + source = TRUE; + if (strstr(str, "Source")) + sink = TRUE; + g_free(str); + } + + str = g_key_file_get_string(config, "General", "Disable", &err); + + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + if (strstr(str, "Sink")) + source = FALSE; + if (strstr(str, "Source")) + sink = FALSE; + g_free(str); + } + + str = g_key_file_get_string(config, "A2DP", "SBCSources", &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + sbc_srcs = atoi(str); + g_free(str); + } + + str = g_key_file_get_string(config, "A2DP", "MPEG12Sources", &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + mpeg12_srcs = atoi(str); + g_free(str); + } + + str = g_key_file_get_string(config, "A2DP", "SBCSinks", &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + sbc_sinks = atoi(str); + g_free(str); + } + + str = g_key_file_get_string(config, "A2DP", "MPEG12Sinks", &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + mpeg12_sinks = atoi(str); + g_free(str); + } + +proceed: + if (!connection) + connection = dbus_connection_ref(conn); + + server = find_server(servers, src); + if (!server) { + int av_err; + + server = g_new0(struct a2dp_server, 1); + if (!server) + return -ENOMEM; + + av_err = avdtp_init(src, config); + if (av_err < 0) + return av_err; + + bacpy(&server->src, src); + servers = g_slist_append(servers, server); + } + + if (source) { + for (i = 0; i < sbc_srcs; i++) + a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE, + A2DP_CODEC_SBC); + + for (i = 0; i < mpeg12_srcs; i++) + a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE, + A2DP_CODEC_MPEG12); + } + + if (sink) { + for (i = 0; i < sbc_sinks; i++) + a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK, + A2DP_CODEC_SBC); + + for (i = 0; i < mpeg12_sinks; i++) + a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK, + A2DP_CODEC_MPEG12); + } + + return 0; +} + +static void a2dp_unregister_sep(struct a2dp_sep *sep) +{ + avdtp_unregister_sep(sep->sep); + g_free(sep); +} + +void a2dp_unregister(const bdaddr_t *src) +{ + struct a2dp_server *server; + + server = find_server(servers, src); + if (!server) + return; + + g_slist_foreach(server->sinks, (GFunc) a2dp_unregister_sep, NULL); + g_slist_free(server->sinks); + + g_slist_foreach(server->sources, (GFunc) a2dp_unregister_sep, NULL); + g_slist_free(server->sources); + + avdtp_exit(src); + + if (server->source_record_id) + remove_record_from_server(server->source_record_id); + + if (server->sink_record_id) + remove_record_from_server(server->sink_record_id); + + servers = g_slist_remove(servers, server); + g_free(server); + + if (servers) + return; + + dbus_connection_unref(connection); + connection = NULL; +} + +struct a2dp_sep *a2dp_get(struct avdtp *session, + struct avdtp_remote_sep *rsep) +{ + GSList *l; + struct a2dp_server *server; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap = NULL; + bdaddr_t src; + + avdtp_get_peers(session, &src, NULL); + server = find_server(servers, &src); + if (!server) + return NULL; + + cap = avdtp_get_codec(rsep); + codec_cap = (void *) cap->data; + + if (avdtp_get_type(rsep) == AVDTP_SEP_TYPE_SINK) + l = server->sources; + else + l = server->sinks; + + for (; l != NULL; l = l->next) { + struct a2dp_sep *sep = l->data; + + if (sep->locked) + continue; + + if (sep->codec != codec_cap->media_codec_type) + continue; + + if (!sep->stream || avdtp_has_stream(session, sep->stream)) + return sep; + } + + return NULL; +} + +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_sep *tmp; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap = NULL; + int posix_err; + bdaddr_t src; + uint8_t remote_type; + + avdtp_get_peers(session, &src, NULL); + server = find_server(servers, &src); + 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; + + debug("a2dp_config: selected SEP %p", sep->sep); + + cb_data = g_new0(struct a2dp_setup_cb, 1); + cb_data->config_cb = cb; + cb_data->user_data = user_data; + cb_data->id = ++cb_id; + + setup = find_setup_by_session(session); + if (!setup) { + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setup->dev = a2dp_get_dev(session); + setups = g_slist_append(setups, setup); + } + + setup_ref(setup); + setup->cb = g_slist_append(setup->cb, cb_data); + setup->sep = sep; + setup->stream = sep->stream; + setup->client_caps = caps; + + switch (avdtp_sep_get_state(sep->sep)) { + case AVDTP_STATE_IDLE: + if (sep->type == AVDTP_SEP_TYPE_SOURCE) { + l = server->sources; + remote_type = AVDTP_SEP_TYPE_SINK; + } else { + remote_type = AVDTP_SEP_TYPE_SOURCE; + l = server->sinks; + } + + for (; l != NULL; l = l->next) { + tmp = l->data; + if (avdtp_has_stream(session, tmp->stream)) + break; + } + + if (l != NULL) { + setup->reconfigure = TRUE; + if (avdtp_close(session, tmp->stream) < 0) { + error("avdtp_close failed"); + goto failed; + } + break; + } + + if (avdtp_get_seps(session, remote_type, + codec_cap->media_type, + codec_cap->media_codec_type, + &lsep, &rsep) < 0) { + error("No matching ACP and INT SEPs found"); + goto failed; + } + + posix_err = avdtp_set_configuration(session, rsep, 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)) { + debug("Configuration match: resuming"); + g_idle_add((GSourceFunc) finalize_config, setup); + } else if (!setup->reconfigure) { + setup->reconfigure = TRUE; + if (avdtp_close(session, sep->stream) < 0) { + error("avdtp_close failed"); + goto failed; + } + } + break; + default: + error("SEP in bad state for requesting a new stream"); + goto failed; + } + + return cb_data->id; + +failed: + setup_unref(setup); + cb_id--; + 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; + + cb_data = g_new0(struct a2dp_setup_cb, 1); + cb_data->resume_cb = cb; + cb_data->user_data = user_data; + cb_data->id = ++cb_id; + + setup = find_setup_by_session(session); + if (!setup) { + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setup->dev = a2dp_get_dev(session); + setups = g_slist_append(setups, setup); + } + + setup_ref(setup); + setup->cb = g_slist_append(setup->cb, cb_data); + setup->sep = sep; + setup->stream = sep->stream; + + switch (avdtp_sep_get_state(sep->sep)) { + case AVDTP_STATE_IDLE: + goto failed; + break; + case AVDTP_STATE_OPEN: + if (avdtp_start(session, sep->stream) < 0) { + error("avdtp_start failed"); + goto failed; + } + break; + case AVDTP_STATE_STREAMING: + if (!sep->suspending && sep->suspend_timer) { + g_source_remove(sep->suspend_timer); + sep->suspend_timer = 0; + avdtp_unref(sep->session); + sep->session = NULL; + } + if (sep->suspending) + setup->start = TRUE; + else + g_idle_add((GSourceFunc) finalize_resume, setup); + break; + default: + error("SEP in bad state for resume"); + goto failed; + } + + return cb_data->id; + +failed: + setup_unref(setup); + cb_id--; + 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; + + cb_data = g_new0(struct a2dp_setup_cb, 1); + cb_data->suspend_cb = cb; + cb_data->user_data = user_data; + cb_data->id = ++cb_id; + + setup = find_setup_by_session(session); + if (!setup) { + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setup->dev = a2dp_get_dev(session); + setups = g_slist_append(setups, setup); + } + + setup_ref(setup); + setup->cb = g_slist_append(setup->cb, cb_data); + setup->sep = sep; + setup->stream = sep->stream; + + switch (avdtp_sep_get_state(sep->sep)) { + case AVDTP_STATE_IDLE: + error("a2dp_suspend: no stream to suspend"); + goto failed; + break; + case AVDTP_STATE_OPEN: + g_idle_add((GSourceFunc) finalize_suspend, setup); + break; + case AVDTP_STATE_STREAMING: + if (avdtp_suspend(session, sep->stream) < 0) { + error("avdtp_suspend failed"); + goto failed; + } + break; + default: + error("SEP in bad state for suspend"); + goto failed; + } + + return cb_data->id; + +failed: + setup_unref(setup); + cb_id--; + return 0; +} + +gboolean a2dp_cancel(struct audio_device *dev, unsigned int id) +{ + struct a2dp_setup_cb *cb_data; + struct a2dp_setup *setup; + GSList *l; + + debug("a2dp_cancel()"); + + setup = find_setup_by_dev(dev); + if (!setup) + return FALSE; + + for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) { + struct a2dp_setup_cb *cb = l->data; + + if (cb->id == id) { + cb_data = cb; + break; + } + } + + if (!cb_data) + error("a2dp_cancel: no matching callback with id %u", id); + + setup->cb = g_slist_remove(setup->cb, cb_data); + g_free(cb_data); + + if (!setup->cb) { + setup->canceled = TRUE; + setup->sep = NULL; + } + + return TRUE; +} + +gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session) +{ + if (sep->locked) + return FALSE; + + debug("SEP %p locked", sep->sep); + sep->locked = TRUE; + + return TRUE; +} + +gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session) +{ + avdtp_state_t state; + + state = avdtp_sep_get_state(sep->sep); + + sep->locked = FALSE; + + debug("SEP %p unlocked", sep->sep); + + if (!sep->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, sep->stream) == 0) + sep->suspending = TRUE; + break; + default: + break; + } + + return TRUE; +} + +gboolean a2dp_sep_get_lock(struct a2dp_sep *sep) +{ + return sep->locked; +} + +static int stream_cmp(gconstpointer data, gconstpointer user_data) +{ + const struct a2dp_sep *sep = data; + const struct avdtp_stream *stream = user_data; + + return (sep->stream != stream); +} + +struct a2dp_sep *a2dp_get_sep(struct avdtp *session, + struct avdtp_stream *stream) +{ + struct a2dp_server *server; + bdaddr_t src, dst; + GSList *l; + + avdtp_get_peers(session, &src, &dst); + + for (l = servers; l; l = l->next) { + server = l->data; + + if (bacmp(&src, &server->src) == 0) + break; + } + + if (!l) + return NULL; + + l = g_slist_find_custom(server->sources, stream, stream_cmp); + if (l) + return l->data; + + l = g_slist_find_custom(server->sinks, stream, stream_cmp); + if (l) + return l->data; + + return NULL; +} diff --git a/audio/a2dp.h b/audio/a2dp.h new file mode 100644 index 000000000..f08c64377 --- /dev/null +++ b/audio/a2dp.h @@ -0,0 +1,149 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x03 + +#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 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 MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct sbc_codec_cap { + struct avdtp_media_codec_capability cap; + 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)); + +struct mpeg_codec_cap { + struct avdtp_media_codec_capability cap; + 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; + uint16_t bitrate; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct sbc_codec_cap { + struct avdtp_media_codec_capability cap; + 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)); + +struct mpeg_codec_cap { + struct avdtp_media_codec_capability cap; + 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; + uint16_t bitrate; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +struct a2dp_sep; + +typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data); +typedef void (*a2dp_stream_cb_t) (struct avdtp *session, + struct avdtp_error *err, + void *user_data); + +int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config); +void a2dp_unregister(const bdaddr_t *src); + +struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep); +unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, + a2dp_config_cb_t cb, GSList *caps, + void *user_data); +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(struct audio_device *dev, 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); +gboolean a2dp_sep_get_lock(struct a2dp_sep *sep); +struct a2dp_sep *a2dp_get_sep(struct avdtp *session, + struct avdtp_stream *stream); diff --git a/audio/audio.conf b/audio/audio.conf new file mode 100644 index 000000000..a093ffbd6 --- /dev/null +++ b/audio/audio.conf @@ -0,0 +1,43 @@ +# Configuration file for the audio service + +# This section contains options which are not specific to any +# particular interface +[General] +Enable=Source,Control,Sink +Disable=Headset,Gateway + +# Switch to master role for incoming connections (defaults to true) +#Master=true + +# If we want to disable support for specific services +# Defaults to supporting all implemented services +#Disable=Control,Source + +# SCO routing. Either PCM or HCI (in which case audio is routed to/from ALSA) +# Defaults to HCI +#SCORouting=PCM + +# Automatically connect both A2DP and HFP/HSP profiles for incoming +# connections. Some headsets that support both profiles will only connect the +# other one automatically so the default setting of true is usually a good +# idea. +#AutoConnect=true + +# Headset interface specific options (i.e. options which affect how the audio +# service interacts with remote headset devices) +#[Headset] + +# Set to true to support HFP (in addition to HSP only which is the default) +# Defaults to false +#HFP=true + +# Maximum number of connected HSP/HFP devices per adapter. Defaults to 1 +#MaxConnections=1 + +# Just an example of potential config options for the other interfaces +[A2DP] +SBCSources=1 +MPEG12Sources=0 + +[AVRCP] +InputDeviceName=AVRCP diff --git a/audio/avdtp.c b/audio/avdtp.c new file mode 100644 index 000000000..939db100f --- /dev/null +++ b/audio/avdtp.c @@ -0,0 +1,3510 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "logging.h" + +#include "../src/adapter.h" +#include "../src/device.h" + +#include "device.h" +#include "manager.h" +#include "control.h" +#include "avdtp.h" +#include "glib-helper.h" +#include "btio.h" +#include "sink.h" +#include "source.h" + +#include + +#define AVDTP_PSM 25 + +#define MAX_SEID 0x3E + +#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_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_ACCEPT 0x02 +#define AVDTP_MSG_TYPE_REJECT 0x03 + +#define REQ_TIMEOUT 4 +#define DISCONNECT_TIMEOUT 1 +#define STREAM_TIMEOUT 20 + +#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 { + struct seid first_seid; + struct seid other_seids[0]; +} __attribute__ ((packed)); + +struct suspend_req { + struct seid first_seid; + struct seid other_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)); + +#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)); + +#else +#error "Unknown byte order" +#endif + +struct in_buf { + gboolean active; + int no_of_packets; + uint8_t transaction; + uint8_t message_type; + 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 */ + guint timeout; +}; + +struct avdtp_remote_sep { + uint8_t seid; + uint8_t type; + uint8_t media_type; + struct avdtp_service_capability *codec; + GSList *caps; /* of type struct avdtp_service_capability */ + struct avdtp_stream *stream; +}; + +struct avdtp_server { + bdaddr_t src; + GIOChannel *io; + GSList *seps; + GSList *sessions; +}; + +struct avdtp_local_sep { + avdtp_state_t state; + struct avdtp_stream *stream; + struct seid_info info; + uint8_t codec; + GSList *caps; + struct avdtp_sep_ind *ind; + struct avdtp_sep_cfm *cfm; + void *user_data; + struct avdtp_server *server; +}; + +struct stream_callback { + avdtp_stream_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct avdtp_state_callback { + avdtp_session_state_cb cb; + void *user_data; + unsigned int id; +}; + +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 */ + guint 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 */ + guint idle_timer; +}; + +/* Structure describing an AVDTP connection between two devices */ + +struct avdtp { + int ref; + int free_lock; + + struct avdtp_server *server; + bdaddr_t dst; + + avdtp_session_state_t state; + + /* True if the session should be automatically disconnected */ + gboolean auto_dc; + + 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; + + uint16_t imtu; + uint16_t omtu; + + struct in_buf in; + + char *buf; + + avdtp_discover_cb_t discov_cb; + void *user_data; + + struct pending_req *req; + + guint dc_timer; + + /* Attempt stream setup instead of disconnecting */ + gboolean stream_setup; + + DBusPendingCall *pending_auth; +}; + +static GSList *servers = NULL; + +static GSList *avdtp_callbacks = NULL; + +static gboolean auto_connect = TRUE; + +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 connection_lost(struct avdtp *session, int err); +static void avdtp_sep_set_state(struct avdtp *session, + struct avdtp_local_sep *sep, + avdtp_state_t state); + +static struct avdtp_server *find_server(GSList *list, const bdaddr_t *src) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct avdtp_server *server = l->data; + + if (bacmp(&server->src, src) == 0) + return server; + } + + return NULL; +} + +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)); + memcpy(session->buf + sizeof(single), data, len); + + return try_send(sock, session->buf, sizeof(single) + len); + } + + /* Count the number of needed fragments */ + cont_fragments = (len - (session->omtu - sizeof(start))) / + (session->omtu - sizeof(cont)) + 1; + + debug("avdtp_send: %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; + + debug("avdtp_send: 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); + debug("avdtp_send: sending continue with %d bytes", + to_copy); + } else { + cont.packet_type = AVDTP_PKT_TYPE_END; + to_copy = left; + debug("avdtp_send: 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(struct pending_req *req) +{ + if (req->timeout) + g_source_remove(req->timeout); + g_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 gboolean stream_close_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + + debug("Timed out waiting for peer to close the transport channel"); + + stream->timer = 0; + + close_stream(stream); + + return FALSE; +} + +static gboolean stream_open_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + + debug("Timed out waiting for peer to open the transport channel"); + + stream->timer = 0; + + stream->session->pending_open = NULL; + + avdtp_abort(stream->session, stream); + + return FALSE; +} + +static gboolean disconnect_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + struct audio_device *dev; + gboolean stream_setup; + + session->dc_timer = 0; + stream_setup = session->stream_setup; + session->stream_setup = FALSE; + + dev = manager_get_device(&session->server->src, &session->dst, FALSE); + + if (dev && dev->sink && stream_setup) + sink_setup_stream(dev->sink, session); + else if (dev && dev->source && stream_setup) + source_setup_stream(dev->source, session); + else + connection_lost(session, ETIMEDOUT); + + return FALSE; +} + +static void remove_disconnect_timer(struct avdtp *session) +{ + g_source_remove(session->dc_timer); + session->dc_timer = 0; + session->stream_setup = FALSE; +} + +static void set_disconnect_timer(struct avdtp *session) +{ + if (session->dc_timer) + remove_disconnect_timer(session); + + session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT, + disconnect_timeout, + session); +} + +void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id) +{ + err->type = type; + switch (type) { + case AVDTP_ERROR_ERRNO: + err->err.posix_errno = id; + break; + case AVDTP_ERROR_ERROR_CODE: + err->err.error_code = id; + break; + } +} + +avdtp_error_type_t avdtp_error_type(struct avdtp_error *err) +{ + return err->type; +} + +int avdtp_error_error_code(struct avdtp_error *err) +{ + assert(err->type == AVDTP_ERROR_ERROR_CODE); + return err->err.error_code; +} + +int avdtp_error_posix_errno(struct avdtp_error *err) +{ + assert(err->type == AVDTP_ERROR_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; + struct audio_device *dev; + bdaddr_t src, dst; + avdtp_session_state_t old_state = session->state; + + session->state = new_state; + + avdtp_get_peers(session, &src, &dst); + dev = manager_get_device(&src, &dst, FALSE); + if (dev == NULL) { + error("avdtp_set_state(): no matching audio device"); + return; + } + + for (l = avdtp_callbacks; l != NULL; l = l->next) { + struct avdtp_state_callback *cb = l->data; + cb->cb(dev, session, old_state, new_state, cb->user_data); + } +} + +static void stream_free(struct avdtp_stream *stream) +{ + struct avdtp_remote_sep *rsep; + + stream->lsep->info.inuse = 0; + stream->lsep->stream = NULL; + + rsep = find_remote_sep(stream->session->seps, stream->rseid); + if (rsep) + rsep->stream = NULL; + + if (stream->timer) + g_source_remove(stream->timer); + + if (stream->io) + g_io_channel_unref(stream->io); + + if (stream->io_id) + g_source_remove(stream->io_id); + + g_slist_foreach(stream->callbacks, (GFunc) g_free, NULL); + g_slist_free(stream->callbacks); + + g_slist_foreach(stream->caps, (GFunc) g_free, NULL); + g_slist_free(stream->caps); + + g_free(stream); +} + +static gboolean stream_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + struct avdtp *session = stream->session; + + avdtp_close(session, stream); + + stream->idle_timer = 0; + + return FALSE; +} + +static gboolean transport_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp_stream *stream = data; + struct avdtp_local_sep *sep = stream->lsep; + + 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_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE); + + return FALSE; +} + +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; + + session->pending_open = NULL; + + if (stream->timer) { + g_source_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_ERROR_ERRNO, EIO); + sep->cfm->open(session, sep, NULL, &err, + sep->user_data); + } + return; + } + + stream->io = g_io_channel_ref(io); + stream->omtu = omtu; + stream->imtu = imtu; + + if (!stream->open_acp && sep->cfm && sep->cfm->open) + sep->cfm->open(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + stream->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) transport_cb, stream); +} + +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 avdtp_sep_set_state(struct avdtp *session, + struct avdtp_local_sep *sep, + avdtp_state_t state) +{ + struct avdtp_stream *stream = sep->stream; + 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; + } + + if (sep->state == state) { + avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO); + debug("stream state change failed: %s", avdtp_strerror(&err)); + err_ptr = &err; + } else { + err_ptr = NULL; + debug("stream state changed: %s -> %s", + avdtp_statestr(sep->state), + avdtp_statestr(state)); + } + + old_state = sep->state; + sep->state = state; + + for (l = stream->callbacks; l != NULL; l = g_slist_next(l)) { + struct stream_callback *cb = l->data; + cb->cb(stream, old_state, state, err_ptr, cb->user_data); + } + + switch (state) { + case AVDTP_STATE_OPEN: + if (old_state > AVDTP_STATE_OPEN && session->auto_dc) + stream->idle_timer = g_timeout_add_seconds(STREAM_TIMEOUT, + stream_timeout, + stream); + break; + case AVDTP_STATE_STREAMING: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + if (stream->idle_timer) { + g_source_remove(stream->idle_timer); + stream->idle_timer = 0; + } + break; + case AVDTP_STATE_IDLE: + if (stream->idle_timer) { + g_source_remove(stream->idle_timer); + stream->idle_timer = 0; + } + session->streams = g_slist_remove(session->streams, stream); + if (session->pending_open == stream) + handle_transport_connect(session, NULL, 0, 0); + if (session->req && session->req->stream == stream) + session->req->stream = NULL; + /* Remove pending commands for this stream from the queue */ + cleanup_queue(session, stream); + stream_free(stream); + if (session->ref == 1 && !session->streams) + set_disconnect_timer(session); + break; + default: + break; + } +} + +static void finalize_discovery(struct avdtp *session, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, err); + + if (!session->discov_cb) + return; + + session->discov_cb(session, session->seps, + err ? &avdtp_err : NULL, + session->user_data); + + session->discov_cb = NULL; + session->user_data = 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 && + (sep->state != AVDTP_STATE_ABORTING || + stream->abort_int)) + sep->cfm->abort(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); +} + +static void connection_lost(struct avdtp *session, int err) +{ + char address[18]; + + ba2str(&session->dst, address); + debug("Disconnected from %s", address); + + if (session->state == AVDTP_SESSION_STATE_CONNECTING && err != EACCES) + btd_cancel_authorization(&session->server->src, &session->dst); + + session->free_lock = 1; + + finalize_discovery(session, err); + + g_slist_foreach(session->streams, (GFunc) release_stream, session); + session->streams = NULL; + + session->free_lock = 0; + + if (session->io) { + g_io_channel_shutdown(session->io, FALSE, NULL); + g_io_channel_unref(session->io); + session->io = NULL; + } + + avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED); + + if (session->io_id) { + g_source_remove(session->io_id); + session->io_id = 0; + } + + if (session->dc_timer) + remove_disconnect_timer(session); + + session->auto_dc = TRUE; + + if (session->ref != 1) + error("connection_lost: ref count not 1 after all callbacks"); + else + avdtp_unref(session); +} + +void avdtp_unref(struct avdtp *session) +{ + struct avdtp_server *server; + + if (!session) + return; + + session->ref--; + + debug("avdtp_unref(%p): ref=%d", session, session->ref); + + if (session->ref == 1) { + if (session->state == AVDTP_SESSION_STATE_CONNECTING && + session->io) { + btd_cancel_authorization(&session->server->src, + &session->dst); + g_io_channel_shutdown(session->io, TRUE, NULL); + g_io_channel_unref(session->io); + session->io = NULL; + } + + if (session->io) + set_disconnect_timer(session); + else if (!session->free_lock) /* Drop the local ref if we + aren't connected */ + session->ref--; + } + + if (session->ref > 0) + return; + + server = session->server; + + debug("avdtp_unref(%p): freeing session and removing from list", + session); + + if (session->dc_timer) + remove_disconnect_timer(session); + + server->sessions = g_slist_remove(server->sessions, session); + + if (session->req) + pending_req_free(session->req); + + g_slist_foreach(session->seps, (GFunc) g_free, NULL); + g_slist_free(session->seps); + + g_free(session->buf); + + g_free(session); +} + +struct avdtp *avdtp_ref(struct avdtp *session) +{ + session->ref++; + debug("avdtp_ref(%p): ref=%d", session, session->ref); + if (session->dc_timer) + remove_disconnect_timer(session); + return session; +} + +static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp_server *server, + uint8_t seid) +{ + GSList *l; + + for (l = server->seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_local_sep *sep = l->data; + + if (sep->info.seid == seid) + return sep; + } + + return NULL; +} + +static struct avdtp_local_sep *find_local_sep(struct avdtp_server *server, + uint8_t type, + uint8_t media_type, + uint8_t codec) +{ + GSList *l; + + for (l = server->seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_local_sep *sep = l->data; + + if (sep->info.inuse) + continue; + + if (sep->info.type == type && + sep->info.media_type == media_type && + sep->codec == codec) + return sep; + } + + return NULL; +} + +static GSList *caps_to_list(uint8_t *data, int size, + struct avdtp_service_capability **codec) +{ + GSList *caps; + int processed; + + for (processed = 0, caps = NULL; processed + 2 < size;) { + struct avdtp_service_capability *cap; + uint8_t length, category; + + category = data[0]; + length = data[1]; + + if (processed + 2 + length > size) { + error("Invalid capability data in getcap resp"); + break; + } + + cap = g_malloc(sizeof(struct avdtp_service_capability) + + length); + memcpy(cap, data, 2 + length); + + processed += 2 + length; + data += 2 + length; + + caps = g_slist_append(caps, cap); + + if (category == AVDTP_MEDIA_CODEC && + length >= + sizeof(struct avdtp_media_codec_capability)) + *codec = cap; + } + + return caps; +} + +static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction, + void *buf, int size) +{ + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + 0, NULL, 0); +} + +static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction, + void *buf, int size) +{ + GSList *l; + unsigned int rsp_size, sep_count, i; + struct seid_info *seps; + gboolean ret; + + sep_count = g_slist_length(session->server->seps); + + 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); + + for (l = session->server->seps, i = 0; l != NULL; l = l->next, i++) { + struct avdtp_local_sep *sep = l->data; + + memcpy(&seps[i], &sep->info, sizeof(struct seid_info)); + } + + 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) +{ + GSList *l, *caps; + struct avdtp_local_sep *sep = NULL; + unsigned int rsp_size; + uint8_t err, buf[1024], *ptr = buf; + + if (size < sizeof(struct seid_req)) { + err = AVDTP_BAD_LENGTH; + goto failed; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (!sep->ind->get_capability(session, sep, &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, + AVDTP_GET_CAPABILITIES, buf, rsp_size); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_GET_CAPABILITIES, &err, sizeof(err)); +} + +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, category = 0x00; + struct audio_device *dev; + bdaddr_t src, dst; + GSList *l; + + if (size < sizeof(struct setconf_req)) { + error("Too short getcap request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->stream) { + err = AVDTP_SEP_IN_USE; + goto failed; + } + + avdtp_get_peers(session, &src, &dst); + dev = manager_get_device(&src, &dst, FALSE); + if (!dev) { + error("Unable to get a audio device object"); + goto failed; + } + + switch (sep->info.type) { + case AVDTP_SEP_TYPE_SOURCE: + if (!dev->sink) { + btd_device_add_uuid(dev->btd_dev, A2DP_SINK_UUID); + if (!dev->sink) { + error("Unable to get a audio sink object"); + goto failed; + } + } + break; + case AVDTP_SEP_TYPE_SINK: + /* Do source_init() here when it's implemented */ + break; + } + + stream = g_new0(struct avdtp_stream, 1); + stream->session = session; + stream->lsep = sep; + stream->rseid = req->int_seid; + stream->caps = caps_to_list(req->caps, + size - sizeof(struct setconf_req), + &stream->codec); + + /* Verify that the Media Transport capability's length = 0. Reject otherwise */ + for (l = stream->caps; l != NULL; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (cap->category == AVDTP_MEDIA_TRANSPORT && cap->length != 0) { + err = AVDTP_BAD_MEDIA_TRANSPORT_FORMAT; + goto failed_stream; + } + } + + if (sep->ind && sep->ind->set_configuration) { + if (!sep->ind->set_configuration(session, sep, stream, + stream->caps, &err, + &category, + sep->user_data)) + goto failed_stream; + } + + if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_SET_CONFIGURATION, NULL, 0)) { + stream_free(stream); + return FALSE; + } + + sep->stream = stream; + session->streams = g_slist_append(session->streams, stream); + + avdtp_sep_set_state(session, sep, 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_local_sep *sep = NULL; + 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)); + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + if (!sep->stream || !sep->stream->caps) { + err = AVDTP_UNSUPPORTED_CONFIGURATION; + goto failed; + } + + for (l = sep->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) +{ + return avdtp_unknown_cmd(session, transaction, (void *) req, size); +} + +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->server, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->state != AVDTP_STATE_CONFIGURED) { + err = AVDTP_BAD_STATE; + goto failed; + } + + stream = sep->stream; + + if (sep->ind && sep->ind->open) { + if (!sep->ind->open(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_OPEN, NULL, 0)) + return FALSE; + + stream->open_acp = TRUE; + session->pending_open = stream; + stream->timer = g_timeout_add_seconds(REQ_TIMEOUT, + stream_open_timeout, + stream); + + 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; + struct seid *seid; + 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); + + seid = &req->first_seid; + + for (i = 0; i < seid_count; i++, seid++) { + failed_seid = seid->seid; + + sep = find_local_sep_by_seid(session->server, + req->first_seid.seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + stream = sep->stream; + + if (sep->state != AVDTP_STATE_OPEN) { + err = AVDTP_BAD_STATE; + goto failed; + } + + if (sep->ind && sep->ind->start) { + if (!sep->ind->start(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_START, 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_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; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->state != AVDTP_STATE_OPEN && + sep->state != AVDTP_STATE_STREAMING) { + err = AVDTP_BAD_STATE; + goto failed; + } + + stream = sep->stream; + + if (sep->ind && sep->ind->close) { + if (!sep->ind->close(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); + + if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_CLOSE, NULL, 0)) + return FALSE; + + stream->timer = g_timeout_add_seconds(REQ_TIMEOUT, + stream_close_timeout, + stream); + + 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; + struct seid *seid; + 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); + + seid = &req->first_seid; + + for (i = 0; i < seid_count; i++, seid++) { + failed_seid = seid->seid; + + sep = find_local_sep_by_seid(session->server, + req->first_seid.seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + stream = sep->stream; + + if (sep->state != AVDTP_STATE_STREAMING) { + err = AVDTP_BAD_STATE; + goto failed; + } + + if (sep->ind && sep->ind->suspend) { + if (!sep->ind->suspend(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + avdtp_sep_set_state(session, sep, 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; + uint8_t err; + gboolean ret; + + if (size < sizeof(struct seid_req)) { + error("Too short abort request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session->server, req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->ind && sep->ind->abort) { + if (!sep->ind->abort(session, sep, sep->stream, &err, + sep->user_data)) + goto failed; + } + + ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_ABORT, NULL, 0); + if (ret) + avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING); + + return ret; + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_ABORT, &err, sizeof(err)); +} + +static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + return avdtp_unknown_cmd(session, transaction, (void *) req, size); +} + +static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction, + uint8_t signal_id, void *buf, int size) +{ + switch (signal_id) { + case AVDTP_DISCOVER: + debug("Received DISCOVER_CMD"); + return avdtp_discover_cmd(session, transaction, buf, size); + case AVDTP_GET_CAPABILITIES: + debug("Received GET_CAPABILITIES_CMD"); + return avdtp_getcap_cmd(session, transaction, buf, size); + case AVDTP_SET_CONFIGURATION: + debug("Received SET_CONFIGURATION_CMD"); + return avdtp_setconf_cmd(session, transaction, buf, size); + case AVDTP_GET_CONFIGURATION: + debug("Received GET_CONFIGURATION_CMD"); + return avdtp_getconf_cmd(session, transaction, buf, size); + case AVDTP_RECONFIGURE: + debug("Received RECONFIGURE_CMD"); + return avdtp_reconf_cmd(session, transaction, buf, size); + case AVDTP_OPEN: + debug("Received OPEN_CMD"); + return avdtp_open_cmd(session, transaction, buf, size); + case AVDTP_START: + debug("Received START_CMD"); + return avdtp_start_cmd(session, transaction, buf, size); + case AVDTP_CLOSE: + debug("Received CLOSE_CMD"); + return avdtp_close_cmd(session, transaction, buf, size); + case AVDTP_SUSPEND: + debug("Received SUSPEND_CMD"); + return avdtp_suspend_cmd(session, transaction, buf, size); + case AVDTP_ABORT: + debug("Received ABORT_CMD"); + return avdtp_abort_cmd(session, transaction, buf, size); + case AVDTP_SECURITY_CONTROL: + debug("Received SECURITY_CONTROL_CMD"); + return avdtp_secctl_cmd(session, transaction, buf, size); + default: + debug("Received unknown request id %u", signal_id); + return avdtp_unknown_cmd(session, transaction, buf, size); + } +} + +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; + + 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 (session->in.active) { + error("SINGLE: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(*single); + payload_size = size - sizeof(*single); + + session->in.active = TRUE; + session->in.data_size = 0; + session->in.no_of_packets = 1; + session->in.transaction = header->transaction; + session->in.message_type = header->message_type; + session->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 (session->in.active) { + error("START: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + + session->in.active = TRUE; + session->in.data_size = 0; + session->in.transaction = header->transaction; + session->in.message_type = header->message_type; + session->in.no_of_packets = start->no_of_packets; + session->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 (!session->in.active) { + error("CONTINUE: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + if (session->in.transaction != header->transaction) { + error("Continue transaction id doesn't match"); + return PARSE_ERROR; + } + if (session->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 (!session->in.active) { + error("END: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + if (session->in.transaction != header->transaction) { + error("End transaction id doesn't match"); + return PARSE_ERROR; + } + if (session->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 (session->in.data_size + payload_size > + sizeof(session->in.buf)) { + error("Not enough incoming buffer space!"); + return PARSE_ERROR; + } + + memcpy(session->in.buf + session->in.data_size, payload, payload_size); + session->in.data_size += payload_size; + + if (session->in.no_of_packets > 1) { + session->in.no_of_packets--; + debug("Received AVDTP fragment. %d to go", + session->in.no_of_packets); + return PARSE_FRAGMENT; + } + + session->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; + gsize size; + + debug("session_cb"); + + if (cond & G_IO_NVAL) + return FALSE; + + header = (void *) session->buf; + + if (cond & (G_IO_HUP | G_IO_ERR)) + goto failed; + + if (g_io_channel_read(chan, session->buf, session->imtu, &size) + != G_IO_ERROR_NONE) { + error("IO Channel read error"); + goto failed; + } + + if (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 (session->in.message_type == AVDTP_MSG_TYPE_COMMAND) { + if (!avdtp_parse_cmd(session, session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to handle command. Disconnecting"); + goto failed; + } + + if (session->ref == 1 && !session->streams && !session->req) + set_disconnect_timer(session); + + if (session->streams && session->dc_timer) + remove_disconnect_timer(session); + + 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.signal_id != session->req->signal_id) { + error("Reponse signal doesn't match"); + return TRUE; + } + + g_source_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.transaction, + session->in.signal_id, + session->in.buf, + session->in.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.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to parse reject response"); + goto failed; + } + break; + default: + error("Unknown message type 0x%02X", header->message_type); + break; + } + + pending_req_free(session->req); + session->req = NULL; + + process_queue(session); + + return TRUE; + +failed: + connection_lost(session, EIO); + + return FALSE; +} + +static struct avdtp *find_session(GSList *list, const bdaddr_t *dst) +{ + GSList *l; + + for (l = list; l != NULL; l = g_slist_next(l)) { + struct avdtp *s = l->data; + + if (bacmp(dst, &s->dst)) + continue; + + return s; + } + + return NULL; +} + +static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct avdtp_server *server; + struct avdtp *session; + + assert(src != NULL); + assert(dst != NULL); + + server = find_server(servers, src); + if (server == NULL) + return NULL; + + session = find_session(server->sessions, dst); + if (session) { + if (session->pending_auth) + return NULL; + else + return session; + } + + session = g_new0(struct avdtp, 1); + + session->server = server; + bacpy(&session->dst, dst); + session->ref = 1; + /* 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->auto_dc = TRUE; + + server->sessions = g_slist_append(server->sessions, session); + + return session; +} + +struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst) +{ + struct avdtp *session; + + session = avdtp_get_internal(src, dst); + + if (!session) + return NULL; + + return avdtp_ref(session); +} + +static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct avdtp *session = user_data; + char address[18]; + GError *gerr = NULL; + + if (err) { + error("%s", err->message); + goto failed; + } + + if (!session->io) + session->io = g_io_channel_ref(chan); + + bt_io_get(chan, BT_IO_L2CAP, &gerr, + BT_IO_OPT_OMTU, &session->omtu, + BT_IO_OPT_IMTU, &session->imtu, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + goto failed; + } + + ba2str(&session->dst, address); + debug("AVDTP: connected %s channel to %s", + session->pending_open ? "transport" : "signaling", + address); + + if (session->state == AVDTP_SESSION_STATE_CONNECTING) { + debug("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu); + + session->buf = g_malloc0(session->imtu); + avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED); + + if (session->io_id) + g_source_remove(session->io_id); + + /* This watch should be low priority since otherwise the + * connect callback might be dispatched before the session + * callback if the kernel wakes us up at the same time for + * them. This could happen if a headset is very quick in + * sending the Start command after connecting the stream + * transport channel. + */ + session->io_id = g_io_add_watch_full(chan, + G_PRIORITY_LOW, + G_IO_IN | G_IO_ERR | G_IO_HUP + | G_IO_NVAL, + (GIOFunc) session_cb, session, + NULL); + + if (session->stream_setup) { + set_disconnect_timer(session); + avdtp_set_auto_disconnect(session, FALSE); + } + } 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_sep_set_state(session, stream->lsep, + AVDTP_STATE_IDLE); + } else + connection_lost(session, EIO); + + return; +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct avdtp *session = user_data; + GError *err = NULL; + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + connection_lost(session, EACCES); + return; + } + + if (!bt_io_accept(session->io, avdtp_connect_cb, session, NULL, + &err)) { + error("bt_io_accept: %s", err->message); + connection_lost(session, EACCES); + g_error_free(err); + return; + } + + /* This is so that avdtp_connect_cb will know to do the right thing + * with respect to the disconnect timer */ + session->stream_setup = TRUE; +} + +static void avdtp_confirm_cb(GIOChannel *chan, gpointer data) +{ + struct avdtp *session; + struct audio_device *dev; + char address[18]; + bdaddr_t src, dst; + int perr; + GError *err = NULL; + + bt_io_get(chan, BT_IO_L2CAP, &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; + } + + debug("AVDTP: incoming connect from %s", address); + + session = avdtp_get_internal(&src, &dst); + if (!session) + goto drop; + + /* This state (ie, session is already *connecting*) happens when the + * device initiates a connect (really a config'd L2CAP channel) even + * though there is a connect we initiated in progress. In sink.c & + * source.c, this state is referred to as XCASE connect:connect. + * Abort the device's channel in favor of our own. + */ + if (session->state == AVDTP_SESSION_STATE_CONNECTING) { + debug("avdtp_confirm_cb: connect already in progress" + " (XCASE connect:connect)"); + goto drop; + } + + if (session->pending_open && session->pending_open->open_acp) { + if (!bt_io_accept(chan, avdtp_connect_cb, session, NULL, NULL)) + goto drop; + return; + } + + if (session->io) { + error("Refusing unexpected connect from %s", address); + goto drop; + } + + dev = manager_get_device(&src, &dst, FALSE); + if (!dev) { + dev = manager_get_device(&src, &dst, TRUE); + if (!dev) { + error("Unable to get audio device object for %s", + address); + goto drop; + } + btd_device_add_uuid(dev->btd_dev, ADVANCED_AUDIO_UUID); + } + + session->io = g_io_channel_ref(chan); + avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING); + + session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) session_cb, session); + + perr = audio_device_request_authorization(dev, ADVANCED_AUDIO_UUID, + auth_cb, session); + if (perr < 0) { + avdtp_unref(session); + goto drop; + } + + dev->auto_connect = auto_connect; + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static int l2cap_connect(struct avdtp *session) +{ + GError *err = NULL; + GIOChannel *io; + + io = bt_io_connect(BT_IO_L2CAP, avdtp_connect_cb, session, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &session->server->src, + BT_IO_OPT_DEST_BDADDR, &session->dst, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return -EIO; + } + + g_io_channel_unref(io); + + return 0; +} + +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 gboolean request_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + struct pending_req *req; + struct seid_req sreq; + struct avdtp_local_sep *lsep; + struct avdtp_stream *stream; + uint8_t seid; + struct avdtp_error err; + + req = session->req; + session->req = NULL; + + avdtp_error_init(&err, AVDTP_ERROR_ERRNO, ETIMEDOUT); + + 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 request timed out"); + if (lsep && lsep->cfm && lsep->cfm->reconfigure) + lsep->cfm->reconfigure(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_OPEN: + error("Open request timed out"); + if (lsep && lsep->cfm && lsep->cfm->open) + lsep->cfm->open(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_START: + error("Start request timed out"); + if (lsep && lsep->cfm && lsep->cfm->start) + lsep->cfm->start(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_SUSPEND: + error("Suspend request timed out"); + if (lsep && lsep->cfm && lsep->cfm->suspend) + lsep->cfm->suspend(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_CLOSE: + error("Close request timed out"); + if (lsep && lsep->cfm && lsep->cfm->close) { + lsep->cfm->close(session, lsep, stream, &err, + lsep->user_data); + if (stream) + stream->close_int = FALSE; + } + break; + case AVDTP_SET_CONFIGURATION: + error("SetConfiguration request timed out"); + if (lsep && lsep->cfm && lsep->cfm->set_configuration) + lsep->cfm->set_configuration(session, lsep, stream, + &err, lsep->user_data); + goto failed; + case AVDTP_DISCOVER: + error("Discover request timed out"); + goto failed; + case AVDTP_GET_CAPABILITIES: + error("GetCapabilities request timed out"); + goto failed; + case AVDTP_ABORT: + error("Abort request timed out"); + goto failed; + } + + if (!stream) + goto failed; + + memset(&sreq, 0, sizeof(sreq)); + sreq.acp_seid = seid; + + if (send_request(session, TRUE, stream, AVDTP_ABORT, + &sreq, sizeof(sreq)) < 0) { + error("Unable to send abort request"); + goto failed; + } + + stream->abort_int = TRUE; + + goto done; + +failed: + connection_lost(session, ETIMEDOUT); +done: + pending_req_free(req); + return FALSE; +} + +static int send_req(struct avdtp *session, gboolean priority, + struct pending_req *req) +{ + static int transaction = 0; + int err; + + if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) { + err = l2cap_connect(session); + if (err < 0) + 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; + + req->timeout = g_timeout_add_seconds(REQ_TIMEOUT, + request_timeout, + session); + return 0; + +failed: + g_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; + + req = g_new0(struct pending_req, 1); + req->signal_id = signal_id; + req->data = g_malloc(size); + memcpy(req->data, 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; + + 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; + int ret; + + debug("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) { + 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; + + memset(&req, 0, sizeof(req)); + req.acp_seid = sep->seid; + + ret = send_request(session, TRUE, NULL, + AVDTP_GET_CAPABILITIES, + &req, sizeof(req)); + if (ret < 0) { + finalize_discovery(session, -ret); + break; + } + } + + 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); + + debug("seid %d type %d media %d", sep->seid, + sep->type, sep->media_type); + + if (sep->caps) { + g_slist_foreach(sep->caps, (GFunc) g_free, NULL); + g_slist_free(sep->caps); + sep->caps = NULL; + sep->codec = NULL; + } + + sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp), + &sep->codec); + + 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_sep_set_state(session, sep, 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) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (l2cap_connect(session) < 0) { + avdtp_sep_set_state(session, sep, 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 (sep->state != AVDTP_STATE_STREAMING) + avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); + + return TRUE; +} + +static gboolean avdtp_close_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); + + close_stream(stream); + + 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_sep_set_state(session, sep, 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_sep_set_state(session, sep, AVDTP_STATE_ABORTING); + + if (sep->cfm && sep->cfm->abort) + sep->cfm->abort(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); + + 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; + + 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: + debug("DISCOVER request succeeded"); + return avdtp_discover_resp(session, buf, size); + case AVDTP_GET_CAPABILITIES: + debug("GET_CAPABILITIES request succeeded"); + if (!avdtp_get_capabilities_resp(session, buf, size)) + return FALSE; + if (!(next && next->signal_id == AVDTP_GET_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) { + debug("AVDTP: stream was closed while waiting for reply"); + return TRUE; + } + + switch (signal_id) { + case AVDTP_SET_CONFIGURATION: + debug("SET_CONFIGURATION request succeeded"); + return avdtp_set_configuration_resp(session, stream, + buf, size); + case AVDTP_RECONFIGURE: + debug("RECONFIGURE request succeeded"); + return avdtp_reconfigure_resp(session, stream, buf, size); + case AVDTP_OPEN: + debug("OPEN request succeeded"); + return avdtp_open_resp(session, stream, buf, size); + case AVDTP_SUSPEND: + debug("SUSPEND request succeeded"); + return avdtp_suspend_resp(session, stream, buf, size); + case AVDTP_START: + debug("START request succeeded"); + return avdtp_start_resp(session, stream, buf, size); + case AVDTP_CLOSE: + debug("CLOSE request succeeded"); + return avdtp_close_resp(session, stream, buf, size); + case AVDTP_ABORT: + debug("ABORT request succeeded"); + return avdtp_abort_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, AVDTP_ERROR_ERROR_CODE, rej->error); + + return TRUE; +} + +static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size, + struct avdtp_error *err, uint8_t *category) +{ + if (size < sizeof(struct conf_rej)) { + error("Too small packet for conf_rej"); + return FALSE; + } + + avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error); + + if (category) + *category = rej->category; + + 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, AVDTP_ERROR_ERROR_CODE, 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, category; + 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: + 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, &category)) + 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, &category)) + 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); + 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 TRUE; + default: + error("Unknown reject response signal id: %u", signal_id); + return TRUE; + } +} + +gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct avdtp_server *server; + struct avdtp *session; + + server = find_server(servers, src); + if (!server) + return FALSE; + + session = find_session(server->sessions, dst); + if (!session) + return FALSE; + + if (session->state != AVDTP_SESSION_STATE_DISCONNECTED) + return TRUE; + + return FALSE; +} + +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; +} + +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) +{ + GSList *l; + + for (l = caps; l; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (!avdtp_stream_has_capability(stream, cap)) + return FALSE; + } + + return TRUE; +} + +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); +} + +struct avdtp_remote_sep *avdtp_get_remote_sep(struct avdtp *session, + uint8_t seid) +{ + GSList *l; + + for (l = session->seps; l; l = l->next) { + struct avdtp_remote_sep *sep = l->data; + + if (sep->seid == seid) + return sep; + } + + return NULL; +} + +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; +} + +struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep) +{ + return sep->stream; +} + +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_MEDIA_CODEC) + return NULL; + + cap = g_malloc(sizeof(struct avdtp_service_capability) + length); + cap->category = category; + cap->length = length; + memcpy(cap->data, data, length); + + return cap; +} + +static gboolean process_discover(gpointer data) +{ + struct avdtp *session = data; + + finalize_discovery(session, 0); + + return FALSE; +} + +int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, + void *user_data) +{ + int ret; + + if (session->discov_cb) + return -EBUSY; + + if (session->seps) { + session->discov_cb = cb; + session->user_data = user_data; + g_idle_add(process_discover, session); + return 0; + } + + ret = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0); + if (ret == 0) { + session->discov_cb = cb; + session->user_data = user_data; + } + + return ret; +} + +int avdtp_get_seps(struct avdtp *session, uint8_t acp_type, uint8_t media_type, + uint8_t codec, struct avdtp_local_sep **lsep, + struct avdtp_remote_sep **rsep) +{ + GSList *l; + uint8_t int_type; + + int_type = acp_type == AVDTP_SEP_TYPE_SINK ? + AVDTP_SEP_TYPE_SOURCE : AVDTP_SEP_TYPE_SINK; + + *lsep = find_local_sep(session->server, int_type, media_type, codec); + if (!*lsep) + return -EINVAL; + + 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; + + if (sep->type != acp_type) + continue; + + if (sep->media_type != media_type) + continue; + + if (!sep->codec) + continue; + + cap = sep->codec; + codec_data = (void *) cap->data; + + if (codec_data->media_codec_type != codec) + continue; + + if (!sep->stream) { + *rsep = sep; + return 0; + } + } + + return -EINVAL; +} + +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 ret, caps_len; + struct avdtp_service_capability *cap; + GSList *l; + + if (session->state != AVDTP_SESSION_STATE_CONNECTED) + return -ENOTCONN; + + if (!(lsep && rsep)) + return -EINVAL; + + debug("avdtp_set_configuration(%p): int_seid=%u, acp_seid=%u", + session, lsep->info.seid, rsep->seid); + + new_stream = g_new0(struct avdtp_stream, 1); + new_stream->session = session; + new_stream->lsep = lsep; + new_stream->rseid = rsep->seid; + + 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; + } + + ret = send_request(session, FALSE, new_stream, + AVDTP_SET_CONFIGURATION, req, + sizeof(struct setconf_req) + caps_len); + if (ret < 0) + stream_free(new_stream); + else { + lsep->info.inuse = 1; + lsep->stream = new_stream; + rsep->stream = new_stream; + session->streams = g_slist_append(session->streams, new_stream); + if (stream) + *stream = new_stream; + } + + g_free(req); + + return ret; +} + +int avdtp_reconfigure(struct avdtp *session, GSList *caps, + struct avdtp_stream *stream) +{ + struct reconf_req *req; + unsigned char *ptr; + int caps_len, err; + GSList *l; + struct avdtp_service_capability *cap; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_OPEN) + return -EINVAL; + + /* 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 reconf_req) + caps_len); + + req->acp_seid = stream->rseid; + + /* 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, stream, AVDTP_RECONFIGURE, req, + sizeof(*req) + caps_len); + 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->lsep->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)); +} + +int avdtp_start(struct avdtp *session, struct avdtp_stream *stream) +{ + struct start_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_OPEN) + return -EINVAL; + + if (stream->close_int == TRUE) { + error("avdtp_start: rejecting start since close is initiated"); + return -EINVAL; + } + + memset(&req, 0, sizeof(req)); + req.first_seid.seid = stream->rseid; + + return send_request(session, FALSE, stream, AVDTP_START, + &req, sizeof(req)); +} + +int avdtp_close(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state < AVDTP_STATE_OPEN) + return -EINVAL; + + if (stream->close_int == TRUE) { + error("avdtp_close: rejecting since close is already initiated"); + return -EINVAL; + } + + 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; + + 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->lsep->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 (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state <= AVDTP_STATE_OPEN) + return -EINVAL; + + 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; + + return ret; +} + +struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type, + uint8_t media_type, + uint8_t codec_type, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm, + void *user_data) +{ + struct avdtp_server *server; + struct avdtp_local_sep *sep; + + server = find_server(servers, src); + if (!server) + return NULL; + + if (g_slist_length(server->seps) > MAX_SEID) + return NULL; + + sep = g_new0(struct avdtp_local_sep, 1); + + sep->state = AVDTP_STATE_IDLE; + sep->info.seid = g_slist_length(server->seps) + 1; + 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->server = server; + + debug("SEP %p registered: type:%d codec:%d seid:%d", sep, + sep->info.type, sep->codec, sep->info.seid); + server->seps = g_slist_append(server->seps, sep); + + return sep; +} + +int avdtp_unregister_sep(struct avdtp_local_sep *sep) +{ + struct avdtp_server *server; + + if (!sep) + return -EINVAL; + + server = sep->server; + server->seps = g_slist_remove(server->seps, sep); + + if (sep->stream) + release_stream(sep->stream, sep->stream->session); + + g_free(sep); + + return 0; +} + +static GIOChannel *avdtp_server_socket(const bdaddr_t *src, gboolean master) +{ + GError *err = NULL; + GIOChannel *io; + + io = bt_io_listen(BT_IO_L2CAP, NULL, avdtp_confirm_cb, + NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + } + + return io; +} + +const char *avdtp_strerror(struct avdtp_error *err) +{ + if (err->type == AVDTP_ERROR_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 Lenght"; + 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 Protetion 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 "Unknow error"; + } +} + +avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep) +{ + return sep->state; +} + +void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst) +{ + if (src) + bacpy(src, &session->server->src); + if (dst) + bacpy(dst, &session->dst); +} + +int avdtp_init(const bdaddr_t *src, GKeyFile *config) +{ + GError *err = NULL; + gboolean tmp, master = TRUE; + struct avdtp_server *server; + + if (config) { + tmp = g_key_file_get_boolean(config, "General", + "Master", &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else + master = tmp; + + tmp = g_key_file_get_boolean(config, "General", "AutoConnect", + &err); + if (err) + g_clear_error(&err); + else + auto_connect = tmp; + } + + server = g_new0(struct avdtp_server, 1); + if (!server) + return -ENOMEM; + + server->io = avdtp_server_socket(src, master); + if (!server->io) { + g_free(server); + return -1; + } + + bacpy(&server->src, src); + + servers = g_slist_append(servers, server); + + return 0; +} + +void avdtp_exit(const bdaddr_t *src) +{ + struct avdtp_server *server; + GSList *l; + + server = find_server(servers, src); + if (!server) + return; + + for (l = server->sessions; l; l = l->next) { + struct avdtp *session = l->data; + + connection_lost(session, -ECONNABORTED); + } + + servers = g_slist_remove(servers, server); + + g_io_channel_shutdown(server->io, TRUE, NULL); + g_io_channel_unref(server->io); + g_free(server); +} + +gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream) +{ + return g_slist_find(session->streams, stream) ? TRUE : FALSE; +} + +void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc) +{ + session->auto_dc = auto_dc; +} + +gboolean avdtp_stream_setup_active(struct avdtp *session) +{ + return session->stream_setup; +} + +unsigned int avdtp_add_state_cb(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->user_data = user_data; + state_cb->id = ++id; + + avdtp_callbacks = g_slist_append(avdtp_callbacks, state_cb);; + + return state_cb->id; +} + +gboolean avdtp_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = avdtp_callbacks; l != NULL; l = l->next) { + struct avdtp_state_callback *cb = l->data; + if (cb && cb->id == id) { + avdtp_callbacks = g_slist_remove(avdtp_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/audio/avdtp.h b/audio/avdtp.h new file mode 100644 index 000000000..81b8f58b5 --- /dev/null +++ b/audio/avdtp.h @@ -0,0 +1,298 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +typedef enum { + AVDTP_ERROR_ERRNO, + AVDTP_ERROR_ERROR_CODE +} avdtp_error_type_t; + +typedef enum { + AVDTP_SESSION_STATE_DISCONNECTED, + AVDTP_SESSION_STATE_CONNECTING, + AVDTP_SESSION_STATE_CONNECTED +} avdtp_session_state_t; + +struct avdtp; +struct avdtp_stream; +struct avdtp_local_sep; +struct avdtp_remote_sep; +struct avdtp_error { + avdtp_error_type_t type; + 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 + +/* 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 audio_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); + +/* 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); +}; + +/* 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 (*get_capability) (struct avdtp *session, + struct avdtp_local_sep *sep, + 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, uint8_t *err, + uint8_t *category, 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); + gboolean (*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); +}; + +typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps, + struct avdtp_error *err, void *user_data); + +struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst); + +void avdtp_unref(struct avdtp *session); +struct avdtp *avdtp_ref(struct avdtp *session); + +gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst); + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int size); + +struct avdtp_remote_sep *avdtp_get_remote_sep(struct avdtp *session, + uint8_t seid); + +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); + +struct avdtp_stream *avdtp_get_stream(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_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_capability(struct avdtp_stream *stream, + struct avdtp_service_capability *cap); +gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, + GSList *caps); + +unsigned int avdtp_add_state_cb(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_reconfigure(struct avdtp *session, GSList *caps, + 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); +int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream); + +struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type, + uint8_t media_type, + uint8_t codec_type, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm, + void *user_data); + +/* Find a matching pair of local and remote SEP ID's */ +int avdtp_get_seps(struct avdtp *session, uint8_t type, uint8_t media, + uint8_t codec, struct avdtp_local_sep **lsep, + struct avdtp_remote_sep **rsep); + +int avdtp_unregister_sep(struct avdtp_local_sep *sep); + +avdtp_state_t avdtp_sep_get_state(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); +avdtp_error_type_t avdtp_error_type(struct avdtp_error *err); +int avdtp_error_error_code(struct avdtp_error *err); +int avdtp_error_posix_errno(struct avdtp_error *err); + +void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst); + +void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc); +gboolean avdtp_stream_setup_active(struct avdtp *session); + +int avdtp_init(const bdaddr_t *src, GKeyFile *config); +void avdtp_exit(const bdaddr_t *src); diff --git a/audio/bluetooth.conf b/audio/bluetooth.conf new file mode 100644 index 000000000..55b51e436 --- /dev/null +++ b/audio/bluetooth.conf @@ -0,0 +1,36 @@ +# Please note that this ALSA configuration file fragment needs be enabled in +# /etc/asound.conf or a similar configuration file with directives similar to +# the following: +# +#@hooks [ +# { +# func load +# files [ +# "/etc/alsa/bluetooth.conf" +# ] +# errors false +# } +#] + +pcm.rawbluetooth { + @args [ ADDRESS ] + @args.ADDRESS { + type string + } + type bluetooth + device $ADDRESS +} + +pcm.bluetooth { + @args [ ADDRESS ] + @args.ADDRESS { + type string + } + type plug + slave { + pcm { + type bluetooth + device $ADDRESS + } + } +} diff --git a/audio/control.c b/audio/control.c new file mode 100644 index 000000000..e55d1f47d --- /dev/null +++ b/audio/control.c @@ -0,0 +1,1160 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "logging.h" +#include "error.h" +#include "uinput.h" +#include "adapter.h" +#include "../src/device.h" +#include "device.h" +#include "manager.h" +#include "avdtp.h" +#include "control.h" +#include "sdpd.h" +#include "glib-helper.h" +#include "btio.h" +#include "dbus-common.h" + +#define AVCTP_PSM 23 + +/* 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 + +/* ctype entries */ +#define CTYPE_CONTROL 0x0 +#define CTYPE_STATUS 0x1 +#define CTYPE_NOT_IMPLEMENTED 0x8 +#define CTYPE_ACCEPTED 0x9 +#define CTYPE_REJECTED 0xA +#define CTYPE_STABLE 0xC + +/* opcodes */ +#define OP_UNITINFO 0x30 +#define OP_SUBUNITINFO 0x31 +#define OP_PASSTHROUGH 0x7c + +/* subunits of interest */ +#define SUBUNIT_PANEL 0x09 + +/* operands in passthrough commands */ +#define VOL_UP_OP 0x41 +#define VOL_DOWN_OP 0x42 +#define MUTE_OP 0x43 +#define PLAY_OP 0x44 +#define STOP_OP 0x45 +#define PAUSE_OP 0x46 +#define RECORD_OP 0x47 +#define REWIND_OP 0x48 +#define FAST_FORWARD_OP 0x49 +#define EJECT_OP 0x4a +#define FORWARD_OP 0x4b +#define BACKWARD_OP 0x4c + +static DBusConnection *connection = NULL; +static gchar *input_device_name = NULL; +static GSList *servers = NULL; + +#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 avrcp_header { + uint8_t code:4; + uint8_t _hdr0:4; + uint8_t subunit_id:3; + uint8_t subunit_type:5; + uint8_t opcode; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 3 + +#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 avrcp_header { + uint8_t _hdr0:4; + uint8_t code:4; + uint8_t subunit_type:5; + uint8_t subunit_id:3; + uint8_t opcode; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 3 + +#else +#error "Unknown byte order" +#endif + +struct avctp_state_callback { + avctp_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct avctp_server { + bdaddr_t src; + GIOChannel *io; + uint32_t tg_record_id; +#ifndef ANDROID + uint32_t ct_record_id; +#endif +}; + +struct control { + struct audio_device *dev; + + avctp_state_t state; + + int uinput; + + GIOChannel *io; + guint io_id; + + uint16_t mtu; + + gboolean target; +}; + +static struct { + const char *name; + uint8_t avrcp; + uint16_t uinput; +} key_map[] = { + { "PLAY", PLAY_OP, KEY_PLAYCD }, + { "STOP", STOP_OP, KEY_STOPCD }, + { "PAUSE", PAUSE_OP, KEY_PAUSECD }, + { "FORWARD", FORWARD_OP, KEY_NEXTSONG }, + { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG }, + { "REWIND", REWIND_OP, KEY_REWIND }, + { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD }, + { NULL } +}; + +static GSList *avctp_callbacks = NULL; + +static sdp_record_t *avrcp_ct_record() +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrct; + 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 = AVCTP_PSM, ver = 0x0100, 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); + + /* Service Class ID List */ + sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); + svclass_id = sdp_list_append(0, &avrct); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + 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(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(0, &avctp); + version = sdp_data_alloc(SDP_UINT16, &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); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = ver; + pfseq = sdp_list_append(0, &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", 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 sdp_record_t *avrcp_tg_record() +{ + 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, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVCTP_PSM, ver = 0x0100, 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); + + /* Service Class ID List */ + sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); + svclass_id = sdp_list_append(0, &avrtg); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + 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(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(0, &avctp); + version = sdp_data_alloc(SDP_UINT16, &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); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = ver; + pfseq = sdp_list_append(0, &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", 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(aproto, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static int send_event(int fd, uint16_t type, uint16_t code, int32_t value) +{ + struct uinput_event event; + + memset(&event, 0, sizeof(event)); + event.type = type; + event.code = code; + event.value = value; + + return write(fd, &event, sizeof(event)); +} + +static void send_key(int fd, uint16_t key, int pressed) +{ + if (fd < 0) + return; + + send_event(fd, EV_KEY, key, pressed); + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static void handle_panel_passthrough(struct control *control, + const unsigned char *operands, + int operand_count) +{ + const char *status; + int pressed, i; + + if (operand_count == 0) + return; + + if (operands[0] & 0x80) { + status = "released"; + pressed = 0; + } else { + status = "pressed"; + pressed = 1; + } + + for (i = 0; key_map[i].name != NULL; i++) { + if ((operands[0] & 0x7F) == key_map[i].avrcp) { + debug("AVRCP: %s %s", key_map[i].name, status); + send_key(control->uinput, key_map[i].uinput, pressed); + break; + } + } + + if (key_map[i].name == NULL) + debug("AVRCP: unknown button 0x%02X %s", + operands[0] & 0x7F, status); +} + +static void avctp_disconnected(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (!control) + return; + + if (control->io) { + g_io_channel_shutdown(control->io, TRUE, NULL); + g_io_channel_unref(control->io); + control->io = NULL; + } + + if (control->io_id) { + g_source_remove(control->io_id); + control->io_id = 0; + } + + if (control->uinput >= 0) { + ioctl(control->uinput, UI_DEV_DESTROY); + close(control->uinput); + control->uinput = -1; + } +} + +static void avctp_set_state(struct control *control, avctp_state_t new_state) +{ + GSList *l; + struct audio_device *dev = control->dev; + avdtp_session_state_t old_state = control->state; + gboolean value; + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + avctp_disconnected(control->dev); + + if (old_state != AVCTP_STATE_CONNECTED) + break; + + value = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_CONTROL_INTERFACE, + "Disconnected", DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + break; + case AVCTP_STATE_CONNECTING: + break; + case AVCTP_STATE_CONNECTED: + value = TRUE; + g_dbus_emit_signal(control->dev->conn, control->dev->path, + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_INVALID); + emit_property_changed(control->dev->conn, control->dev->path, + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + break; + default: + error("Invalid AVCTP state %d", new_state); + return; + } + + control->state = new_state; + + for (l = avctp_callbacks; l != NULL; l = l->next) { + struct avctp_state_callback *cb = l->data; + cb->cb(control->dev, old_state, new_state, cb->user_data); + } +} + +static gboolean control_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct control *control = data; + unsigned char buf[1024], *operands; + struct avctp_header *avctp; + struct avrcp_header *avrcp; + int ret, packet_size, operand_count, sock; + + if (!(cond | G_IO_IN)) + goto failed; + + sock = g_io_channel_unix_get_fd(control->io); + + ret = read(sock, buf, sizeof(buf)); + if (ret <= 0) + goto failed; + + debug("Got %d bytes of data for AVCTP session %p", ret, control); + + if ((unsigned int) ret < sizeof(struct avctp_header)) { + error("Too small AVCTP packet"); + goto failed; + } + + packet_size = ret; + + avctp = (struct avctp_header *) buf; + + debug("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, " + "PID 0x%04X", + avctp->transaction, avctp->packet_type, + avctp->cr, avctp->ipid, ntohs(avctp->pid)); + + ret -= sizeof(struct avctp_header); + if ((unsigned int) ret < sizeof(struct avrcp_header)) { + error("Too small AVRCP packet"); + goto failed; + } + + avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header)); + + ret -= sizeof(struct avrcp_header); + + operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header); + operand_count = ret; + + debug("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, " + "opcode 0x%02X, %d operands", + avctp->cr ? "response" : "command", + avrcp->code, avrcp->subunit_type, avrcp->subunit_id, + avrcp->opcode, operand_count); + + if (avctp->packet_type != AVCTP_PACKET_SINGLE) { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_NOT_IMPLEMENTED; + } else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) { + avctp->ipid = 1; + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_REJECTED; + } else if (avctp->cr == AVCTP_COMMAND && + avrcp->code == CTYPE_CONTROL && + avrcp->subunit_type == SUBUNIT_PANEL && + avrcp->opcode == OP_PASSTHROUGH) { + handle_panel_passthrough(control, operands, operand_count); + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_ACCEPTED; + } else if (avctp->cr == AVCTP_COMMAND && + avrcp->code == CTYPE_STATUS && + (avrcp->opcode == OP_UNITINFO + || avrcp->opcode == OP_SUBUNITINFO)) { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = 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 && avrcp->opcode == OP_UNITINFO) + operands[0] = 0x07; + if (operand_count >= 2) + operands[1] = SUBUNIT_PANEL << 3; + debug("reply to %s", avrcp->opcode == OP_UNITINFO ? + "OP_UNITINFO" : "OP_SUBUNITINFO"); + } else { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_REJECTED; + } + ret = write(sock, buf, packet_size); + + return TRUE; + +failed: + debug("AVCTP session %p got disconnected", control); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + return FALSE; +} + +static int uinput_create(char *name) +{ + struct uinput_dev dev; + int fd, err, i; + + fd = open("/dev/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/input/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/misc/uinput", O_RDWR); + if (fd < 0) { + err = errno; + error("Can't open input device: %s (%d)", + strerror(err), err); + return -err; + } + } + } + + memset(&dev, 0, sizeof(dev)); + if (name) + strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1); + + dev.id.bustype = BUS_BLUETOOTH; + dev.id.vendor = 0x0000; + dev.id.product = 0x0000; + dev.id.version = 0x0000; + + if (write(fd, &dev, sizeof(dev)) < 0) { + err = errno; + error("Can't write device information: %s (%d)", + strerror(err), err); + close(fd); + errno = err; + return -err; + } + + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_EVBIT, EV_REL); + ioctl(fd, UI_SET_EVBIT, EV_REP); + ioctl(fd, UI_SET_EVBIT, EV_SYN); + + for (i = 0; key_map[i].name != NULL; i++) + ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput); + + if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) { + err = errno; + error("Can't create uinput device: %s (%d)", + strerror(err), err); + close(fd); + errno = err; + return -err; + } + + return fd; +} + +static void init_uinput(struct control *control) +{ + char address[18], *name; + + ba2str(&control->dev->dst, address); + + /* Use device name from config file if specified */ + name = input_device_name; + if (!name) + name = address; + + control->uinput = uinput_create(name); + if (control->uinput < 0) + error("AVRCP: failed to init uinput for %s", address); + else + debug("AVRCP: uinput initialized for %s", address); +} + +static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data) +{ + struct control *control = data; + char address[18]; + uint16_t imtu; + GError *gerr = NULL; + + if (err) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", err->message); + return; + } + + bt_io_get(chan, BT_IO_L2CAP, &gerr, + BT_IO_OPT_DEST, &address, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID); + if (gerr) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", gerr->message); + g_error_free(gerr); + return; + } + + debug("AVCTP: connected to %s", address); + + if (!control->io) + control->io = g_io_channel_ref(chan); + + init_uinput(control); + + avctp_set_state(control, AVCTP_STATE_CONNECTED); + control->mtu = imtu; + control->io_id = g_io_add_watch(chan, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) control_cb, control); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct control *control = user_data; + GError *err = NULL; + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + return; + } + + if (!bt_io_accept(control->io, avctp_connect_cb, control, + NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + } +} + +static void avctp_confirm_cb(GIOChannel *chan, gpointer data) +{ + struct control *control = NULL; + struct audio_device *dev; + char address[18]; + bdaddr_t src, dst; + GError *err = NULL; + + bt_io_get(chan, BT_IO_L2CAP, &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); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + dev = manager_get_device(&src, &dst, TRUE); + if (!dev) { + error("Unable to get audio device object for %s", address); + goto drop; + } + + if (!dev->control) { + btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID); + if (!dev->control) + goto drop; + } + + control = dev->control; + + if (control->io) { + error("Refusing unexpected connect from %s", address); + goto drop; + } + + avctp_set_state(control, AVCTP_STATE_CONNECTING); + control->io = g_io_channel_ref(chan); + + if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID, + auth_cb, dev->control) < 0) + goto drop; + + return; + +drop: + if (!control || !control->io) + g_io_channel_shutdown(chan, TRUE, NULL); + if (control) + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); +} + +static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master) +{ + GError *err = NULL; + GIOChannel *io; + + io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, AVCTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + } + + return io; +} + +gboolean avrcp_connect(struct audio_device *dev) +{ + struct control *control = dev->control; + GError *err = NULL; + GIOChannel *io; + + if (control->state > AVCTP_STATE_DISCONNECTED) + return TRUE; + + avctp_set_state(control, AVCTP_STATE_CONNECTING); + + io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, AVCTP_PSM, + BT_IO_OPT_INVALID); + if (err) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", err->message); + g_error_free(err); + return FALSE; + } + + control->io = io; + + return TRUE; +} + +void avrcp_disconnect(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (!(control && control->io)) + return; + + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); +} + +int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) +{ + sdp_record_t *record; + gboolean tmp, master = TRUE; + GError *err = NULL; + struct avctp_server *server; + + if (config) { + tmp = g_key_file_get_boolean(config, "General", + "Master", &err); + if (err) { + debug("audio.conf: %s", err->message); + g_error_free(err); + } else + master = tmp; + err = NULL; + input_device_name = g_key_file_get_string(config, + "AVRCP", "InputDeviceName", &err); + if (err) { + debug("audio.conf: %s", err->message); + input_device_name = NULL; + g_error_free(err); + } + } + + server = g_new0(struct avctp_server, 1); + if (!server) + return -ENOMEM; + + if (!connection) + connection = dbus_connection_ref(conn); + + record = avrcp_tg_record(); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(src, record) < 0) { + error("Unable to register AVRCP target service record"); + sdp_record_free(record); + return -1; + } + server->tg_record_id = record->handle; + +#ifndef ANDROID + record = avrcp_ct_record(); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(src, record) < 0) { + error("Unable to register AVRCP controller service record"); + sdp_record_free(record); + return -1; + } + server->ct_record_id = record->handle; +#endif + + server->io = avctp_server_socket(src, master); + if (!server->io) { +#ifndef ANDROID + remove_record_from_server(server->ct_record_id); +#endif + remove_record_from_server(server->tg_record_id); + g_free(server); + return -1; + } + + bacpy(&server->src, src); + + servers = g_slist_append(servers, server); + + return 0; +} + +static struct avctp_server *find_server(GSList *list, const bdaddr_t *src) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct avctp_server *server = l->data; + + if (bacmp(&server->src, src) == 0) + return server; + } + + return NULL; +} + +void avrcp_unregister(const bdaddr_t *src) +{ + struct avctp_server *server; + + server = find_server(servers, src); + if (!server) + return; + + servers = g_slist_remove(servers, server); + +#ifndef ANDROID + remove_record_from_server(server->ct_record_id); +#endif + remove_record_from_server(server->tg_record_id); + + g_io_channel_shutdown(server->io, TRUE, NULL); + g_io_channel_unref(server->io); + g_free(server); + + if (servers) + return; + + dbus_connection_unref(connection); + connection = NULL; +} + +static DBusMessage *control_is_connected(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + DBusMessage *reply; + dbus_bool_t connected; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + connected = (control->state == AVCTP_STATE_CONNECTED); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + return reply; +} + +static int avctp_send_passthrough(struct control *control, uint8_t op) +{ + unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2]; + struct avctp_header *avctp = (void *) buf; + struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH]; + uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH]; + int err, sk = g_io_channel_unix_get_fd(control->io); + static uint8_t transaction = 0; + + memset(buf, 0, sizeof(buf)); + + avctp->transaction = transaction++; + avctp->packet_type = AVCTP_PACKET_SINGLE; + avctp->cr = AVCTP_COMMAND; + avctp->pid = htons(AV_REMOTE_SVCLASS_ID); + + avrcp->code = CTYPE_CONTROL; + avrcp->subunit_type = SUBUNIT_PANEL; + avrcp->opcode = OP_PASSTHROUGH; + + operands[0] = op & 0x7f; + operands[1] = 0; + + err = write(sk, buf, sizeof(buf)); + if (err < 0) + return err; + + /* Button release */ + avctp->transaction = transaction++; + operands[0] |= 0x80; + + return write(sk, buf, sizeof(buf)); +} + +static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + DBusMessage *reply; + int err; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (control->state != AVCTP_STATE_CONNECTED) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotConnected", + "Device not Connected"); + + if (!control->target) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotSupported", + "AVRCP Target role not supported"); + + err = avctp_send_passthrough(control, VOL_UP_OP); + if (err < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + strerror(-err)); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + DBusMessage *reply; + int err; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (control->state != AVCTP_STATE_CONNECTED) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotConnected", + "Device not Connected"); + + if (!control->target) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotSupported", + "AVRCP Target role not supported"); + + err = avctp_send_passthrough(control, VOL_DOWN_OP); + if (err < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + strerror(-err)); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *control_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + gboolean value; + + 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); + + /* Connected */ + value = (device->control->state == AVCTP_STATE_CONNECTED); + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable control_methods[] = { + { "IsConnected", "", "b", control_is_connected, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetProperties", "", "a{sv}",control_get_properties }, + { "VolumeUp", "", "", volume_up }, + { "VolumeDown", "", "", volume_down }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable control_signals[] = { + { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED}, + { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED}, + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + struct control *control = dev->control; + + debug("Unregistered interface %s on path %s", + AUDIO_CONTROL_INTERFACE, dev->path); + + if (control->state != AVCTP_STATE_DISCONNECTED) + avctp_disconnected(dev); + + g_free(control); + dev->control = NULL; +} + +void control_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_CONTROL_INTERFACE); +} + +void control_update(struct audio_device *dev, uint16_t uuid16) +{ + struct control *control = dev->control; + + if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) + control->target = TRUE; +} + +struct control *control_init(struct audio_device *dev, uint16_t uuid16) +{ + struct control *control; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_CONTROL_INTERFACE, + control_methods, control_signals, NULL, + dev, path_unregister)) + return NULL; + + debug("Registered interface %s on path %s", + AUDIO_CONTROL_INTERFACE, dev->path); + + control = g_new0(struct control, 1); + control->dev = dev; + control->state = AVCTP_STATE_DISCONNECTED; + control->uinput = -1; + + if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) + control->target = TRUE; + + return control; +} + +gboolean control_is_active(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (control && control->state != AVCTP_STATE_DISCONNECTED) + return TRUE; + + return FALSE; +} + +unsigned int avctp_add_state_cb(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->user_data = user_data; + state_cb->id = ++id; + + avctp_callbacks = g_slist_append(avctp_callbacks, state_cb); + + return state_cb->id; +} + +gboolean avctp_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = avctp_callbacks; l != NULL; l = l->next) { + struct avctp_state_callback *cb = l->data; + if (cb && cb->id == id) { + avctp_callbacks = g_slist_remove(avctp_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/audio/control.h b/audio/control.h new file mode 100644 index 000000000..ad0aca5c6 --- /dev/null +++ b/audio/control.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#define AUDIO_CONTROL_INTERFACE "org.bluez.Control" + +typedef enum { + AVCTP_STATE_DISCONNECTED = 0, + AVCTP_STATE_CONNECTING, + AVCTP_STATE_CONNECTED +} avctp_state_t; + +typedef void (*avctp_state_cb) (struct audio_device *dev, + avctp_state_t old_state, + avctp_state_t new_state, + void *user_data); + +unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data); +gboolean avctp_remove_state_cb(unsigned int id); + +int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config); +void avrcp_unregister(const bdaddr_t *src); + +gboolean avrcp_connect(struct audio_device *dev); +void avrcp_disconnect(struct audio_device *dev); + +struct control *control_init(struct audio_device *dev, uint16_t uuid16); +void control_update(struct audio_device *dev, uint16_t uuid16); +void control_unregister(struct audio_device *dev); +gboolean control_is_active(struct audio_device *dev); diff --git a/audio/ctl_bluetooth.c b/audio/ctl_bluetooth.c new file mode 100644 index 000000000..2e12f6581 --- /dev/null +++ b/audio/ctl_bluetooth.c @@ -0,0 +1,384 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include + +#include "ipc.h" + +#ifdef ENABLE_DEBUG +#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg) +#else +#define DBG(fmt, arg...) +#endif + +#define BLUETOOTH_MINVOL 0 +#define BLUETOOTH_MAXVOL 15 + +struct bluetooth_data { + snd_ctl_ext_t ext; + int sock; +}; + +enum { + BLUETOOTH_PLAYBACK, + BLUETOOTH_CAPTURE, +}; + +static const char *vol_devices[2] = { + [BLUETOOTH_PLAYBACK] = "Playback volume", + [BLUETOOTH_CAPTURE] = "Capture volume", +}; + +static void bluetooth_exit(struct bluetooth_data *data) +{ + if (data == NULL) + return; + + if (data->sock >= 0) + bt_audio_service_close(data->sock); + + free(data); +} + +static void bluetooth_close(snd_ctl_ext_t *ext) +{ + struct bluetooth_data *data = ext->private_data; + + DBG("ext %p", ext); + + bluetooth_exit(data); +} + +static int bluetooth_elem_count(snd_ctl_ext_t *ext) +{ + DBG("ext %p", ext); + + return 2; +} + +static int bluetooth_elem_list(snd_ctl_ext_t *ext, + unsigned int offset, snd_ctl_elem_id_t *id) +{ + DBG("ext %p offset %d id %p", ext, offset, id); + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + + if (offset > 1) + return -EINVAL; + + snd_ctl_elem_id_set_name(id, vol_devices[offset]); + + return 0; +} + +static snd_ctl_ext_key_t bluetooth_find_elem(snd_ctl_ext_t *ext, + const snd_ctl_elem_id_t *id) +{ + const char *name = snd_ctl_elem_id_get_name(id); + int i; + + DBG("ext %p id %p name '%s'", ext, id, name); + + for (i = 0; i < 2; i++) + if (strcmp(name, vol_devices[i]) == 0) + return i; + + return SND_CTL_EXT_KEY_NOT_FOUND; +} + +static int bluetooth_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + int *type, unsigned int *acc, unsigned int *count) +{ + DBG("ext %p key %ld", ext, key); + + *type = SND_CTL_ELEM_TYPE_INTEGER; + *acc = SND_CTL_EXT_ACCESS_READWRITE; + *count = 1; + + return 0; +} + +static int bluetooth_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *imin, long *imax, long *istep) +{ + DBG("ext %p key %ld", ext, key); + + *istep = 1; + *imin = BLUETOOTH_MINVOL; + *imax = BLUETOOTH_MAXVOL; + + return 0; +} + +static int bluetooth_send_ctl(struct bluetooth_data *data, + uint8_t mode, uint8_t key, struct bt_control_rsp *rsp) +{ + int ret; + struct bt_control_req *req = (void *) rsp; + bt_audio_error_t *err = (void *) rsp; + const char *type, *name; + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_CONTROL; + req->h.length = sizeof(*req); + + req->mode = mode; + req->key = key; + + ret = send(data->sock, req, BT_SUGGESTED_BUFFER_SIZE, MSG_NOSIGNAL); + if (ret <= 0) { + SYSERR("Unable to request new volume value to server"); + return -errno; + } + + ret = recv(data->sock, rsp, BT_SUGGESTED_BUFFER_SIZE, 0); + if (ret <= 0) { + SNDERR("Unable to receive new volume value from server"); + return -errno; + } + + if (rsp->h.type == BT_ERROR) { + SNDERR("BT_CONTROL failed : %s (%d)", + strerror(err->posix_errno), + err->posix_errno); + return -err->posix_errno; + } + + type = bt_audio_strtype(rsp->h.type); + if (!type) { + SNDERR("Bogus message type %d " + "received from audio service", + rsp->h.type); + return -EINVAL; + } + + name = bt_audio_strname(rsp->h.name); + if (!name) { + SNDERR("Bogus message name %d " + "received from audio service", + rsp->h.name); + return -EINVAL; + } + + if (rsp->h.name != BT_CONTROL) { + SNDERR("Unexpected message %s received", type); + return -EINVAL; + } + + return 0; +} + +static int bluetooth_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *value) +{ + struct bluetooth_data *data = ext->private_data; + int ret; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_control_rsp *rsp = (void *) buf; + + DBG("ext %p key %ld", ext, key); + + memset(buf, 0, sizeof(buf)); + *value = 0; + + ret = bluetooth_send_ctl(data, key, 0, rsp); + if (ret < 0) + goto done; + + *value = rsp->key; +done: + return ret; +} + +static int bluetooth_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *value) +{ + struct bluetooth_data *data = ext->private_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_control_rsp *rsp = (void *) buf; + long current; + int ret, keyvalue; + + DBG("ext %p key %ld", ext, key); + + ret = bluetooth_read_integer(ext, key, ¤t); + if (ret < 0) + return ret; + + if (*value == current) + return 0; + + while (*value != current) { + keyvalue = (*value > current) ? BT_CONTROL_KEY_VOL_UP : + BT_CONTROL_KEY_VOL_DOWN; + + ret = bluetooth_send_ctl(data, key, keyvalue, rsp); + if (ret < 0) + break; + + current = keyvalue; + } + + return ret; +} + +static int bluetooth_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id, + unsigned int *event_mask) +{ + struct bluetooth_data *data = ext->private_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_control_ind *ind = (void *) buf; + int ret; + const char *type, *name; + + DBG("ext %p id %p", ext, id); + + memset(buf, 0, sizeof(buf)); + + ret = recv(data->sock, ind, BT_SUGGESTED_BUFFER_SIZE, MSG_DONTWAIT); + if (ret < 0) { + SNDERR("Failed while receiving data: %s (%d)", + strerror(errno), errno); + return -errno; + } + + type = bt_audio_strtype(ind->h.type); + if (!type) { + SNDERR("Bogus message type %d " + "received from audio service", + ind->h.type); + return -EAGAIN; + } + + name = bt_audio_strname(ind->h.name); + if (!name) { + SNDERR("Bogus message name %d " + "received from audio service", + ind->h.name); + return -EAGAIN; + } + + if (ind->h.name != BT_CONTROL) { + SNDERR("Unexpected message %s received", name); + return -EAGAIN; + } + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(id, ind->mode == BLUETOOTH_PLAYBACK ? + vol_devices[BLUETOOTH_PLAYBACK] : + vol_devices[BLUETOOTH_CAPTURE]); + *event_mask = SND_CTL_EVENT_MASK_VALUE; + + return 1; +} + +static snd_ctl_ext_callback_t bluetooth_callback = { + .close = bluetooth_close, + .elem_count = bluetooth_elem_count, + .elem_list = bluetooth_elem_list, + .find_elem = bluetooth_find_elem, + .get_attribute = bluetooth_get_attribute, + .get_integer_info = bluetooth_get_integer_info, + .read_integer = bluetooth_read_integer, + .write_integer = bluetooth_write_integer, + .read_event = bluetooth_read_event, +}; + +static int bluetooth_init(struct bluetooth_data *data) +{ + int sk; + + if (!data) + return -EINVAL; + + memset(data, 0, sizeof(struct bluetooth_data)); + + data->sock = -1; + + sk = bt_audio_service_open(); + if (sk < 0) + return -errno; + + data->sock = sk; + + return 0; +} + +SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth); + +SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth) +{ + struct bluetooth_data *data; + int err; + + DBG("Bluetooth Control plugin"); + + data = malloc(sizeof(struct bluetooth_data)); + if (!data) { + err = -ENOMEM; + goto error; + } + + err = bluetooth_init(data); + if (err < 0) + goto error; + + data->ext.version = SND_CTL_EXT_VERSION; + data->ext.card_idx = -1; + + strncpy(data->ext.id, "bluetooth", sizeof(data->ext.id) - 1); + strncpy(data->ext.driver, "Bluetooth-Audio", sizeof(data->ext.driver) - 1); + strncpy(data->ext.name, "Bluetooth Audio", sizeof(data->ext.name) - 1); + strncpy(data->ext.longname, "Bluetooth Audio", sizeof(data->ext.longname) - 1); + strncpy(data->ext.mixername, "Bluetooth Audio", sizeof(data->ext.mixername) - 1); + + data->ext.callback = &bluetooth_callback; + data->ext.poll_fd = data->sock; + data->ext.private_data = data; + + err = snd_ctl_ext_create(&data->ext, name, mode); + if (err < 0) + goto error; + + *handlep = data->ext.handle; + + return 0; + +error: + bluetooth_exit(data); + + return err; +} + +SND_CTL_PLUGIN_SYMBOL(bluetooth); diff --git a/audio/device.c b/audio/device.c new file mode 100644 index 000000000..9662dec9c --- /dev/null +++ b/audio/device.c @@ -0,0 +1,799 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "logging.h" +#include "textfile.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#include "error.h" +#include "ipc.h" +#include "dbus-common.h" +#include "device.h" +#include "unix.h" +#include "avdtp.h" +#include "control.h" +#include "headset.h" +#include "gateway.h" +#include "sink.h" +#include "source.h" + +#define AUDIO_INTERFACE "org.bluez.Audio" + +#define CONTROL_CONNECT_TIMEOUT 2 +#define AVDTP_CONNECT_TIMEOUT 1 +#define HEADSET_CONNECT_TIMEOUT 1 + +typedef enum { + AUDIO_STATE_DISCONNECTED, + AUDIO_STATE_CONNECTING, + AUDIO_STATE_CONNECTED, +} audio_state_t; + +struct service_auth { + service_auth_cb cb; + void *user_data; +}; + +struct dev_priv { + audio_state_t state; + + headset_state_t hs_state; + sink_state_t sink_state; + avctp_state_t avctp_state; + GSList *auths; + + DBusMessage *conn_req; + DBusMessage *dc_req; + + guint control_timer; + guint avdtp_timer; + guint headset_timer; + + gboolean authorized; + guint auth_idle_id; +}; + +static unsigned int sink_callback_id = 0; +static unsigned int avctp_callback_id = 0; +static unsigned int avdtp_callback_id = 0; +static unsigned int headset_callback_id = 0; + +static void device_free(struct audio_device *dev) +{ + struct dev_priv *priv = dev->priv; + + if (dev->conn) + dbus_connection_unref(dev->conn); + + btd_device_unref(dev->btd_dev); + + if (priv) { + if (priv->control_timer) + g_source_remove(priv->control_timer); + if (priv->avdtp_timer) + g_source_remove(priv->avdtp_timer); + if (priv->headset_timer) + g_source_remove(priv->headset_timer); + if (priv->dc_req) + dbus_message_unref(priv->dc_req); + if (priv->conn_req) + dbus_message_unref(priv->conn_req); + g_free(priv); + } + + g_free(dev->path); + g_free(dev); +} + +static const char *state2str(audio_state_t state) +{ + switch (state) { + case AUDIO_STATE_DISCONNECTED: + return "disconnected"; + case AUDIO_STATE_CONNECTING: + return "connecting"; + case AUDIO_STATE_CONNECTED: + return "connected"; + default: + error("Invalid audio state %d", state); + return NULL; + } +} + +static void device_set_state(struct audio_device *dev, audio_state_t new_state) +{ + struct dev_priv *priv = dev->priv; + const char *state_str; + DBusMessage *reply = NULL; + + state_str = state2str(new_state); + if (!state_str) + return; + + if (new_state == AUDIO_STATE_DISCONNECTED) + priv->authorized = FALSE; + + if (dev->priv->state == new_state) { + debug("state change attempted from %s to %s", + state_str, state_str); + return; + } + + dev->priv->state = new_state; + + if (priv->dc_req && new_state == AUDIO_STATE_DISCONNECTED) { + reply = dbus_message_new_method_return(priv->dc_req); + dbus_message_unref(priv->dc_req); + priv->dc_req = NULL; + g_dbus_send_message(dev->conn, reply); + } + + if (priv->conn_req && new_state != AUDIO_STATE_CONNECTING) { + if (new_state == AUDIO_STATE_CONNECTED) + reply = dbus_message_new_method_return(priv->conn_req); + else + reply = g_dbus_create_error(priv->conn_req, + ERROR_INTERFACE + ".ConnectFailed", + "Connecting failed"); + dbus_message_unref(priv->conn_req); + priv->conn_req = NULL; + g_dbus_send_message(dev->conn, reply); + } + + emit_property_changed(dev->conn, dev->path, + AUDIO_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); +} + +static gboolean control_connect_timeout(gpointer user_data) +{ + struct audio_device *dev = user_data; + + dev->priv->control_timer = 0; + + if (dev->control) + avrcp_connect(dev); + + return FALSE; +} + +static gboolean device_set_control_timer(struct audio_device *dev) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->control) + return FALSE; + + if (priv->control_timer) + return FALSE; + + priv->control_timer = g_timeout_add_seconds(CONTROL_CONNECT_TIMEOUT, + control_connect_timeout, + dev); + + return TRUE; +} + +static void device_remove_control_timer(struct audio_device *dev) +{ + if (dev->priv->control_timer) + g_source_remove(dev->priv->control_timer); + dev->priv->control_timer = 0; +} + +static gboolean avdtp_connect_timeout(gpointer user_data) +{ + struct audio_device *dev = user_data; + + dev->priv->avdtp_timer = 0; + + if (dev->sink) { + struct avdtp *session = avdtp_get(&dev->src, &dev->dst); + + if (!session) + return FALSE; + + sink_setup_stream(dev->sink, session); + avdtp_unref(session); + } + + return FALSE; +} + +static gboolean device_set_avdtp_timer(struct audio_device *dev) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->sink) + return FALSE; + + if (priv->avdtp_timer) + return FALSE; + + priv->avdtp_timer = g_timeout_add_seconds(AVDTP_CONNECT_TIMEOUT, + avdtp_connect_timeout, + dev); + + return TRUE; +} + +static void device_remove_avdtp_timer(struct audio_device *dev) +{ + if (dev->priv->avdtp_timer) + g_source_remove(dev->priv->avdtp_timer); + dev->priv->avdtp_timer = 0; +} + +static gboolean headset_connect_timeout(gpointer user_data) +{ + struct audio_device *dev = user_data; + struct dev_priv *priv = dev->priv; + + dev->priv->headset_timer = 0; + + if (dev->headset == NULL) + return FALSE; + + if (headset_config_stream(dev, FALSE, NULL, NULL) == 0) { + if (priv->state != AUDIO_STATE_CONNECTED && + (priv->sink_state == SINK_STATE_CONNECTED || + priv->sink_state == SINK_STATE_PLAYING)) + device_set_state(dev, AUDIO_STATE_CONNECTED); + } + + return FALSE; +} + +static gboolean device_set_headset_timer(struct audio_device *dev) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->headset) + return FALSE; + + if (priv->headset_timer) + return FALSE; + + priv->headset_timer = g_timeout_add_seconds(HEADSET_CONNECT_TIMEOUT, + headset_connect_timeout, dev); + + return TRUE; +} + +static void device_remove_headset_timer(struct audio_device *dev) +{ + if (dev->priv->headset_timer) + g_source_remove(dev->priv->headset_timer); + dev->priv->headset_timer = 0; +} + +static void device_avdtp_cb(struct audio_device *dev, struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + if (!dev->sink || !dev->control) + return; + + if (new_state == AVDTP_SESSION_STATE_CONNECTED) { + if (avdtp_stream_setup_active(session)) + device_set_control_timer(dev); + else + avrcp_connect(dev); + } +} + +static void device_sink_cb(struct audio_device *dev, + sink_state_t old_state, + sink_state_t new_state, + void *user_data) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->sink) + return; + + priv->sink_state = new_state; + + switch (new_state) { + case SINK_STATE_DISCONNECTED: + if (dev->control) { + device_remove_control_timer(dev); + avrcp_disconnect(dev); + } + if (priv->hs_state == HEADSET_STATE_DISCONNECTED) + device_set_state(dev, AUDIO_STATE_DISCONNECTED); + else if (old_state == SINK_STATE_CONNECTING) { + switch (priv->hs_state) { + case HEADSET_STATE_CONNECTED: + case HEADSET_STATE_PLAY_IN_PROGRESS: + case HEADSET_STATE_PLAYING: + device_set_state(dev, AUDIO_STATE_CONNECTED); + default: + break; + } + } + break; + case SINK_STATE_CONNECTING: + device_remove_avdtp_timer(dev); + if (priv->hs_state == HEADSET_STATE_DISCONNECTED) + device_set_state(dev, AUDIO_STATE_CONNECTING); + break; + case SINK_STATE_CONNECTED: + if (old_state == SINK_STATE_PLAYING) + break; +#ifdef ANDROID + android_set_high_priority(&dev->dst); +#endif + if (dev->auto_connect) { + if (!dev->headset) + device_set_state(dev, AUDIO_STATE_CONNECTED); + if (priv->hs_state == HEADSET_STATE_DISCONNECTED) + device_set_headset_timer(dev); + else if (priv->hs_state == HEADSET_STATE_CONNECTED) + device_set_state(dev, AUDIO_STATE_CONNECTED); + } else if (priv->hs_state != HEADSET_STATE_CONNECTED) + device_set_state(dev, AUDIO_STATE_CONNECTED); + break; + case SINK_STATE_PLAYING: + break; + } +} + +static void device_avctp_cb(struct audio_device *dev, + avctp_state_t old_state, + avctp_state_t new_state, + void *user_data) +{ + if (!dev->control) + return; + + dev->priv->avctp_state = new_state; + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + break; + case AVCTP_STATE_CONNECTING: + device_remove_control_timer(dev); + break; + case AVCTP_STATE_CONNECTED: + break; + } +} + +static void device_headset_cb(struct audio_device *dev, + headset_state_t old_state, + headset_state_t new_state, + void *user_data) +{ + struct dev_priv *priv = dev->priv; + + if (!dev->headset) + return; + + priv->hs_state = new_state; + + switch (new_state) { + case HEADSET_STATE_DISCONNECTED: + device_remove_avdtp_timer(dev); + if (priv->sink_state != SINK_STATE_DISCONNECTED && + dev->sink && priv->dc_req) { + sink_shutdown(dev->sink); + break; + } + if (priv->sink_state == SINK_STATE_DISCONNECTED) + device_set_state(dev, AUDIO_STATE_DISCONNECTED); + else if (old_state == HEADSET_STATE_CONNECT_IN_PROGRESS && + (priv->sink_state == SINK_STATE_CONNECTED || + priv->sink_state == SINK_STATE_PLAYING)) + device_set_state(dev, AUDIO_STATE_CONNECTED); + break; + case HEADSET_STATE_CONNECT_IN_PROGRESS: + device_remove_headset_timer(dev); + if (priv->sink_state == SINK_STATE_DISCONNECTED) + device_set_state(dev, AUDIO_STATE_CONNECTING); + break; + case HEADSET_STATE_CONNECTED: + if (old_state == HEADSET_STATE_CONNECTED || + old_state == HEADSET_STATE_PLAY_IN_PROGRESS || + old_state == HEADSET_STATE_PLAYING) + break; + if (dev->auto_connect) { + if (!dev->sink) + device_set_state(dev, AUDIO_STATE_CONNECTED); + else if (priv->sink_state == SINK_STATE_DISCONNECTED) + device_set_avdtp_timer(dev); + else if (priv->sink_state == SINK_STATE_CONNECTED) + device_set_state(dev, AUDIO_STATE_CONNECTED); + } else if (priv->sink_state != SINK_STATE_CONNECTED) + device_set_state(dev, AUDIO_STATE_CONNECTED); + break; + case HEADSET_STATE_PLAY_IN_PROGRESS: + break; + case HEADSET_STATE_PLAYING: + break; + } +} + +static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *dev = data; + struct dev_priv *priv = dev->priv; + + if (priv->state == AUDIO_STATE_CONNECTING) + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", + "Connect in Progress"); + else if (priv->state == AUDIO_STATE_CONNECTED) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".AlreadyConnected", + "Already Connected"); + + dev->auto_connect = TRUE; + + if (dev->headset) + headset_config_stream(dev, FALSE, NULL, NULL); + + if (priv->state != AUDIO_STATE_CONNECTING && dev->sink) { + struct avdtp *session = avdtp_get(&dev->src, &dev->dst); + + if (!session) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".Failed", + "Failed to get AVDTP session"); + + sink_setup_stream(dev->sink, session); + avdtp_unref(session); + } + + /* The previous calls should cause a call to the state callback to + * indicate AUDIO_STATE_CONNECTING */ + if (priv->state != AUDIO_STATE_CONNECTING) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".ConnectFailed", + "Headset connect failed"); + + priv->conn_req = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *dev = data; + struct dev_priv *priv = dev->priv; + + if (priv->state == AUDIO_STATE_DISCONNECTED) + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected", + "Not connected"); + + if (priv->dc_req) + return dbus_message_new_method_return(msg); + + priv->dc_req = dbus_message_ref(msg); + + if (priv->hs_state != HEADSET_STATE_DISCONNECTED) + headset_shutdown(dev); + else if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED) + sink_shutdown(dev->sink); + else { + dbus_message_unref(priv->dc_req); + priv->dc_req = NULL; + return dbus_message_new_method_return(msg); + } + + return NULL; +} + +static DBusMessage *dev_get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *state; + + 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); + + /* State */ + state = state2str(device->priv->state); + if (state) + dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable dev_methods[] = { + { "Connect", "", "", dev_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", dev_disconnect }, + { "GetProperties", "", "a{sv}",dev_get_properties }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable dev_signals[] = { + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +struct audio_device *audio_device_register(DBusConnection *conn, + struct btd_device *device, + const char *path, const bdaddr_t *src, + const bdaddr_t *dst) +{ + struct audio_device *dev; + + if (!conn || !path) + return NULL; + + dev = g_new0(struct audio_device, 1); + + dev->btd_dev = btd_device_ref(device); + dev->path = g_strdup(path); + bacpy(&dev->dst, dst); + bacpy(&dev->src, src); + dev->conn = dbus_connection_ref(conn); + dev->priv = g_new0(struct dev_priv, 1); + dev->priv->state = AUDIO_STATE_DISCONNECTED; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_INTERFACE, + dev_methods, dev_signals, NULL, + dev, NULL)) { + error("Unable to register %s on %s", AUDIO_INTERFACE, + dev->path); + device_free(dev); + return NULL; + } + + debug("Registered interface %s on path %s", AUDIO_INTERFACE, + dev->path); + + if (sink_callback_id == 0) + sink_callback_id = sink_add_state_cb(device_sink_cb, NULL); + + if (avdtp_callback_id == 0) + avdtp_callback_id = avdtp_add_state_cb(device_avdtp_cb, NULL); + if (avctp_callback_id == 0) + avctp_callback_id = avctp_add_state_cb(device_avctp_cb, NULL); + + if (headset_callback_id == 0) + headset_callback_id = headset_add_state_cb(device_headset_cb, + NULL); + + return dev; +} + +gboolean audio_device_is_active(struct audio_device *dev, + const char *interface) +{ + if (!interface) { + if ((dev->sink || dev->source) && + avdtp_is_connected(&dev->src, &dev->dst)) + return TRUE; + + if (dev->headset && headset_is_active(dev)) + return TRUE; + } + else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink && + avdtp_is_connected(&dev->src, &dev->dst)) + return TRUE; + else if (!strcmp(interface, AUDIO_SOURCE_INTERFACE) && dev->source && + avdtp_is_connected(&dev->src, &dev->dst)) + return TRUE; + else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset && + headset_is_active(dev)) + return TRUE; + else if (!strcmp(interface, AUDIO_CONTROL_INTERFACE) && dev->control && + control_is_active(dev)) + return TRUE; + else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway && + gateway_is_connected(dev)) + return TRUE; + + return FALSE; +} + +void audio_device_unregister(struct audio_device *device) +{ + unix_device_removed(device); + + if (device->headset) + headset_unregister(device); + + if (device->sink) + sink_unregister(device); + + if (device->source) + source_unregister(device); + + if (device->control) + control_unregister(device); + + g_dbus_unregister_interface(device->conn, device->path, + AUDIO_INTERFACE); + + device_free(device); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *dev = user_data; + struct dev_priv *priv = dev->priv; + + if (derr == NULL) + priv->authorized = TRUE; + + while (priv->auths) { + struct service_auth *auth = priv->auths->data; + + auth->cb(derr, auth->user_data); + priv->auths = g_slist_remove(priv->auths, auth); + g_free(auth); + } +} + +static gboolean auth_idle_cb(gpointer user_data) +{ + struct audio_device *dev = user_data; + struct dev_priv *priv = dev->priv; + + priv->auth_idle_id = 0; + + auth_cb(NULL, dev); + + return FALSE; +} + +static gboolean audio_device_is_connected(struct audio_device *dev) +{ + if (dev->headset) { + headset_state_t state = headset_get_state(dev); + + if (state == HEADSET_STATE_CONNECTED || + state == HEADSET_STATE_PLAY_IN_PROGRESS || + state == HEADSET_STATE_PLAYING) + return TRUE; + } + + if (dev->sink) { + sink_state_t state = sink_get_state(dev); + + if (state == SINK_STATE_CONNECTED || + state == SINK_STATE_PLAYING) + return TRUE; + } + + if (dev->source) { + source_state_t state = source_get_state(dev); + + if (state == SOURCE_STATE_CONNECTED || + state == SOURCE_STATE_PLAYING) + return TRUE; + } + + return FALSE; +} + +int audio_device_request_authorization(struct audio_device *dev, + const char *uuid, service_auth_cb cb, + void *user_data) +{ + struct dev_priv *priv = dev->priv; + struct service_auth *auth; + int err; + + auth = g_try_new0(struct service_auth, 1); + if (!auth) + return -ENOMEM; + + auth->cb = cb; + auth->user_data = user_data; + + priv->auths = g_slist_append(priv->auths, auth); + if (g_slist_length(priv->auths) > 1) + return 0; + + if (priv->authorized || audio_device_is_connected(dev)) { + priv->auth_idle_id = g_idle_add(auth_idle_cb, dev); + return 0; + } + + err = btd_request_authorization(&dev->src, &dev->dst, uuid, auth_cb, + dev); + if (err < 0) { + priv->auths = g_slist_remove(priv->auths, auth); + g_free(auth); + } + + return err; +} + +int audio_device_cancel_authorization(struct audio_device *dev, + authorization_cb cb, void *user_data) +{ + struct dev_priv *priv = dev->priv; + GSList *l, *next; + + for (l = priv->auths; l != NULL; l = next) { + struct service_auth *auth = priv->auths->data; + + next = g_slist_next(l); + + if (cb && auth->cb != cb) + continue; + + if (user_data && auth->user_data != user_data) + continue; + + priv->auths = g_slist_remove(priv->auths, auth); + g_free(auth); + } + + if (g_slist_length(priv->auths) == 0) { + if (priv->auth_idle_id > 0) { + g_source_remove(priv->auth_idle_id); + priv->auth_idle_id = 0; + } else + btd_cancel_authorization(&dev->src, &dev->dst); + } + + return 0; +} diff --git a/audio/device.h b/audio/device.h new file mode 100644 index 000000000..061c3da4f --- /dev/null +++ b/audio/device.h @@ -0,0 +1,87 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805F9B34FB" + +#define HSP_HS_UUID "00001108-0000-1000-8000-00805F9B34FB" +#define HSP_AG_UUID "00001112-0000-1000-8000-00805F9B34FB" + +#define HFP_HS_UUID "0000111E-0000-1000-8000-00805F9B34FB" +#define HFP_AG_UUID "0000111F-0000-1000-8000-00805F9B34FB" + +#define ADVANCED_AUDIO_UUID "0000110D-0000-1000-8000-00805F9B34FB" + +#define A2DP_SOURCE_UUID "0000110A-0000-1000-8000-00805F9B34FB" +#define A2DP_SINK_UUID "0000110B-0000-1000-8000-00805F9B34FB" + +#define AVRCP_REMOTE_UUID "0000110E-0000-1000-8000-00805F9B34FB" +#define AVRCP_TARGET_UUID "0000110C-0000-1000-8000-00805F9B34FB" + +/* Move these to respective .h files once they exist */ +#define AUDIO_SOURCE_INTERFACE "org.bluez.AudioSource" +#define AUDIO_CONTROL_INTERFACE "org.bluez.Control" + +struct source; +struct control; +struct target; +struct sink; +struct headset; +struct gateway; +struct dev_priv; + +struct audio_device { + struct btd_device *btd_dev; + + DBusConnection *conn; + char *path; + bdaddr_t src; + bdaddr_t dst; + + gboolean auto_connect; + + struct headset *headset; + struct gateway *gateway; + struct sink *sink; + struct source *source; + struct control *control; + struct target *target; + + struct dev_priv *priv; +}; + +struct audio_device *audio_device_register(DBusConnection *conn, + struct btd_device *device, + const char *path, const bdaddr_t *src, + const bdaddr_t *dst); + +void audio_device_unregister(struct audio_device *device); + +gboolean audio_device_is_active(struct audio_device *dev, + const char *interface); + +typedef void (*authorization_cb) (DBusError *derr, void *user_data); + +int audio_device_request_authorization(struct audio_device *dev, + const char *uuid, authorization_cb cb, + void *user_data); diff --git a/audio/gateway.c b/audio/gateway.c new file mode 100644 index 000000000..09a401c8f --- /dev/null +++ b/audio/gateway.c @@ -0,0 +1,1223 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2008-2009 Leonid Movshovich + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "glib-helper.h" +#include "device.h" +#include "gateway.h" +#include "logging.h" +#include "error.h" +#include "btio.h" +#include "dbus-common.h" + +#define RFCOMM_BUF_SIZE 256 +/* not-more-then-16 defined by GSM + 1 for NULL + padding */ +#define AG_INDICATOR_DESCR_SIZE 20 +#define AG_CALLER_NUM_SIZE 64 /* size of number + type */ + +/* commands */ +#define AG_FEATURES "AT+BRSF=26\r" /* = 0x7F = All features supported */ +#define AG_INDICATORS_SUPP "AT+CIND=?\r" +#define AG_INDICATORS_VAL "AT+CIND?\r" +#define AG_INDICATORS_ENABLE "AT+CMER=3,0,0,1\r" +#define AG_HOLD_MPTY_SUPP "AT+CHLD=?\r" +#define AG_CALLER_IDENT_ENABLE "AT+CLIP=1\r" +#define AG_CARRIER_FORMAT "AT+COPS=3,0\r" +#define AG_EXTENDED_RESULT_CODE "AT+CMEE=1\r" + +#define AG_FEATURE_3WAY 0x1 +#define AG_FEATURE_EXTENDED_RES_CODE 0x100 +/* Hold and multipary AG features. + * Comments below are copied from hands-free spec for reference */ +/* Releases all held calls or sets User Determined User Busy (UDUB) + * for a waiting call */ +#define AG_CHLD_0 0x01 +/* Releases all active calls (if any exist) and accepts the other + * (held or waiting) call */ +#define AG_CHLD_1 0x02 +/* Releases specified active call only */ +#define AG_CHLD_1x 0x04 +/* Places all active calls (if any exist) on hold and accepts the other + * (held or waiting) call */ +#define AG_CHLD_2 0x08 +/* Request private consultation mode with specified call (Place all + * calls on hold EXCEPT the call ) */ +#define AG_CHLD_2x 0x10 +/* Adds a held call to the conversation */ +#define AG_CHLD_3 0x20 +/* Connects the two calls and disconnects the subscriber from both calls + * (Explicit Call Transfer). Support for this value and its associated + * functionality is optional for the HF. */ +#define AG_CHLD_4 0x40 + +#define OK_RESPONSE "\r\nOK\r\n" +#define ERROR_RESPONSE "\r\nERROR\r\n" + +struct indicator { + gchar descr[AG_INDICATOR_DESCR_SIZE]; + gint value; +}; + +struct gateway { + gateway_state_t state; + GIOChannel *rfcomm; + guint rfcomm_watch_id; + GIOChannel *sco; + gateway_stream_cb_t sco_start_cb; + void *sco_start_cb_data; + DBusMessage *connect_message; + guint ag_features; + guint hold_multiparty_features; + GSList *indies; + gboolean is_dialing; + gboolean call_active; + + int sp_gain; + int mic_gain; +}; + +static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *device); + +int gateway_close(struct audio_device *device); + +static void rfcomm_start_watch(struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + + gw->rfcomm_watch_id = g_io_add_watch(gw->rfcomm, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) rfcomm_ag_data_cb, dev); +} + +static void rfcomm_stop_watch(struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + + g_source_remove(gw->rfcomm_watch_id); +} + +static gboolean io_channel_write_all(GIOChannel *io, gchar *data, + gsize count) +{ + gsize written = 0; + GIOStatus status; + + while (count > 0) { + status = g_io_channel_write_chars(io, data, count, &written, + NULL); + if (status != G_IO_STATUS_NORMAL) + return FALSE; + + data += written; + count -= written; + } + return TRUE; +} + +/* it's worth to mention that data and response could be the same pointers */ +static gboolean rfcomm_send_and_read(struct gateway *gw, gchar *data, + gchar *response, gsize count) +{ + GIOChannel *rfcomm = gw->rfcomm; + gsize read = 0; + gboolean got_ok = FALSE; + gboolean got_error = FALSE; + gchar *resp_buf = response; + gsize toread = RFCOMM_BUF_SIZE - 1; + GIOStatus status; + + if (!io_channel_write_all(rfcomm, data, count)) + return FALSE; + + while (!(got_ok || got_error)) { + status = g_io_channel_read_chars(rfcomm, resp_buf, toread, + &read, NULL); + if (status == G_IO_STATUS_NORMAL) + resp_buf[read] = '\0'; + else { + debug("rfcomm_send_and_read(): %m"); + return FALSE; + } + got_ok = NULL != strstr(resp_buf, OK_RESPONSE); + got_error = NULL != strstr(resp_buf, ERROR_RESPONSE); + resp_buf += read; + toread -= read; + } + return TRUE; +} + +/* get from the names: (, ()), (, ()) + * ... */ +static GSList *parse_indicator_names(gchar *names, GSList *indies) +{ + gchar *current = names - 1; + GSList *result = indies; + gchar *next; + struct indicator *ind; + + while (current != NULL) { + current += 2; + next = strstr(current, ",("); + ind = g_slice_new(struct indicator); + strncpy(ind->descr, current, 20); + ind->descr[(intptr_t) next - (intptr_t) current] = '\0'; + result = g_slist_append(result, (gpointer) ind); + current = strstr(next + 1, ",("); + } + return result; +} + +/* get values from ,,... */ +static GSList *parse_indicator_values(gchar *values, GSList *indies) +{ + gint val; + gchar *current = values - 1; + GSList *runner = indies; + struct indicator *ind; + + while (current != NULL) { + current += 1; + sscanf(current, "%d", &val); + current = strchr(current, ','); + ind = g_slist_nth_data(runner, 0); + ind->value = val; + runner = g_slist_next(runner); + } + return indies; +} + +/* get values from ,,... */ +static guint get_hold_mpty_features(gchar *features) +{ + guint result = 0; + + if (strstr(features, "0")) + result |= AG_CHLD_0; + + if (strstr(features, "1")) + result |= AG_CHLD_1; + + if (strstr(features, "1x")) + result |= AG_CHLD_1x; + + if (strstr(features, "2")) + result |= AG_CHLD_2; + + if (strstr(features, "2x")) + result |= AG_CHLD_2x; + + if (strstr(features, "3")) + result |= AG_CHLD_3; + + if (strstr(features, "4")) + result |= AG_CHLD_4; + + return result; +} + +static gboolean establish_service_level_conn(struct gateway *gw) +{ + gchar buf[RFCOMM_BUF_SIZE]; + gboolean res; + + debug("at the begin of establish_service_level_conn()"); + res = rfcomm_send_and_read(gw, AG_FEATURES, buf, + sizeof(AG_FEATURES) - 1); + if (!res || sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features) != 1) + return FALSE; + + debug("features are 0x%X", gw->ag_features); + res = rfcomm_send_and_read(gw, AG_INDICATORS_SUPP, buf, + sizeof(AG_INDICATORS_SUPP) - 1); + if (!res || !strstr(buf, "+CIND:")) + return FALSE; + + gw->indies = parse_indicator_names(strchr(buf, '('), NULL); + + res = rfcomm_send_and_read(gw, AG_INDICATORS_VAL, buf, + sizeof(AG_INDICATORS_VAL) - 1); + if (!res || !strstr(buf, "+CIND:")) + return FALSE; + + gw->indies = parse_indicator_values(strchr(buf, ':') + 1, gw->indies); + + res = rfcomm_send_and_read(gw, AG_INDICATORS_ENABLE, buf, + sizeof(AG_INDICATORS_ENABLE) - 1); + if (!res || !strstr(buf, "OK")) + return FALSE; + + if ((gw->ag_features & AG_FEATURE_3WAY) != 0) { + res = rfcomm_send_and_read(gw, AG_HOLD_MPTY_SUPP, buf, + sizeof(AG_HOLD_MPTY_SUPP) - 1); + if (!res || !strstr(buf, "+CHLD:")) { + g_slice_free1(RFCOMM_BUF_SIZE, buf); + return FALSE; + } + gw->hold_multiparty_features = get_hold_mpty_features( + strchr(buf, '(')); + + } else + gw->hold_multiparty_features = 0; + + debug("Service layer connection successfully established!"); + rfcomm_send_and_read(gw, AG_CALLER_IDENT_ENABLE, buf, + sizeof(AG_CALLER_IDENT_ENABLE) - 1); + rfcomm_send_and_read(gw, AG_CARRIER_FORMAT, buf, + sizeof(AG_CARRIER_FORMAT) - 1); + if ((gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) != 0) + rfcomm_send_and_read(gw, AG_EXTENDED_RESULT_CODE, buf, + sizeof(AG_EXTENDED_RESULT_CODE) - 1); + + return TRUE; +} + +static void process_ind_change(struct audio_device *dev, guint index, + gint value) +{ + struct gateway *gw = dev->gateway; + struct indicator *ind = g_slist_nth_data(gw->indies, index - 1); + gchar *name = ind->descr; + + ind->value = value; + + debug("at the begin of process_ind_change, name is %s", name); + if (!strcmp(name, "\"call\"")) { + if (value > 0) { + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, + "CallStarted", DBUS_TYPE_INVALID); + gw->is_dialing = FALSE; + gw->call_active = TRUE; + } else { + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, + "CallEnded", DBUS_TYPE_INVALID); + gw->call_active = FALSE; + } + + } else if (!strcmp(name, "\"callsetup\"")) { + if (value == 0 && gw->is_dialing) { + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, + "CallTerminated", + DBUS_TYPE_INVALID); + gw->is_dialing = FALSE; + } else if (!gw->is_dialing && value > 0) + gw->is_dialing = TRUE; + + } else if (!strcmp(name, "\"callheld\"")) { + /* FIXME: The following code is based on assumptions only. + * Has to be tested for interoperability + * I assume that callheld=2 would be sent when dial from HF + * failed in case of 3-way call + * Unfortunately this path is not covered by the HF spec so + * the code has to be tested for interop + */ + /* '2' means: all calls held, no active calls */ + if (value == 2) { + if (gw->is_dialing) { + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, + "CallTerminated", + DBUS_TYPE_INVALID); + gw->is_dialing = FALSE; + } + } + } else if (!strcmp(name, "\"service\"")) + emit_property_changed(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, "RegistrationStatus", + DBUS_TYPE_UINT16, &value); + else if (!strcmp(name, "\"signal\"")) + emit_property_changed(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, "SignalStrength", + DBUS_TYPE_UINT16, &value); + else if (!strcmp(name, "\"roam\"")) + emit_property_changed(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, "RoamingStatus", + DBUS_TYPE_UINT16, &value); + else if (!strcmp(name, "\"battchg\"")) + emit_property_changed(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, "BatteryCharge", + DBUS_TYPE_UINT16, &value); +} + +static void process_ring(struct audio_device *device, GIOChannel *chan, + gchar *buf) +{ + gchar number[AG_CALLER_NUM_SIZE]; + gchar *cli; + gchar *sep; + gsize read; + GIOStatus status; + + rfcomm_stop_watch(device); + status = g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read, NULL); + if (status != G_IO_STATUS_NORMAL) + return; + + debug("at the begin of process_ring"); + if (strlen(buf) > AG_CALLER_NUM_SIZE + 10) + error("process_ring(): buf is too long '%s'", buf); + else if ((cli = strstr(buf, "\r\n+CLIP"))) { + if (sscanf(cli, "\r\n+CLIP: \"%s", number) == 1) { + sep = strchr(number, '"'); + sep[0] = '\0'; + + /* FIXME:signal will be emitted on each RING+CLIP. + * That's bad */ + cli = number; + g_dbus_emit_signal(device->conn, device->path, + AUDIO_GATEWAY_INTERFACE, "Ring", + DBUS_TYPE_STRING, &cli, + DBUS_TYPE_INVALID); + device->gateway->is_dialing = TRUE; + } else + error("process_ring(): '%s' in place of +CLIP after RING", buf); + + } + + rfcomm_start_watch(device); +} + +static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *device) +{ + gchar buf[RFCOMM_BUF_SIZE]; + struct gateway *gw; + gsize read; + /* some space for value */ + gchar indicator[AG_INDICATOR_DESCR_SIZE + 4]; + gint value; + guint index; + gchar *sep; + + debug("at the begin of rfcomm_ag_data_cb()"); + if (cond & G_IO_NVAL) + return FALSE; + + gw = device->gateway; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + debug("connection with remote BT is closed"); + gateway_close(device); + return FALSE; + } + + if (g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read, NULL) + != G_IO_STATUS_NORMAL) + return TRUE; + buf[read] = '\0'; + + if (strlen(buf) > AG_INDICATOR_DESCR_SIZE + 14) + error("rfcomm_ag_data_cb(): buf is too long '%s'", buf); + else if (sscanf(buf, "\r\n+CIEV:%s\r\n", indicator) == 1) { + sep = strchr(indicator, ','); + sep[0] = '\0'; + sep += 1; + index = atoi(indicator); + value = atoi(sep); + process_ind_change(device, index, value); + } else if (strstr(buf, "RING")) + process_ring(device, chan, buf); + else if (sscanf(buf, "\r\n+BVRA:%d\r\n", &value) == 1) { + if (value == 0) + g_dbus_emit_signal(device->conn, device->path, + AUDIO_GATEWAY_INTERFACE, + "VoiceRecognitionActive", + DBUS_TYPE_INVALID); + else + g_dbus_emit_signal(device->conn, device->path, + AUDIO_GATEWAY_INTERFACE, + "VoiceRecognitionInactive", + DBUS_TYPE_INVALID); + } else if (sscanf(buf, "\r\n+VGS:%d\r\n", &value) == 1) { + gw->sp_gain = value; + emit_property_changed(device->conn, device->path, + AUDIO_GATEWAY_INTERFACE, "SpeakerGain", + DBUS_TYPE_UINT16, &value); + } else if (sscanf(buf, "\r\n+VGM:%d\r\n", &value) == 1) { + gw->mic_gain = value; + emit_property_changed(device->conn, device->path, + AUDIO_GATEWAY_INTERFACE, "MicrophoneGain", + DBUS_TYPE_UINT16, &value); + } else + error("rfcomm_ag_data_cb(): read wrong data '%s'", buf); + + return TRUE; +} + +static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + debug("sco connection is released"); + g_io_channel_shutdown(gw->sco, TRUE, NULL); + g_io_channel_unref(gw->sco); + gw->sco = NULL; + return FALSE; + } + + return TRUE; +} + +static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct audio_device *dev = (struct audio_device *) user_data; + struct gateway *gw = dev->gateway; + + debug("at the begin of sco_connect_cb() in gateway.c"); + + if (err) { + error("sco_connect_cb(): %s", err->message); + /* not sure, but from other point of view, + * what is the reason to have headset which + * cannot play audio? */ + if (gw->sco_start_cb) + gw->sco_start_cb(NULL, gw->sco_start_cb_data); + gateway_close(dev); + return; + } + + gw->sco = g_io_channel_ref(chan); + if (gw->sco_start_cb) + gw->sco_start_cb(dev, gw->sco_start_cb_data); + + /* why is this here? */ + fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0); + g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) sco_io_cb, dev); +} + +static void rfcomm_connect_cb(GIOChannel *chan, GError *err, + gpointer user_data) +{ + struct audio_device *dev = user_data; + struct gateway *gw = dev->gateway; + DBusMessage *conn_mes = gw->connect_message; + gchar gw_addr[18]; + GIOFlags flags; + + if (err) { + error("connect(): %s", err->message); + if (gw->sco_start_cb) + gw->sco_start_cb(NULL, gw->sco_start_cb_data); + return; + } + + ba2str(&dev->dst, gw_addr); + /* Blocking mode should be default, but just in case: */ + flags = g_io_channel_get_flags(chan); + flags &= ~G_IO_FLAG_NONBLOCK; + flags &= G_IO_FLAG_MASK; + g_io_channel_set_flags(chan, flags, NULL); + g_io_channel_set_encoding(chan, NULL, NULL); + g_io_channel_set_buffered(chan, FALSE); + if (!gw->rfcomm) + gw->rfcomm = g_io_channel_ref(chan); + + if (establish_service_level_conn(dev->gateway)) { + gboolean value = TRUE; + + debug("%s: Connected to %s", dev->path, gw_addr); + rfcomm_start_watch(dev); + if (conn_mes) { + DBusMessage *reply = + dbus_message_new_method_return(conn_mes); + dbus_connection_send(dev->conn, reply, NULL); + dbus_message_unref(reply); + dbus_message_unref(conn_mes); + gw->connect_message = NULL; + } + + gw->state = GATEWAY_STATE_CONNECTED; + emit_property_changed(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, + "Connected", DBUS_TYPE_BOOLEAN, &value); + return; + } else + error("%s: Failed to establish service layer connection to %s", + dev->path, gw_addr); + + if (NULL != gw->sco_start_cb) + gw->sco_start_cb(NULL, gw->sco_start_cb_data); + + gateway_close(dev); +} + +static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data) +{ + struct audio_device *dev = user_data; + DBusMessage *msg = dev->gateway->connect_message; + int ch = -1; + sdp_list_t *protos, *classes; + uuid_t uuid; + gateway_stream_cb_t sco_cb; + GIOChannel *io; + GError *err = NULL; + + if (perr < 0) { + error("Unable to get service record: %s (%d)", strerror(-perr), + -perr); + goto fail; + } + + if (!recs || !recs->data) { + error("No records found"); + goto fail; + } + + if (sdp_get_service_classes(recs->data, &classes) < 0) { + error("Unable to get service classes from record"); + goto fail; + } + + if (sdp_get_access_protos(recs->data, &protos) < 0) { + error("Unable to get access protocols from record"); + goto fail; + } + + memcpy(&uuid, classes->data, sizeof(uuid)); + sdp_list_free(classes, free); + + if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 || + uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) { + sdp_list_free(protos, NULL); + error("Invalid service record or not HFP"); + goto fail; + } + + 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); + if (ch <= 0) { + error("Unable to extract RFCOMM channel from service record"); + goto fail; + } + + io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_INVALID); + if (!io) { + error("Unable to connect: %s", err->message); + if (msg) { + error_common_reply(dev->conn, msg, ERROR_INTERFACE + ".ConnectionAttemptFailed", + err->message); + msg = NULL; + } + g_error_free(err); + gateway_close(dev); + } + + g_io_channel_unref(io); + return; + +fail: + if (msg) + error_common_reply(dev->conn, msg, ERROR_INTERFACE + ".NotSupported", "Not supported"); + + dev->gateway->connect_message = NULL; + + sco_cb = dev->gateway->sco_start_cb; + if (sco_cb) + sco_cb(NULL, dev->gateway->sco_start_cb_data); +} + +static int get_records(struct audio_device *device) +{ + uuid_t uuid; + + sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID); + return bt_search_service(&device->src, &device->dst, &uuid, + get_record_cb, device, NULL); +} + +static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *au_dev = (struct audio_device *) data; + struct gateway *gw = au_dev->gateway; + + debug("at the begin of ag_connect()"); + if (gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".AlreadyConnected", + "Already Connected"); + + gw->connect_message = dbus_message_ref(msg); + if (get_records(au_dev) < 0) { + dbus_message_unref(gw->connect_message); + return g_dbus_create_error(msg, ERROR_INTERFACE + ".ConnectAttemptFailed", + "Connect Attempt Failed"); + } + debug("at the end of ag_connect()"); + return NULL; +} + +static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + DBusMessage *reply = NULL; + char gw_addr[18]; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (!gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + gateway_close(device); + ba2str(&device->dst, gw_addr); + debug("Disconnected from %s, %s", gw_addr, device->path); + + return reply; +} + +static DBusMessage *process_ag_reponse(DBusMessage *msg, gchar *response) +{ + DBusMessage *reply; + + + debug("in process_ag_reponse, response is %s", response); + if (strstr(response, OK_RESPONSE)) + reply = dbus_message_new_method_return(msg); + else { + /* FIXME: some code should be here to processes errors + * in better fasion */ + debug("AG responded with '%s' to %s method call", response, + dbus_message_get_member(msg)); + reply = dbus_message_new_error(msg, ERROR_INTERFACE + ".OperationFailed", + "Operation failed.See log for details"); + } + return reply; +} + +static DBusMessage *process_simple(DBusMessage *msg, struct audio_device *dev, + gchar *data) +{ + struct gateway *gw = dev->gateway; + gchar buf[RFCOMM_BUF_SIZE]; + + rfcomm_stop_watch(dev); + rfcomm_send_and_read(gw, data, buf, strlen(data)); + rfcomm_start_watch(dev); + return process_ag_reponse(msg, buf); +} + +#define AG_ANSWER "ATA\r" + +static DBusMessage *ag_answer(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *dev = data; + struct gateway *gw = dev->gateway; + + if (!gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Not Connected"); + + if (gw->call_active) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".CallAlreadyAnswered", + "Call AlreadyAnswered"); + + return process_simple(msg, dev, AG_ANSWER); +} + +#define AG_HANGUP "AT+CHUP\r" + +static DBusMessage *ag_terminate_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *dev = data; + struct gateway *gw = dev->gateway; + + if (!gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Not Connected"); + + return process_simple(msg, dev, AG_HANGUP); +} + +/* according to GSM spec */ +#define ALLOWED_NUMBER_SYMBOLS "1234567890*#ABCD" +#define AG_PLACE_CALL "ATD%s;\r" +/* dialing from memory is not supported as headset spec doesn't define a way + * to retreive phone memory entries. + */ +static DBusMessage *ag_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + gchar buf[RFCOMM_BUF_SIZE]; + gchar *number; + gint atd_len; + DBusMessage *result; + + debug("at the begin of ag_call()"); + if (!gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Not Connected"); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID); + if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS)) + return dbus_message_new_error(msg, + ERROR_INTERFACE ".BadNumber", + "Number contains characters which are not allowed"); + + atd_len = sprintf(buf, AG_PLACE_CALL, number); + rfcomm_stop_watch(device); + rfcomm_send_and_read(gw, buf, buf, atd_len); + rfcomm_start_watch(device); + + result = process_ag_reponse(msg, buf); + return result; +} + +#define AG_GET_CARRIER "AT+COPS?\r" + +static DBusMessage *ag_get_operator(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *dev = (struct audio_device *) data; + struct gateway *gw = dev->gateway; + gchar buf[RFCOMM_BUF_SIZE]; + GIOChannel *rfcomm = gw->rfcomm; + gsize read; + gchar *result, *sep; + DBusMessage *reply; + GIOStatus status; + + if (!gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Not Connected"); + + rfcomm_stop_watch(dev); + io_channel_write_all(rfcomm, AG_GET_CARRIER, strlen(AG_GET_CARRIER)); + + status = g_io_channel_read_chars(rfcomm, buf, RFCOMM_BUF_SIZE - 1, + &read, NULL); + rfcomm_start_watch(dev); + if (G_IO_STATUS_NORMAL == status) { + buf[read] = '\0'; + if (strstr(buf, "+COPS")) { + if (!strrchr(buf, ',')) + result = "0"; + else { + result = strchr(buf, '\"') + 1; + sep = strchr(result, '\"'); + sep[0] = '\0'; + } + + reply = dbus_message_new_method_return(msg); + dbus_message_append_args(reply, DBUS_TYPE_STRING, + &result, DBUS_TYPE_INVALID); + } else { + info("ag_get_operator(): '+COPS' expected but" + " '%s' received", buf); + reply = dbus_message_new_error(msg, ERROR_INTERFACE + ".Failed", + "Unexpected response from AG"); + } + } else { + error("ag_get_operator(): %m"); + reply = dbus_message_new_error(msg, ERROR_INTERFACE + ".ConnectionFailed", + "Failed to receive response from AG"); + } + + return reply; +} + +#define AG_SEND_DTMF "AT+VTS=%c\r" +static DBusMessage *ag_send_dtmf(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + gchar buf[RFCOMM_BUF_SIZE]; + gchar *number; + gint com_len; + gboolean got_ok = TRUE; + gint num_len; + gint i = 0; + + if (!gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Not Connected"); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID); + if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS)) + return dbus_message_new_error(msg, + ERROR_INTERFACE ".BadNumber", + "Number contains characters which are not allowed"); + + num_len = strlen(number); + rfcomm_stop_watch(device); + while (i < num_len && got_ok) { + com_len = sprintf(buf, AG_SEND_DTMF, number[i]); + rfcomm_send_and_read(gw, buf, buf, com_len); + got_ok = NULL != strstr(buf, OK_RESPONSE); + i += 1; + } + rfcomm_start_watch(device); + return process_ag_reponse(msg, buf); +} + +#define AG_GET_SUBSCRIBER_NUMS "AT+CNUM\r" +#define CNUM_LEN 5 /* length of "+CNUM" string */ +#define MAX_NUMBER_CNT 16 +static DBusMessage *ag_get_subscriber_num(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + gchar buf[RFCOMM_BUF_SIZE]; + gchar *number, *end; + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (!gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Not Connected"); + + rfcomm_stop_watch(device); + rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf, + strlen(AG_GET_SUBSCRIBER_NUMS)); + rfcomm_start_watch(device); + + if (strlen(buf) > AG_CALLER_NUM_SIZE + 21) + error("ag_get_subscriber_num(): buf is too long '%s'", buf); + else if (strstr(buf, "+CNUM")) { + number = strchr(buf, ','); + number++; + end = strchr(number, ','); + if (end) { + *end = '\0'; + dbus_message_append_args(reply, DBUS_TYPE_STRING, + &number, DBUS_TYPE_INVALID); + } + } else + error("ag_get_subscriber_num(): read wrong data '%s'", buf); + + return reply; +} + +static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct gateway *gw = device->gateway; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + gboolean value; + guint index = 0; + struct indicator *ind; + + 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); + + /* Connected */ + value = gateway_is_connected(device); + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); + + if (!value) + goto done; + + while ((ind = g_slist_nth_data(gw->indies, index))) { + if(!strcmp(ind->descr, "\"service\"")) + dict_append_entry(&dict, "RegistrationStatus", + DBUS_TYPE_UINT16, &ind->value); + else if (!strcmp(ind->descr, "\"signal\"")) + dict_append_entry(&dict, "SignalStrength", + DBUS_TYPE_UINT16, &ind->value); + else if (!strcmp(ind->descr, "\"roam\"")) + dict_append_entry(&dict, "RoamingStatus", + DBUS_TYPE_UINT16, &ind->value); + else if (!strcmp(ind->descr, "\"battchg\"")) + dict_append_entry(&dict, "BatteryCharge", + DBUS_TYPE_UINT16, &ind->value); + index++; + } + + /* SpeakerGain */ + dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16, + &device->gateway->sp_gain); + + /* MicrophoneGain */ + dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16, + &device->gateway->mic_gain); +done: + dbus_message_iter_close_container(&iter, &dict); + return reply; +} + +static GDBusMethodTable gateway_methods[] = { + { "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", ag_disconnect }, + { "AnswerCall", "", "", ag_answer }, + { "TerminateCall", "", "", ag_terminate_call }, + { "Call", "s", "", ag_call }, + { "GetOperatorName", "", "s", ag_get_operator }, + { "SendDTMF", "s", "", ag_send_dtmf }, + { "GetSubscriberNumber", "", "s", ag_get_subscriber_num }, + { "GetProperties", "", "a{sv}", ag_get_properties }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable gateway_signals[] = { + { "Ring", "s" }, + { "CallTerminated", "" }, + { "CallStarted", "" }, + { "CallEnded", "" }, + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +struct gateway *gateway_init(struct audio_device *dev) +{ + struct gateway *gw; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_GATEWAY_INTERFACE, + gateway_methods, gateway_signals, + NULL, dev, NULL)) + return NULL; + + debug("in gateway_init, dev is %p", dev); + gw = g_new0(struct gateway, 1); + gw->indies = NULL; + gw->is_dialing = FALSE; + gw->call_active = FALSE; + gw->state = GATEWAY_STATE_DISCONNECTED; + return gw; + +} + +gboolean gateway_is_connected(struct audio_device *dev) +{ + return (dev && dev->gateway && + dev->gateway->state == GATEWAY_STATE_CONNECTED); +} + +int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io) +{ + if (!io) + return -EINVAL; + + g_io_channel_ref(io); + dev->gateway->rfcomm = io; + + return 0; +} + +int gateway_connect_sco(struct audio_device *dev, GIOChannel *io) +{ + struct gateway *gw = dev->gateway; + + if (gw->sco) + return -EISCONN; + + gw->sco = g_io_channel_ref(io); + + g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) sco_io_cb, dev); + return 0; +} + +void gateway_start_service(struct audio_device *device) +{ + rfcomm_connect_cb(device->gateway->rfcomm, NULL, device); +} + +static void indicator_slice_free(gpointer mem) +{ + g_slice_free(struct indicator, mem); +} + +int gateway_close(struct audio_device *device) +{ + struct gateway *gw = device->gateway; + GIOChannel *rfcomm = gw->rfcomm; + GIOChannel *sco = gw->sco; + gboolean value = FALSE; + + g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL); + g_slist_free(gw->indies); + if (rfcomm) { + g_io_channel_shutdown(rfcomm, TRUE, NULL); + g_io_channel_unref(rfcomm); + gw->rfcomm = NULL; + } + + if (sco) { + g_io_channel_shutdown(sco, TRUE, NULL); + g_io_channel_unref(sco); + gw->sco = NULL; + gw->sco_start_cb = NULL; + gw->sco_start_cb_data = NULL; + } + + gw->state = GATEWAY_STATE_DISCONNECTED; + + emit_property_changed(device->conn, device->path, + AUDIO_GATEWAY_INTERFACE, + "Connected", DBUS_TYPE_BOOLEAN, &value); + return 0; +} + +/* These are functions to be called from unix.c for audio system + * ifaces (alsa, gstreamer, etc.) */ +gboolean gateway_request_stream(struct audio_device *dev, + gateway_stream_cb_t cb, void *user_data) +{ + struct gateway *gw = dev->gateway; + GError *err = NULL; + GIOChannel *io; + + if (!gw->rfcomm) { + gw->sco_start_cb = cb; + gw->sco_start_cb_data = user_data; + get_records(dev); + } else if (!gw->sco) { + gw->sco_start_cb = cb; + gw->sco_start_cb_data = user_data; + io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return FALSE; + } + } else { + if (cb) + cb(dev, user_data); + } + + return TRUE; +} + +int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t sco_cb, + void *user_data) +{ + struct gateway *gw = dev->gateway; + + if (!gw->rfcomm) { + gw->sco_start_cb = sco_cb; + gw->sco_start_cb_data = user_data; + return get_records(dev); + } + + if (sco_cb) + sco_cb(dev, user_data); + + return 0; +} + +gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id) +{ + gateway_close(dev); + return TRUE; +} + +int gateway_get_sco_fd(struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + + if (!gw || !gw->sco) + return -1; + + return g_io_channel_unix_get_fd(gw->sco); +} + +void gateway_suspend_stream(struct audio_device *dev) +{ + struct gateway *gw = dev->gateway; + + if (!gw || !gw->sco) + return; + + g_io_channel_shutdown(gw->sco, TRUE, NULL); + g_io_channel_unref(gw->sco); + gw->sco = NULL; + gw->sco_start_cb = NULL; + gw->sco_start_cb_data = NULL; +} + diff --git a/audio/gateway.h b/audio/gateway.h new file mode 100644 index 000000000..e539469f5 --- /dev/null +++ b/audio/gateway.h @@ -0,0 +1,47 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway" + +#define DEFAULT_HSP_HS_CHANNEL 6 +#define DEFAULT_HFP_HS_CHANNEL 7 + +typedef enum { + GATEWAY_STATE_DISCONNECTED, + GATEWAY_STATE_CONNECTED +} gateway_state_t; + +typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data); +struct gateway *gateway_init(struct audio_device *device); +gboolean gateway_is_connected(struct audio_device *dev); +int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan); +int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan); +void gateway_start_service(struct audio_device *device); +gboolean gateway_request_stream(struct audio_device *dev, + gateway_stream_cb_t cb, void *user_data); +int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t cb, + void *user_data); +gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id); +int gateway_get_sco_fd(struct audio_device *dev); +void gateway_suspend_stream(struct audio_device *dev); diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c new file mode 100644 index 000000000..0753f3868 --- /dev/null +++ b/audio/gsta2dpsink.c @@ -0,0 +1,703 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "gsta2dpsink.h" + +GType gst_avdtp_sink_get_type(void); + +GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug); +#define GST_CAT_DEFAULT gst_a2dp_sink_debug + +#define A2DP_SBC_RTP_PAYLOAD_TYPE 1 +#define TEMPLATE_MAX_BITPOOL_STR "64" + +#define DEFAULT_AUTOCONNECT TRUE + +enum { + PROP_0, + PROP_DEVICE, + PROP_AUTOCONNECT +}; + +GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN); + +static const GstElementDetails gst_a2dp_sink_details = + GST_ELEMENT_DETAILS("Bluetooth A2DP sink", + "Sink/Audio", + "Plays audio to an A2DP device", + "Marcel Holtmann "); + +static GstStaticPadTemplate gst_a2dp_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " + "blocks = (int) { 4, 8, 12, 16 }, " + "subbands = (int) { 4, 8 }, " + "allocation = (string) { \"snr\", \"loudness\" }, " + "bitpool = (int) [ 2, " + TEMPLATE_MAX_BITPOOL_STR " ]; " + "audio/mpeg" + )); + +static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event); +static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps); +static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad); +static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self); +static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self); +static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self); + +static void gst_a2dp_sink_finalize(GObject *obj) +{ + GstA2dpSink *self = GST_A2DP_SINK(obj); + + g_mutex_free(self->cb_mutex); + + G_OBJECT_CLASS(parent_class)->finalize(obj); +} + +static GstState gst_a2dp_sink_get_state(GstA2dpSink *self) +{ + GstState current, pending; + + gst_element_get_state(GST_ELEMENT(self), ¤t, &pending, 0); + if (pending == GST_STATE_VOID_PENDING) + return current; + + return pending; +} + +/* + * Helper function to create elements, add to the bin and link it + * to another element. + */ +static GstElement *gst_a2dp_sink_init_element(GstA2dpSink *self, + const gchar *elementname, const gchar *name, + GstElement *link_to) +{ + GstElement *element; + GstState state; + + GST_LOG_OBJECT(self, "Initializing %s", elementname); + + element = gst_element_factory_make(elementname, name); + if (element == NULL) { + GST_DEBUG_OBJECT(self, "Couldn't create %s", elementname); + return NULL; + } + + if (!gst_bin_add(GST_BIN(self), element)) { + GST_DEBUG_OBJECT(self, "failed to add %s to the bin", + elementname); + goto cleanup_and_fail; + } + + state = gst_a2dp_sink_get_state(self); + if (gst_element_set_state(element, state) == + GST_STATE_CHANGE_FAILURE) { + GST_DEBUG_OBJECT(self, "%s failed to go to playing", + elementname); + goto remove_element_and_fail; + } + + if (link_to != NULL) + if (!gst_element_link(link_to, element)) { + GST_DEBUG_OBJECT(self, "couldn't link %s", + elementname); + goto remove_element_and_fail; + } + + return element; + +remove_element_and_fail: + gst_element_set_state(element, GST_STATE_NULL); + gst_bin_remove(GST_BIN(self), element); + return NULL; + +cleanup_and_fail: + if (element != NULL) + g_object_unref(G_OBJECT(element)); + + return NULL; +} + +static void gst_a2dp_sink_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_set_details(element_class, + &gst_a2dp_sink_details); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&gst_a2dp_sink_factory)); +} + +static void gst_a2dp_sink_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstA2dpSink *self = GST_A2DP_SINK(object); + + switch (prop_id) { + case PROP_DEVICE: + if (self->sink != NULL) + gst_avdtp_sink_set_device(self->sink, + g_value_get_string(value)); + + if (self->device != NULL) + g_free(self->device); + self->device = g_value_dup_string(value); + break; + + case PROP_AUTOCONNECT: + self->autoconnect = g_value_get_boolean(value); + + if (self->sink != NULL) + g_object_set(G_OBJECT(self->sink), "auto-connect", + self->autoconnect, NULL); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_a2dp_sink_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstA2dpSink *self = GST_A2DP_SINK(object); + gchar *device; + + switch (prop_id) { + case PROP_DEVICE: + if (self->sink != NULL) { + device = gst_avdtp_sink_get_device(self->sink); + if (device != NULL) + g_value_take_string(value, device); + } + break; + case PROP_AUTOCONNECT: + if (self->sink != NULL) + g_object_get(G_OBJECT(self->sink), "auto-connect", + &self->autoconnect, NULL); + + g_value_set_boolean(value, self->autoconnect); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static gboolean gst_a2dp_sink_init_ghost_pad(GstA2dpSink *self) +{ + GstPad *capsfilter_pad; + + /* we search for the capsfilter sinkpad */ + capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink"); + + /* now we add a ghostpad */ + self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink", + capsfilter_pad)); + g_object_unref(capsfilter_pad); + + /* the getcaps of our ghostpad must reflect the device caps */ + gst_pad_set_getcaps_function(GST_PAD(self->ghostpad), + gst_a2dp_sink_get_caps); + self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad); + gst_pad_set_setcaps_function(GST_PAD(self->ghostpad), + GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps)); + + /* we need to handle events on our own and we also need the eventfunc + * of the ghostpad for forwarding calls */ + self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad)); + gst_pad_set_event_function(GST_PAD(self->ghostpad), + gst_a2dp_sink_handle_event); + + if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad))) + GST_ERROR_OBJECT(self, "failed to add ghostpad"); + + return TRUE; +} + +static void gst_a2dp_sink_remove_dynamic_elements(GstA2dpSink *self) +{ + if (self->rtp) { + GST_LOG_OBJECT(self, "removing rtp element from the bin"); + if (!gst_bin_remove(GST_BIN(self), GST_ELEMENT(self->rtp))) + GST_WARNING_OBJECT(self, "failed to remove rtp " + "element from bin"); + else + self->rtp = NULL; + } +} + +static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstA2dpSink *self = GST_A2DP_SINK(element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + self->taglist = gst_tag_list_new(); + + gst_a2dp_sink_init_fakesink(self); + break; + + case GST_STATE_CHANGE_NULL_TO_READY: + self->sink_is_in_bin = FALSE; + self->sink = GST_AVDTP_SINK(gst_element_factory_make( + "avdtpsink", "avdtpsink")); + if (self->sink == NULL) { + GST_WARNING_OBJECT(self, "failed to create avdtpsink"); + return GST_STATE_CHANGE_FAILURE; + } + + if (self->device != NULL) + gst_avdtp_sink_set_device(self->sink, + self->device); + + g_object_set(G_OBJECT(self->sink), "auto-connect", + self->autoconnect, NULL); + + ret = gst_element_set_state(GST_ELEMENT(self->sink), + GST_STATE_READY); + break; + default: + break; + } + + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, + transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (self->taglist) { + gst_tag_list_free(self->taglist); + self->taglist = NULL; + } + if (self->newseg_event != NULL) { + gst_event_unref(self->newseg_event); + self->newseg_event = NULL; + } + gst_a2dp_sink_remove_fakesink(self); + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + if (self->sink_is_in_bin) { + if (!gst_bin_remove(GST_BIN(self), + GST_ELEMENT(self->sink))) + GST_WARNING_OBJECT(self, "Failed to remove " + "avdtpsink from bin"); + } else if (self->sink != NULL) { + gst_element_set_state(GST_ELEMENT(self->sink), + GST_STATE_NULL); + g_object_unref(G_OBJECT(self->sink)); + } + + self->sink = NULL; + + gst_a2dp_sink_remove_dynamic_elements(self); + break; + default: + break; + } + + return ret; +} + +static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + object_class->set_property = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_get_property); + + object_class->finalize = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_finalize); + + element_class->change_state = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_change_state); + + g_object_class_install_property(object_class, PROP_DEVICE, + g_param_spec_string("device", "Device", + "Bluetooth remote device address", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_AUTOCONNECT, + g_param_spec_boolean("auto-connect", "Auto-connect", + "Automatically attempt to connect to device", + DEFAULT_AUTOCONNECT, G_PARAM_READWRITE)); + + GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0, + "A2DP sink element"); +} + +GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self) +{ + return gst_avdtp_sink_get_device_caps(self->sink); +} + +static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad) +{ + GstCaps *caps; + GstCaps *caps_aux; + GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + + if (self->sink == NULL) { + GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized " + "returning template caps"); + caps = gst_static_pad_template_get_caps( + &gst_a2dp_sink_factory); + } else { + GST_LOG_OBJECT(self, "Getting device caps"); + caps = gst_a2dp_sink_get_device_caps(self); + if (caps == NULL) + caps = gst_static_pad_template_get_caps( + &gst_a2dp_sink_factory); + } + caps_aux = gst_caps_copy(caps); + g_object_set(self->capsfilter, "caps", caps_aux, NULL); + gst_caps_unref(caps_aux); + return caps; +} + +static gboolean gst_a2dp_sink_init_avdtp_sink(GstA2dpSink *self) +{ + GstElement *sink; + + /* check if we don't need a new sink */ + if (self->sink_is_in_bin) + return TRUE; + + if (self->sink == NULL) + sink = gst_element_factory_make("avdtpsink", "avdtpsink"); + else + sink = GST_ELEMENT(self->sink); + + if (sink == NULL) { + GST_ERROR_OBJECT(self, "Couldn't create avdtpsink"); + return FALSE; + } + + if (!gst_bin_add(GST_BIN(self), sink)) { + GST_ERROR_OBJECT(self, "failed to add avdtpsink " + "to the bin"); + goto cleanup_and_fail; + } + + if (gst_element_set_state(sink, GST_STATE_READY) == + GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT(self, "avdtpsink failed to go to ready"); + goto remove_element_and_fail; + } + + if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) { + GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay " + "to avdtpsink"); + goto remove_element_and_fail; + } + + self->sink = GST_AVDTP_SINK(sink); + self->sink_is_in_bin = TRUE; + g_object_set(G_OBJECT(self->sink), "device", self->device, NULL); + + gst_element_set_state(sink, GST_STATE_PAUSED); + + return TRUE; + +remove_element_and_fail: + gst_element_set_state(sink, GST_STATE_NULL); + gst_bin_remove(GST_BIN(self), sink); + return FALSE; + +cleanup_and_fail: + if (sink != NULL) + g_object_unref(G_OBJECT(sink)); + + return FALSE; +} + +static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self) +{ + GstElement *rtppay; + + /* if we already have a rtp, we don't need a new one */ + if (self->rtp != NULL) + return TRUE; + + rtppay = gst_a2dp_sink_init_element(self, "rtpsbcpay", "rtp", + self->capsfilter); + if (rtppay == NULL) + return FALSE; + + self->rtp = GST_BASE_RTP_PAYLOAD(rtppay); + g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL); + + gst_element_set_state(rtppay, GST_STATE_PAUSED); + + return TRUE; +} + +static gboolean gst_a2dp_sink_init_rtp_mpeg_element(GstA2dpSink *self) +{ + GstElement *rtppay; + + /* check if we don't need a new rtp */ + if (self->rtp) + return TRUE; + + GST_LOG_OBJECT(self, "Initializing rtp mpeg element"); + /* if capsfilter is not created then we can't have our rtp element */ + if (self->capsfilter == NULL) + return FALSE; + + rtppay = gst_a2dp_sink_init_element(self, "rtpmpapay", "rtp", + self->capsfilter); + if (rtppay == NULL) + return FALSE; + + self->rtp = GST_BASE_RTP_PAYLOAD(rtppay); + + gst_element_set_state(rtppay, GST_STATE_PAUSED); + + return TRUE; +} + +static gboolean gst_a2dp_sink_init_dynamic_elements(GstA2dpSink *self, + GstCaps *caps) +{ + GstStructure *structure; + GstEvent *event; + GstPad *capsfilterpad; + gboolean crc; + gchar *mode = NULL; + + structure = gst_caps_get_structure(caps, 0); + + /* before everything we need to remove fakesink */ + gst_a2dp_sink_remove_fakesink(self); + + /* first, we need to create our rtp payloader */ + if (gst_structure_has_name(structure, "audio/x-sbc")) { + GST_LOG_OBJECT(self, "sbc media received"); + if (!gst_a2dp_sink_init_rtp_sbc_element(self)) + return FALSE; + } else if (gst_structure_has_name(structure, "audio/mpeg")) { + GST_LOG_OBJECT(self, "mp3 media received"); + if (!gst_a2dp_sink_init_rtp_mpeg_element(self)) + return FALSE; + } else { + GST_ERROR_OBJECT(self, "Unexpected media type"); + return FALSE; + } + + if (!gst_a2dp_sink_init_avdtp_sink(self)) + return FALSE; + + /* check if we should push the taglist FIXME should we push this? + * we can send the tags directly if needed */ + if (self->taglist != NULL && + gst_structure_has_name(structure, "audio/mpeg")) { + + event = gst_event_new_tag(self->taglist); + + /* send directly the crc */ + if (gst_tag_list_get_boolean(self->taglist, "has-crc", &crc)) + gst_avdtp_sink_set_crc(self->sink, crc); + + if (gst_tag_list_get_string(self->taglist, "channel-mode", + &mode)) + gst_avdtp_sink_set_channel_mode(self->sink, mode); + + capsfilterpad = gst_ghost_pad_get_target(self->ghostpad); + gst_pad_send_event(capsfilterpad, event); + self->taglist = NULL; + g_free(mode); + } + + if (!gst_avdtp_sink_set_device_caps(self->sink, caps)) + return FALSE; + + g_object_set(G_OBJECT(self->rtp), "mtu", + gst_avdtp_sink_get_link_mtu(self->sink), NULL); + + /* we forward our new segment here if we have one */ + if (self->newseg_event) { + gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp), + self->newseg_event); + self->newseg_event = NULL; + } + + return TRUE; +} + +static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps) +{ + GstA2dpSink *self; + + self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + GST_INFO_OBJECT(self, "setting caps"); + + /* now we know the caps */ + gst_a2dp_sink_init_dynamic_elements(self, caps); + + return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps); +} + +/* used for catching newsegment events while we don't have a sink, for + * later forwarding it to the sink */ +static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event) +{ + GstA2dpSink *self; + GstTagList *taglist = NULL; + GstObject *parent; + + self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + parent = gst_element_get_parent(GST_ELEMENT(self->sink)); + + if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT && + parent != GST_OBJECT_CAST(self)) { + if (self->newseg_event != NULL) + gst_event_unref(self->newseg_event); + self->newseg_event = gst_event_ref(event); + + } else if (GST_EVENT_TYPE(event) == GST_EVENT_TAG && + parent != GST_OBJECT_CAST(self)) { + if (self->taglist == NULL) + gst_event_parse_tag(event, &self->taglist); + else { + gst_event_parse_tag(event, &taglist); + gst_tag_list_insert(self->taglist, taglist, + GST_TAG_MERGE_REPLACE); + } + } + + if (parent != NULL) + gst_object_unref(GST_OBJECT(parent)); + + return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event); +} + +static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self) +{ + GstElement *element; + + element = gst_element_factory_make("capsfilter", "filter"); + if (element == NULL) + goto failed; + + if (!gst_bin_add(GST_BIN(self), element)) + goto failed; + + self->capsfilter = element; + return TRUE; + +failed: + GST_ERROR_OBJECT(self, "Failed to initialize caps filter"); + return FALSE; +} + +static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self) +{ + if (self->fakesink != NULL) + return TRUE; + + g_mutex_lock(self->cb_mutex); + self->fakesink = gst_a2dp_sink_init_element(self, "fakesink", + "fakesink", self->capsfilter); + g_mutex_unlock(self->cb_mutex); + + if (!self->fakesink) + return FALSE; + + return TRUE; +} + +static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self) +{ + g_mutex_lock(self->cb_mutex); + + if (self->fakesink != NULL) { + gst_element_set_locked_state(self->fakesink, TRUE); + gst_element_set_state(self->fakesink, GST_STATE_NULL); + + gst_bin_remove(GST_BIN(self), self->fakesink); + self->fakesink = NULL; + } + + g_mutex_unlock(self->cb_mutex); + + return TRUE; +} + +static void gst_a2dp_sink_init(GstA2dpSink *self, + GstA2dpSinkClass *klass) +{ + self->sink = NULL; + self->fakesink = NULL; + self->rtp = NULL; + self->device = NULL; + self->autoconnect = DEFAULT_AUTOCONNECT; + self->capsfilter = NULL; + self->newseg_event = NULL; + self->taglist = NULL; + self->ghostpad = NULL; + self->sink_is_in_bin = FALSE; + + self->cb_mutex = g_mutex_new(); + + /* we initialize our capsfilter */ + gst_a2dp_sink_init_caps_filter(self); + g_object_set(self->capsfilter, "caps", + gst_static_pad_template_get_caps(&gst_a2dp_sink_factory), + NULL); + + gst_a2dp_sink_init_fakesink(self); + + gst_a2dp_sink_init_ghost_pad(self); + +} + +gboolean gst_a2dp_sink_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "a2dpsink", + GST_RANK_MARGINAL, GST_TYPE_A2DP_SINK); +} + diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h new file mode 100644 index 000000000..902b678ab --- /dev/null +++ b/audio/gsta2dpsink.h @@ -0,0 +1,84 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GST_A2DP_SINK_H__ +#define __GST_A2DP_SINK_H__ + +#include +#include +#include "gstavdtpsink.h" + +G_BEGIN_DECLS + +#define GST_TYPE_A2DP_SINK \ + (gst_a2dp_sink_get_type()) +#define GST_A2DP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_A2DP_SINK,GstA2dpSink)) +#define GST_A2DP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_A2DP_SINK,GstA2dpSinkClass)) +#define GST_IS_A2DP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_A2DP_SINK)) +#define GST_IS_A2DP_SINK_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_A2DP_SINK)) + +typedef struct _GstA2dpSink GstA2dpSink; +typedef struct _GstA2dpSinkClass GstA2dpSinkClass; + +struct _GstA2dpSink { + GstBin bin; + + GstBaseRTPPayload *rtp; + GstAvdtpSink *sink; + GstElement *capsfilter; + GstElement *fakesink; + + gchar *device; + gboolean autoconnect; + gboolean sink_is_in_bin; + + GstGhostPad *ghostpad; + GstPadSetCapsFunction ghostpad_setcapsfunc; + GstPadEventFunction ghostpad_eventfunc; + + GstEvent *newseg_event; + /* Store the tags received before the a2dpsender sink is created + * when it is created we forward this to it */ + GstTagList *taglist; + + GMutex *cb_mutex; +}; + +struct _GstA2dpSinkClass { + GstBinClass parent_class; +}; + +//GType gst_a2dp_sink_get_type(void); + +gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin); + +GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self); + +G_END_DECLS + +#endif + diff --git a/audio/gstavdtpsink.c b/audio/gstavdtpsink.c new file mode 100644 index 000000000..bc25bd190 --- /dev/null +++ b/audio/gstavdtpsink.c @@ -0,0 +1,1434 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "ipc.h" +#include "rtp.h" + +#include "gstavdtpsink.h" + +GST_DEBUG_CATEGORY_STATIC(avdtp_sink_debug); +#define GST_CAT_DEFAULT avdtp_sink_debug + +#define BUFFER_SIZE 2048 +#define TEMPLATE_MAX_BITPOOL 64 +#define CRC_PROTECTED 1 +#define CRC_UNPROTECTED 0 + +#define DEFAULT_AUTOCONNECT TRUE + +#define GST_AVDTP_SINK_MUTEX_LOCK(s) G_STMT_START { \ + g_mutex_lock(s->sink_lock); \ + } G_STMT_END + +#define GST_AVDTP_SINK_MUTEX_UNLOCK(s) G_STMT_START { \ + g_mutex_unlock(s->sink_lock); \ + } G_STMT_END + + +struct bluetooth_data { + struct bt_get_capabilities_rsp *caps; /* Bluetooth device caps */ + guint link_mtu; + + gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */ +}; + +#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0) +#define IS_MPEG_AUDIO(n) (strcmp((n), "audio/mpeg") == 0) + +enum { + PROP_0, + PROP_DEVICE, + PROP_AUTOCONNECT +}; + +GST_BOILERPLATE(GstAvdtpSink, gst_avdtp_sink, GstBaseSink, + GST_TYPE_BASE_SINK); + +static const GstElementDetails avdtp_sink_details = + GST_ELEMENT_DETAILS("Bluetooth AVDTP sink", + "Sink/Audio", + "Plays audio to an A2DP device", + "Marcel Holtmann "); + +static GstStaticPadTemplate avdtp_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("application/x-rtp, " + "media = (string) \"audio\"," + "payload = (int) " + GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) { 16000, 32000, " + "44100, 48000 }, " + "encoding-name = (string) \"SBC\"; " + "application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) " + GST_RTP_PAYLOAD_MPA_STRING ", " + "clock-rate = (int) 90000; " + "application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) " + GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) 90000, " + "encoding-name = (string) \"MPA\"" + )); + +static GIOError gst_avdtp_sink_audioservice_send(GstAvdtpSink *self, + const bt_audio_msg_header_t *msg); +static GIOError gst_avdtp_sink_audioservice_expect( + GstAvdtpSink *self, + bt_audio_msg_header_t *outmsg, + guint8 expected_name); + + +static void gst_avdtp_sink_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&avdtp_sink_factory)); + + gst_element_class_set_details(element_class, &avdtp_sink_details); +} + +static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + + GST_INFO_OBJECT(self, "stop"); + + if (self->watch_id != 0) { + g_source_remove(self->watch_id); + self->watch_id = 0; + } + + if (self->server) { + bt_audio_service_close(g_io_channel_unix_get_fd(self->server)); + g_io_channel_unref(self->server); + self->server = NULL; + } + + if (self->stream) { + g_io_channel_shutdown(self->stream, TRUE, NULL); + g_io_channel_unref(self->stream); + self->stream = NULL; + } + + if (self->data) { + g_free(self->data); + self->data = NULL; + } + + if (self->stream_caps) { + gst_caps_unref(self->stream_caps); + self->stream_caps = NULL; + } + + if (self->dev_caps) { + gst_caps_unref(self->dev_caps); + self->dev_caps = NULL; + } + + return TRUE; +} + +static void gst_avdtp_sink_finalize(GObject *object) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(object); + + if (self->data) + gst_avdtp_sink_stop(GST_BASE_SINK(self)); + + if (self->device) + g_free(self->device); + + g_mutex_free(self->sink_lock); + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void gst_avdtp_sink_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstAvdtpSink *sink = GST_AVDTP_SINK(object); + + switch (prop_id) { + case PROP_DEVICE: + if (sink->device) + g_free(sink->device); + sink->device = g_value_dup_string(value); + break; + + case PROP_AUTOCONNECT: + sink->autoconnect = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_avdtp_sink_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstAvdtpSink *sink = GST_AVDTP_SINK(object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_string(value, sink->device); + break; + + case PROP_AUTOCONNECT: + g_value_set_boolean(value, sink->autoconnect); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static gint gst_avdtp_sink_bluetooth_recvmsg_fd(GstAvdtpSink *sink) +{ + int err, ret; + + ret = bt_audio_service_get_data_fd( + g_io_channel_unix_get_fd(sink->server)); + + if (ret < 0) { + err = errno; + GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)", + strerror(err), err); + return -err; + } + + sink->stream = g_io_channel_unix_new(ret); + GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret); + + return 0; +} + +static codec_capabilities_t *gst_avdtp_find_caps(GstAvdtpSink *sink, + uint8_t codec_type) +{ + struct bt_get_capabilities_rsp *rsp = sink->data->caps; + codec_capabilities_t *codec = (void *) rsp->data; + int bytes_left = rsp->h.length - sizeof(*rsp); + + while (bytes_left > 0) { + if ((codec->type == codec_type) && + !(codec->lock & BT_WRITE_LOCK)) + break; + + bytes_left -= codec->length; + codec = (void *) codec + codec->length; + } + + if (bytes_left <= 0) + return NULL; + + return codec; +} + +static gboolean gst_avdtp_sink_init_sbc_pkt_conf(GstAvdtpSink *sink, + GstCaps *caps, + sbc_capabilities_t *pkt) +{ + sbc_capabilities_t *cfg; + const GValue *value = NULL; + const char *pref, *name; + gint rate, subbands, blocks; + GstStructure *structure = gst_caps_get_structure(caps, 0); + + cfg = (void *) gst_avdtp_find_caps(sink, BT_A2DP_SBC_SINK); + name = gst_structure_get_name(structure); + + if (!(IS_SBC(name))) { + GST_ERROR_OBJECT(sink, "Unexpected format %s, " + "was expecting sbc", name); + return FALSE; + } + + value = gst_structure_get_value(structure, "rate"); + rate = g_value_get_int(value); + if (rate == 44100) + cfg->frequency = BT_SBC_SAMPLING_FREQ_44100; + else if (rate == 48000) + cfg->frequency = BT_SBC_SAMPLING_FREQ_48000; + else if (rate == 32000) + cfg->frequency = BT_SBC_SAMPLING_FREQ_32000; + else if (rate == 16000) + cfg->frequency = BT_SBC_SAMPLING_FREQ_16000; + else { + GST_ERROR_OBJECT(sink, "Invalid rate while setting caps"); + return FALSE; + } + + value = gst_structure_get_value(structure, "mode"); + pref = g_value_get_string(value); + if (strcmp(pref, "mono") == 0) + cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + else if (strcmp(pref, "dual") == 0) + cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (strcmp(pref, "stereo") == 0) + cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (strcmp(pref, "joint") == 0) + cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else { + GST_ERROR_OBJECT(sink, "Invalid mode %s", pref); + return FALSE; + } + + value = gst_structure_get_value(structure, "allocation"); + pref = g_value_get_string(value); + if (strcmp(pref, "loudness") == 0) + cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (strcmp(pref, "snr") == 0) + cfg->allocation_method = BT_A2DP_ALLOCATION_SNR; + else { + GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref); + return FALSE; + } + + value = gst_structure_get_value(structure, "subbands"); + subbands = g_value_get_int(value); + if (subbands == 8) + cfg->subbands = BT_A2DP_SUBBANDS_8; + else if (subbands == 4) + cfg->subbands = BT_A2DP_SUBBANDS_4; + else { + GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands); + return FALSE; + } + + value = gst_structure_get_value(structure, "blocks"); + blocks = g_value_get_int(value); + if (blocks == 16) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (blocks == 12) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (blocks == 8) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (blocks == 4) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks); + return FALSE; + } + + value = gst_structure_get_value(structure, "bitpool"); + cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value); + + memcpy(pkt, cfg, sizeof(*pkt)); + + return TRUE; +} + +static gboolean gst_avdtp_sink_conf_recv_stream_fd( + GstAvdtpSink *self) +{ + struct bluetooth_data *data = self->data; + gint ret; + GIOError err; + GError *gerr = NULL; + GIOStatus status; + GIOFlags flags; + gsize read; + + ret = gst_avdtp_sink_bluetooth_recvmsg_fd(self); + if (ret < 0) + return FALSE; + + if (!self->stream) { + GST_ERROR_OBJECT(self, "Error while configuring device: " + "could not acquire audio socket"); + return FALSE; + } + + /* set stream socket to nonblock */ + GST_LOG_OBJECT(self, "setting stream socket to nonblock"); + flags = g_io_channel_get_flags(self->stream); + flags |= G_IO_FLAG_NONBLOCK; + status = g_io_channel_set_flags(self->stream, flags, &gerr); + if (status != G_IO_STATUS_NORMAL) { + if (gerr) + GST_WARNING_OBJECT(self, "Error while " + "setting server socket to nonblock: " + "%s", gerr->message); + else + GST_WARNING_OBJECT(self, "Error while " + "setting server " + "socket to nonblock"); + } + + /* It is possible there is some outstanding + data in the pipe - we have to empty it */ + GST_LOG_OBJECT(self, "emptying stream pipe"); + while (1) { + err = g_io_channel_read(self->stream, data->buffer, + (gsize) data->link_mtu, + &read); + if (err != G_IO_ERROR_NONE || read <= 0) + break; + } + + /* set stream socket to block */ + GST_LOG_OBJECT(self, "setting stream socket to block"); + flags = g_io_channel_get_flags(self->stream); + flags &= ~G_IO_FLAG_NONBLOCK; + status = g_io_channel_set_flags(self->stream, flags, &gerr); + if (status != G_IO_STATUS_NORMAL) { + if (gerr) + GST_WARNING_OBJECT(self, "Error while " + "setting server socket to block:" + "%s", gerr->message); + else + GST_WARNING_OBJECT(self, "Error while " + "setting server " + "socket to block"); + } + + memset(data->buffer, 0, sizeof(data->buffer)); + + return TRUE; +} + +static gboolean server_callback(GIOChannel *chan, + GIOCondition cond, gpointer data) +{ + if (cond & G_IO_HUP || cond & G_IO_NVAL) + return FALSE; + else if (cond & G_IO_ERR) + GST_WARNING_OBJECT(GST_AVDTP_SINK(data), + "Untreated callback G_IO_ERR"); + + return TRUE; +} + +static GstStructure *gst_avdtp_sink_parse_sbc_caps( + GstAvdtpSink *self, sbc_capabilities_t *sbc) +{ + GstStructure *structure; + GValue *value; + GValue *list; + gboolean mono, stereo; + + structure = gst_structure_empty_new("audio/x-sbc"); + value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING); + + /* mode */ + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { + g_value_set_static_string(value, "mono"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) { + g_value_set_static_string(value, "stereo"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) { + g_value_set_static_string(value, "dual"); + gst_value_list_prepend_value(list, value); + } + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) { + g_value_set_static_string(value, "joint"); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "mode", list); + g_free(list); + list = NULL; + } + + /* subbands */ + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + value = g_value_init(value, G_TYPE_INT); + if (sbc->subbands & BT_A2DP_SUBBANDS_4) { + g_value_set_int(value, 4); + gst_value_list_prepend_value(list, value); + } + if (sbc->subbands & BT_A2DP_SUBBANDS_8) { + g_value_set_int(value, 8); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "subbands", list); + g_free(list); + list = NULL; + } + + /* blocks */ + value = g_value_init(value, G_TYPE_INT); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) { + g_value_set_int(value, 16); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) { + g_value_set_int(value, 12); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) { + g_value_set_int(value, 8); + gst_value_list_prepend_value(list, value); + } + if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) { + g_value_set_int(value, 4); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "blocks", list); + g_free(list); + list = NULL; + } + + /* allocation */ + g_value_init(value, G_TYPE_STRING); + list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST); + if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) { + g_value_set_static_string(value, "loudness"); + gst_value_list_prepend_value(list, value); + } + if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) { + g_value_set_static_string(value, "snr"); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "allocation", list); + g_free(list); + list = NULL; + } + + /* rate */ + g_value_init(value, G_TYPE_INT); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_48000) { + g_value_set_int(value, 48000); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) { + g_value_set_int(value, 44100); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) { + g_value_set_int(value, 32000); + gst_value_list_prepend_value(list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_16000) { + g_value_set_int(value, 16000); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "rate", list); + g_free(list); + list = NULL; + } + + /* bitpool */ + value = g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, + MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL), + MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL)); + gst_structure_set_value(structure, "bitpool", value); + g_value_unset(value); + + /* channels */ + mono = FALSE; + stereo = FALSE; + if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + mono = TRUE; + if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || + (sbc->channel_mode & + BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || + (sbc->channel_mode & + BT_A2DP_CHANNEL_MODE_JOINT_STEREO)) + stereo = TRUE; + + if (mono && stereo) { + g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, 1, 2); + } else { + g_value_init(value, G_TYPE_INT); + if (mono) + g_value_set_int(value, 1); + else if (stereo) + g_value_set_int(value, 2); + else { + GST_ERROR_OBJECT(self, + "Unexpected number of channels"); + g_value_set_int(value, 0); + } + } + + gst_structure_set_value(structure, "channels", value); + g_free(value); + + return structure; +} + +static GstStructure *gst_avdtp_sink_parse_mpeg_caps( + GstAvdtpSink *self, mpeg_capabilities_t *mpeg) +{ + GstStructure *structure; + GValue *value; + GValue *list; + gboolean valid_layer = FALSE; + gboolean mono, stereo; + + if (!mpeg) + return NULL; + + GST_LOG_OBJECT(self, "parsing mpeg caps"); + + structure = gst_structure_empty_new("audio/mpeg"); + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_INT); + + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + g_value_set_int(value, 1); + gst_value_list_prepend_value(list, value); + g_value_set_int(value, 2); + gst_value_list_prepend_value(list, value); + gst_structure_set_value(structure, "mpegversion", list); + g_free(list); + + /* layer */ + GST_LOG_OBJECT(self, "setting mpeg layer"); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (mpeg->layer & BT_MPEG_LAYER_1) { + g_value_set_int(value, 1); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (mpeg->layer & BT_MPEG_LAYER_2) { + g_value_set_int(value, 2); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (mpeg->layer & BT_MPEG_LAYER_3) { + g_value_set_int(value, 3); + gst_value_list_prepend_value(list, value); + valid_layer = TRUE; + } + if (list) { + gst_structure_set_value(structure, "layer", list); + g_free(list); + list = NULL; + } + + if (!valid_layer) { + gst_structure_free(structure); + g_free(value); + return NULL; + } + + /* rate */ + GST_LOG_OBJECT(self, "setting mpeg rate"); + list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) { + g_value_set_int(value, 48000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) { + g_value_set_int(value, 44100); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) { + g_value_set_int(value, 32000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) { + g_value_set_int(value, 24000); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) { + g_value_set_int(value, 22050); + gst_value_list_prepend_value(list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_16000) { + g_value_set_int(value, 16000); + gst_value_list_prepend_value(list, value); + } + g_value_unset(value); + if (list) { + gst_structure_set_value(structure, "rate", list); + g_free(list); + list = NULL; + } + + /* channels */ + GST_LOG_OBJECT(self, "setting mpeg channels"); + mono = FALSE; + stereo = FALSE; + if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + mono = TRUE; + if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || + (mpeg->channel_mode & + BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || + (mpeg->channel_mode & + BT_A2DP_CHANNEL_MODE_JOINT_STEREO)) + stereo = TRUE; + + if (mono && stereo) { + g_value_init(value, GST_TYPE_INT_RANGE); + gst_value_set_int_range(value, 1, 2); + } else { + g_value_init(value, G_TYPE_INT); + if (mono) + g_value_set_int(value, 1); + else if (stereo) + g_value_set_int(value, 2); + else { + GST_ERROR_OBJECT(self, + "Unexpected number of channels"); + g_value_set_int(value, 0); + } + } + gst_structure_set_value(structure, "channels", value); + g_free(value); + + return structure; +} + +static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self) +{ + sbc_capabilities_t *sbc; + mpeg_capabilities_t *mpeg; + GstStructure *sbc_structure; + GstStructure *mpeg_structure; + gchar *tmp; + + GST_LOG_OBJECT(self, "updating device caps"); + + sbc = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK); + mpeg = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK); + + sbc_structure = gst_avdtp_sink_parse_sbc_caps(self, sbc); + mpeg_structure = gst_avdtp_sink_parse_mpeg_caps(self, mpeg); + + if (self->dev_caps != NULL) + gst_caps_unref(self->dev_caps); + self->dev_caps = gst_caps_new_full(sbc_structure, NULL); + if (mpeg_structure != NULL) + gst_caps_append_structure(self->dev_caps, mpeg_structure); + + tmp = gst_caps_to_string(self->dev_caps); + GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp); + g_free(tmp); + + return TRUE; +} + +static gboolean gst_avdtp_sink_get_capabilities(GstAvdtpSink *self) +{ + gchar *buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_req *req = (void *) buf; + struct bt_get_capabilities_rsp *rsp = (void *) buf; + GIOError io_error; + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + + req->h.type = BT_REQUEST; + req->h.name = BT_GET_CAPABILITIES; + req->h.length = sizeof(*req); + + if (self->device == NULL) + return FALSE; + strncpy(req->destination, self->device, 18); + if (self->autoconnect) + req->flags |= BT_FLAG_AUTOCONNECT; + + io_error = gst_avdtp_sink_audioservice_send(self, &req->h); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error while asking device caps"); + return FALSE; + } + + rsp->h.length = 0; + io_error = gst_avdtp_sink_audioservice_expect(self, + &rsp->h, BT_GET_CAPABILITIES); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error while getting device caps"); + return FALSE; + } + + self->data->caps = g_malloc0(rsp->h.length); + memcpy(self->data->caps, rsp, rsp->h.length); + if (!gst_avdtp_sink_update_caps(self)) { + GST_WARNING_OBJECT(self, "failed to update capabilities"); + return FALSE; + } + + return TRUE; +} + +static gint gst_avdtp_sink_get_channel_mode(const gchar *mode) +{ + if (strcmp(mode, "stereo") == 0) + return BT_A2DP_CHANNEL_MODE_STEREO; + else if (strcmp(mode, "joint-stereo") == 0) + return BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (strcmp(mode, "dual-channel") == 0) + return BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (strcmp(mode, "mono") == 0) + return BT_A2DP_CHANNEL_MODE_MONO; + else + return -1; +} + +static void gst_avdtp_sink_tag(const GstTagList *taglist, + const gchar *tag, gpointer user_data) +{ + gboolean crc; + gchar *channel_mode = NULL; + GstAvdtpSink *self = GST_AVDTP_SINK(user_data); + + if (strcmp(tag, "has-crc") == 0) { + + if (!gst_tag_list_get_boolean(taglist, tag, &crc)) { + GST_WARNING_OBJECT(self, "failed to get crc tag"); + return; + } + + gst_avdtp_sink_set_crc(self, crc); + + } else if (strcmp(tag, "channel-mode") == 0) { + + if (!gst_tag_list_get_string(taglist, tag, &channel_mode)) { + GST_WARNING_OBJECT(self, + "failed to get channel-mode tag"); + return; + } + + self->channel_mode = gst_avdtp_sink_get_channel_mode( + channel_mode); + if (self->channel_mode == -1) + GST_WARNING_OBJECT(self, "Received invalid channel " + "mode: %s", channel_mode); + g_free(channel_mode); + + } else + GST_DEBUG_OBJECT(self, "received unused tag: %s", tag); +} + +static gboolean gst_avdtp_sink_event(GstBaseSink *basesink, + GstEvent *event) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + GstTagList *taglist = NULL; + + if (GST_EVENT_TYPE(event) == GST_EVENT_TAG) { + /* we check the tags, mp3 has tags that are importants and + * are outside caps */ + gst_event_parse_tag(event, &taglist); + gst_tag_list_foreach(taglist, gst_avdtp_sink_tag, self); + } + + return TRUE; +} + +static gboolean gst_avdtp_sink_start(GstBaseSink *basesink) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + gint sk; + gint err; + + GST_INFO_OBJECT(self, "start"); + + self->watch_id = 0; + + sk = bt_audio_service_open(); + if (sk <= 0) { + err = errno; + GST_ERROR_OBJECT(self, "Cannot open connection to bt " + "audio service: %s %d", strerror(err), err); + goto failed; + } + + self->server = g_io_channel_unix_new(sk); + self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR | + G_IO_NVAL, server_callback, self); + + self->data = g_new0(struct bluetooth_data, 1); + + self->stream = NULL; + self->stream_caps = NULL; + self->mp3_using_crc = -1; + self->channel_mode = -1; + + if (!gst_avdtp_sink_get_capabilities(self)) { + GST_ERROR_OBJECT(self, "failed to get capabilities " + "from device"); + goto failed; + } + + return TRUE; + +failed: + bt_audio_service_close(sk); + return FALSE; +} + +static gboolean gst_avdtp_sink_stream_start(GstAvdtpSink *self) +{ + gchar buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_req *req = (void *) buf; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + GIOError io_error; + + memset(req, 0, sizeof(buf)); + req->h.type = BT_REQUEST; + req->h.name = BT_START_STREAM; + req->h.length = sizeof(*req); + + io_error = gst_avdtp_sink_audioservice_send(self, &req->h); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error ocurred while sending " + "start packet"); + return FALSE; + } + + rsp->h.length = sizeof(*rsp); + io_error = gst_avdtp_sink_audioservice_expect(self, + &rsp->h, BT_START_STREAM); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error while stream " + "start confirmation"); + return FALSE; + } + + ind->h.length = sizeof(*ind); + io_error = gst_avdtp_sink_audioservice_expect(self, &ind->h, + BT_NEW_STREAM); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error while receiving " + "stream filedescriptor"); + return FALSE; + } + + if (!gst_avdtp_sink_conf_recv_stream_fd(self)) + return FALSE; + + return TRUE; +} + +static gboolean gst_avdtp_sink_init_mp3_pkt_conf( + GstAvdtpSink *self, GstCaps *caps, + mpeg_capabilities_t *pkt) +{ + const GValue *value = NULL; + gint rate, layer; + const gchar *name; + GstStructure *structure = gst_caps_get_structure(caps, 0); + + name = gst_structure_get_name(structure); + + if (!(IS_MPEG_AUDIO(name))) { + GST_ERROR_OBJECT(self, "Unexpected format %s, " + "was expecting mp3", name); + return FALSE; + } + + /* layer */ + value = gst_structure_get_value(structure, "layer"); + layer = g_value_get_int(value); + if (layer == 1) + pkt->layer = BT_MPEG_LAYER_1; + else if (layer == 2) + pkt->layer = BT_MPEG_LAYER_2; + else if (layer == 3) + pkt->layer = BT_MPEG_LAYER_3; + else { + GST_ERROR_OBJECT(self, "Unexpected layer: %d", layer); + return FALSE; + } + + /* crc */ + if (self->mp3_using_crc != -1) + pkt->crc = self->mp3_using_crc; + else { + GST_ERROR_OBJECT(self, "No info about crc was received, " + " can't proceed"); + return FALSE; + } + + /* channel mode */ + if (self->channel_mode != -1) + pkt->channel_mode = self->channel_mode; + else { + GST_ERROR_OBJECT(self, "No info about channel mode " + "received, can't proceed"); + return FALSE; + } + + /* mpf - we will only use the mandatory one */ + pkt->mpf = 0; + + value = gst_structure_get_value(structure, "rate"); + rate = g_value_get_int(value); + if (rate == 44100) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_44100; + else if (rate == 48000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_48000; + else if (rate == 32000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_32000; + else if (rate == 24000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_24000; + else if (rate == 22050) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_22050; + else if (rate == 16000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_16000; + else { + GST_ERROR_OBJECT(self, "Invalid rate while setting caps"); + return FALSE; + } + + /* vbr - we always say its vbr, we don't have how to know it */ + pkt->bitrate = 0x8000; + + return TRUE; +} + +static gboolean gst_avdtp_sink_configure(GstAvdtpSink *self, + GstCaps *caps) +{ + gchar buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_req *open_req = (void *) buf; + struct bt_open_rsp *open_rsp = (void *) buf; + struct bt_set_configuration_req *req = (void *) buf; + struct bt_set_configuration_rsp *rsp = (void *) buf; + gboolean ret; + GIOError io_error; + gchar *temp; + GstStructure *structure; + codec_capabilities_t *codec = NULL; + + temp = gst_caps_to_string(caps); + GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp); + g_free(temp); + + structure = gst_caps_get_structure(caps, 0); + + if (gst_structure_has_name(structure, "audio/x-sbc")) + codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK); + else if (gst_structure_has_name(structure, "audio/mpeg")) + codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK); + + if (codec == NULL) { + GST_ERROR_OBJECT(self, "Couldn't parse caps " + "to packet configuration"); + return FALSE; + } + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + open_req->h.type = BT_REQUEST; + open_req->h.name = BT_OPEN; + open_req->h.length = sizeof(*open_req); + + strncpy(open_req->destination, self->device, 18); + open_req->seid = codec->seid; + open_req->lock = BT_WRITE_LOCK; + + io_error = gst_avdtp_sink_audioservice_send(self, &open_req->h); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error ocurred while sending " + "open packet"); + return FALSE; + } + + open_rsp->h.length = sizeof(*open_rsp); + io_error = gst_avdtp_sink_audioservice_expect(self, + &open_rsp->h, BT_OPEN); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error while receiving device " + "confirmation"); + return FALSE; + } + + memset(req, 0, sizeof(buf)); + req->h.type = BT_REQUEST; + req->h.name = BT_SET_CONFIGURATION; + req->h.length = sizeof(*req); + + if (codec->type == BT_A2DP_SBC_SINK) + ret = gst_avdtp_sink_init_sbc_pkt_conf(self, caps, + (void *) &req->codec); + else + ret = gst_avdtp_sink_init_mp3_pkt_conf(self, caps, + (void *) &req->codec); + + if (!ret) { + GST_ERROR_OBJECT(self, "Couldn't parse caps " + "to packet configuration"); + return FALSE; + } + + req->h.length += req->codec.length - sizeof(req->codec); + io_error = gst_avdtp_sink_audioservice_send(self, &req->h); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error ocurred while sending " + "configurarion packet"); + return FALSE; + } + + rsp->h.length = sizeof(*rsp); + io_error = gst_avdtp_sink_audioservice_expect(self, + &rsp->h, BT_SET_CONFIGURATION); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error while receiving device " + "confirmation"); + return FALSE; + } + + self->data->link_mtu = rsp->link_mtu; + + return TRUE; +} + +static GstFlowReturn gst_avdtp_sink_preroll(GstBaseSink *basesink, + GstBuffer *buffer) +{ + GstAvdtpSink *sink = GST_AVDTP_SINK(basesink); + gboolean ret; + + GST_AVDTP_SINK_MUTEX_LOCK(sink); + + ret = gst_avdtp_sink_stream_start(sink); + + GST_AVDTP_SINK_MUTEX_UNLOCK(sink); + + if (!ret) + return GST_FLOW_ERROR; + + return GST_FLOW_OK; +} + +static GstFlowReturn gst_avdtp_sink_render(GstBaseSink *basesink, + GstBuffer *buffer) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + gsize ret; + GIOError err; + + err = g_io_channel_write(self->stream, + (gchar *) GST_BUFFER_DATA(buffer), + (gsize) (GST_BUFFER_SIZE(buffer)), &ret); + + if (err != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error while writting to socket: %d %s", + errno, strerror(errno)); + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; +} + +static gboolean gst_avdtp_sink_unlock(GstBaseSink *basesink) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + + if (self->stream != NULL) + g_io_channel_flush(self->stream, NULL); + + return TRUE; +} + +static GstFlowReturn gst_avdtp_sink_buffer_alloc(GstBaseSink *basesink, + guint64 offset, guint size, GstCaps *caps, + GstBuffer **buf) +{ + GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + + *buf = gst_buffer_new_and_alloc(size); + if (!(*buf)) { + GST_ERROR_OBJECT(self, "buffer allocation failed"); + return GST_FLOW_ERROR; + } + + gst_buffer_set_caps(*buf, caps); + + GST_BUFFER_OFFSET(*buf) = offset; + + return GST_FLOW_OK; +} + +static void gst_avdtp_sink_class_init(GstAvdtpSinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + object_class->finalize = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_finalize); + object_class->set_property = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_get_property); + + basesink_class->start = GST_DEBUG_FUNCPTR(gst_avdtp_sink_start); + basesink_class->stop = GST_DEBUG_FUNCPTR(gst_avdtp_sink_stop); + basesink_class->render = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_render); + basesink_class->preroll = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_preroll); + basesink_class->unlock = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_unlock); + basesink_class->event = GST_DEBUG_FUNCPTR( + gst_avdtp_sink_event); + + basesink_class->buffer_alloc = + GST_DEBUG_FUNCPTR(gst_avdtp_sink_buffer_alloc); + + g_object_class_install_property(object_class, PROP_DEVICE, + g_param_spec_string("device", "Device", + "Bluetooth remote device address", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_AUTOCONNECT, + g_param_spec_boolean("auto-connect", + "Auto-connect", + "Automatically attempt to connect " + "to device", DEFAULT_AUTOCONNECT, + G_PARAM_READWRITE)); + + GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0, + "A2DP headset sink element"); +} + +static void gst_avdtp_sink_init(GstAvdtpSink *self, + GstAvdtpSinkClass *klass) +{ + self->device = NULL; + self->data = NULL; + + self->stream = NULL; + + self->dev_caps = NULL; + + self->autoconnect = DEFAULT_AUTOCONNECT; + + self->sink_lock = g_mutex_new(); + + /* FIXME this is for not synchronizing with clock, should be tested + * with devices to see the behaviour + gst_base_sink_set_sync(GST_BASE_SINK(self), FALSE); + */ +} + +static GIOError gst_avdtp_sink_audioservice_send( + GstAvdtpSink *self, + const bt_audio_msg_header_t *msg) +{ + GIOError error; + gsize written; + const char *type, *name; + uint16_t length; + + length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE; + + error = g_io_channel_write(self->server, (const gchar *) msg, length, + &written); + if (error != G_IO_ERROR_NONE) + GST_ERROR_OBJECT(self, "Error sending data to audio service:" + " %s(%d)", strerror(errno), errno); + + type = bt_audio_strtype(msg->type); + name = bt_audio_strname(msg->name); + + GST_DEBUG_OBJECT(self, "sent: %s -> %s", type, name); + + return error; +} + +static GIOError gst_avdtp_sink_audioservice_recv( + GstAvdtpSink *self, + bt_audio_msg_header_t *inmsg) +{ + GIOError status; + gsize bytes_read; + const char *type, *name; + uint16_t length; + + length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE; + + status = g_io_channel_read(self->server, (gchar *) inmsg, length, + &bytes_read); + if (status != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(self, "Error receiving data from " + "audio service"); + return status; + } + + type = bt_audio_strtype(inmsg->type); + if (!type) { + status = G_IO_ERROR_INVAL; + GST_ERROR_OBJECT(self, "Bogus message type %d " + "received from audio service", + inmsg->type); + } + + name = bt_audio_strname(inmsg->name); + if (!name) { + status = G_IO_ERROR_INVAL; + GST_ERROR_OBJECT(self, "Bogus message name %d " + "received from audio service", + inmsg->name); + } + + if (inmsg->type == BT_ERROR) { + bt_audio_error_t *err = (void *) inmsg; + status = G_IO_ERROR_INVAL; + GST_ERROR_OBJECT(self, "%s failed : " + "%s(%d)", + name, + strerror(err->posix_errno), + err->posix_errno); + } + + GST_DEBUG_OBJECT(self, "received: %s <- %s", type, name); + + return status; +} + +static GIOError gst_avdtp_sink_audioservice_expect( + GstAvdtpSink *self, bt_audio_msg_header_t *outmsg, + guint8 expected_name) +{ + GIOError status; + + status = gst_avdtp_sink_audioservice_recv(self, outmsg); + if (status != G_IO_ERROR_NONE) + return status; + + if (outmsg->name != expected_name) + status = G_IO_ERROR_INVAL; + + return status; +} + +gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "avdtpsink", GST_RANK_NONE, + GST_TYPE_AVDTP_SINK); +} + + +/* public functions */ +GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink) +{ + if (sink->dev_caps == NULL) + return NULL; + + return gst_caps_copy(sink->dev_caps); +} + +gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *self, + GstCaps *caps) +{ + gboolean ret; + + GST_DEBUG_OBJECT(self, "setting device caps"); + GST_AVDTP_SINK_MUTEX_LOCK(self); + ret = gst_avdtp_sink_configure(self, caps); + + if (self->stream_caps) + gst_caps_unref(self->stream_caps); + self->stream_caps = gst_caps_ref(caps); + + GST_AVDTP_SINK_MUTEX_UNLOCK(self); + + return ret; +} + +guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink) +{ + return sink->data->link_mtu; +} + +void gst_avdtp_sink_set_device(GstAvdtpSink *self, const gchar *dev) +{ + if (self->device != NULL) + g_free(self->device); + + GST_LOG_OBJECT(self, "Setting device: %s", dev); + self->device = g_strdup(dev); +} + +gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self) +{ + return g_strdup(self->device); +} + +void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc) +{ + gint new_crc; + + new_crc = crc ? CRC_PROTECTED : CRC_UNPROTECTED; + + /* test if we already received a different crc */ + if (self->mp3_using_crc != -1 && new_crc != self->mp3_using_crc) { + GST_WARNING_OBJECT(self, "crc changed during stream"); + return; + } + self->mp3_using_crc = new_crc; + +} + +void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self, + const gchar *mode) +{ + gint new_mode; + + new_mode = gst_avdtp_sink_get_channel_mode(mode); + + if (self->channel_mode != -1 && new_mode != self->channel_mode) { + GST_WARNING_OBJECT(self, "channel mode changed during stream"); + return; + } + + self->channel_mode = new_mode; + if (self->channel_mode == -1) + GST_WARNING_OBJECT(self, "Received invalid channel " + "mode: %s", mode); +} diff --git a/audio/gstavdtpsink.h b/audio/gstavdtpsink.h new file mode 100644 index 000000000..b4bee39f2 --- /dev/null +++ b/audio/gstavdtpsink.h @@ -0,0 +1,101 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GST_AVDTP_SINK_H +#define __GST_AVDTP_SINK_H + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_AVDTP_SINK \ + (gst_avdtp_sink_get_type()) +#define GST_AVDTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVDTP_SINK,\ + GstAvdtpSink)) +#define GST_AVDTP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVDTP_SINK,\ + GstAvdtpSinkClass)) +#define GST_IS_AVDTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVDTP_SINK)) +#define GST_IS_AVDTP_SINK_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVDTP_SINK)) + +typedef struct _GstAvdtpSink GstAvdtpSink; +typedef struct _GstAvdtpSinkClass GstAvdtpSinkClass; + +struct bluetooth_data; + +struct _GstAvdtpSink { + GstBaseSink sink; + + gchar *device; + GIOChannel *stream; + + struct bluetooth_data *data; + gboolean autoconnect; + GIOChannel *server; + + /* mp3 stream data (outside caps data)*/ + gint mp3_using_crc; + gint channel_mode; + + /* stream connection data */ + GstCaps *stream_caps; + + GstCaps *dev_caps; + + GMutex *sink_lock; + + guint watch_id; +}; + +struct _GstAvdtpSinkClass { + GstBaseSinkClass parent_class; +}; + +//GType gst_avdtp_sink_get_type(void); + +GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink); +gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *sink, + GstCaps *caps); + +guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink); + +void gst_avdtp_sink_set_device(GstAvdtpSink *sink, + const gchar* device); + +gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink); + +gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin); + +void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc); + +void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self, + const gchar *mode); + + +G_END_DECLS + +#endif /* __GST_AVDTP_SINK_H */ diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c new file mode 100644 index 000000000..7775b48d8 --- /dev/null +++ b/audio/gstbluetooth.c @@ -0,0 +1,106 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "gstsbcutil.h" +#include + +#include "gstsbcenc.h" +#include "gstsbcdec.h" +#include "gstsbcparse.h" +#include "gstavdtpsink.h" +#include "gsta2dpsink.h" +#include "gstrtpsbcpay.h" + +static GstStaticCaps sbc_caps = GST_STATIC_CAPS("audio/x-sbc"); + +#define SBC_CAPS (gst_static_caps_get(&sbc_caps)) + +static void sbc_typefind(GstTypeFind *tf, gpointer ignore) +{ + GstCaps *caps; + guint8 *aux; + sbc_t sbc; + guint8 *data = gst_type_find_peek(tf, 0, 32); + + if (sbc_init(&sbc, 0) < 0) + return; + + if (data == NULL || *data != 0x9c) /* SBC syncword */ + return; + + aux = g_new(guint8, 32); + memcpy(aux, data, 32); + sbc_parse(&sbc, aux, 32); + g_free(aux); + caps = gst_sbc_parse_caps_from_sbc(&sbc); + sbc_finish(&sbc); + + gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, caps); + gst_caps_unref(caps); +} + +static gchar *sbc_exts[] = { "sbc", NULL }; + +static gboolean plugin_init(GstPlugin *plugin) +{ + GST_INFO("Bluetooth plugin %s", VERSION); + + if (gst_type_find_register(plugin, "sbc", + GST_RANK_PRIMARY, sbc_typefind, sbc_exts, + SBC_CAPS, NULL, NULL) == FALSE) + return FALSE; + + if (!gst_sbc_enc_plugin_init(plugin)) + return FALSE; + + if (!gst_sbc_dec_plugin_init(plugin)) + return FALSE; + + if (!gst_sbc_parse_plugin_init(plugin)) + return FALSE; + + if (!gst_avdtp_sink_plugin_init(plugin)) + return FALSE; + + if (!gst_a2dp_sink_plugin_init(plugin)) + return FALSE; + + if (!gst_rtp_sbc_pay_plugin_init(plugin)) + return FALSE; + + return TRUE; +} + +extern GstPluginDesc gst_plugin_desc __attribute__ ((visibility("default"))); + +GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, + "bluetooth", "Bluetooth plugin library", + plugin_init, VERSION, "LGPL", "BlueZ", "http://www.bluez.org/") diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c new file mode 100644 index 000000000..78ffb2a0f --- /dev/null +++ b/audio/gstrtpsbcpay.c @@ -0,0 +1,351 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstrtpsbcpay.h" +#include +#include + +#define RTP_SBC_PAYLOAD_HEADER_SIZE 1 +#define DEFAULT_MIN_FRAMES 0 +#define RTP_SBC_HEADER_TOTAL (12 + RTP_SBC_PAYLOAD_HEADER_SIZE) + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct rtp_payload { + guint8 frame_count:4; + guint8 rfa0:1; + guint8 is_last_fragment:1; + guint8 is_first_fragment:1; + guint8 is_fragmented:1; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct rtp_payload { + guint8 is_fragmented:1; + guint8 is_first_fragment:1; + guint8 is_last_fragment:1; + guint8 rfa0:1; + guint8 frame_count:4; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +enum { + PROP_0, + PROP_MIN_FRAMES +}; + +GST_DEBUG_CATEGORY_STATIC(gst_rtp_sbc_pay_debug); +#define GST_CAT_DEFAULT gst_rtp_sbc_pay_debug + +GST_BOILERPLATE(GstRtpSBCPay, gst_rtp_sbc_pay, GstBaseRTPPayload, + GST_TYPE_BASE_RTP_PAYLOAD); + +static const GstElementDetails gst_rtp_sbc_pay_details = + GST_ELEMENT_DETAILS("RTP packet payloader", + "Codec/Payloader/Network", + "Payload SBC audio as RTP packets", + "Thiago Sousa Santos " + ""); + +static GstStaticPadTemplate gst_rtp_sbc_pay_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " + "blocks = (int) { 4, 8, 12, 16 }, " + "subbands = (int) { 4, 8 }, " + "allocation = (string) { \"snr\", \"loudness\" }, " + "bitpool = (int) [ 2, 64 ]") + ); + +static GstStaticPadTemplate gst_rtp_sbc_pay_src_factory = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "application/x-rtp, " + "media = (string) \"audio\"," + "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) { 16000, 32000, 44100, 48000 }," + "encoding-name = (string) \"SBC\"") + ); + +static void gst_rtp_sbc_pay_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void gst_rtp_sbc_pay_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); + +static gint gst_rtp_sbc_pay_get_frame_len(gint subbands, gint channels, + gint blocks, gint bitpool, const gchar *channel_mode) +{ + gint len; + gint join; + + len = 4 + (4 * subbands * channels)/8; + + if (strcmp(channel_mode, "mono") == 0 || + strcmp(channel_mode, "dual") == 0) + len += ((blocks * channels * bitpool) + 7) / 8; + else { + join = strcmp(channel_mode, "joint") == 0 ? 1 : 0; + len += ((join * subbands + blocks * bitpool) + 7) / 8; + } + + return len; +} + +static gboolean gst_rtp_sbc_pay_set_caps(GstBaseRTPPayload *payload, + GstCaps *caps) +{ + GstRtpSBCPay *sbcpay; + gint rate, subbands, channels, blocks, bitpool; + gint frame_len; + const gchar *channel_mode; + GstStructure *structure; + + sbcpay = GST_RTP_SBC_PAY(payload); + + structure = gst_caps_get_structure(caps, 0); + if (!gst_structure_get_int(structure, "rate", &rate)) + return FALSE; + if (!gst_structure_get_int(structure, "channels", &channels)) + return FALSE; + if (!gst_structure_get_int(structure, "blocks", &blocks)) + return FALSE; + if (!gst_structure_get_int(structure, "bitpool", &bitpool)) + return FALSE; + if (!gst_structure_get_int(structure, "subbands", &subbands)) + return FALSE; + + channel_mode = gst_structure_get_string(structure, "mode"); + if (!channel_mode) + return FALSE; + + frame_len = gst_rtp_sbc_pay_get_frame_len(subbands, channels, blocks, + bitpool, channel_mode); + + sbcpay->frame_length = frame_len; + + gst_basertppayload_set_options(payload, "audio", TRUE, "SBC", rate); + + GST_DEBUG_OBJECT(payload, "calculated frame length: %d ", frame_len); + + return gst_basertppayload_set_outcaps(payload, NULL); +} + +static GstFlowReturn gst_rtp_sbc_pay_flush_buffers(GstRtpSBCPay *sbcpay) +{ + guint available; + guint max_payload; + GstBuffer *outbuf; + guint8 *payload_data; + guint frame_count; + guint payload_length; + struct rtp_payload *payload; + + if (sbcpay->frame_length == 0) { + GST_ERROR_OBJECT(sbcpay, "Frame length is 0"); + return GST_FLOW_ERROR; + } + + available = gst_adapter_available(sbcpay->adapter); + + max_payload = gst_rtp_buffer_calc_payload_len( + GST_BASE_RTP_PAYLOAD_MTU(sbcpay) - RTP_SBC_PAYLOAD_HEADER_SIZE, + 0, 0); + + max_payload = MIN(max_payload, available); + frame_count = max_payload / sbcpay->frame_length; + payload_length = frame_count * sbcpay->frame_length; + if (payload_length == 0) /* Nothing to send */ + return GST_FLOW_OK; + + outbuf = gst_rtp_buffer_new_allocate(payload_length + + RTP_SBC_PAYLOAD_HEADER_SIZE, 0, 0); + + gst_rtp_buffer_set_payload_type(outbuf, + GST_BASE_RTP_PAYLOAD_PT(sbcpay)); + + payload_data = gst_rtp_buffer_get_payload(outbuf); + payload = (struct rtp_payload *) payload_data; + memset(payload, 0, sizeof(struct rtp_payload)); + payload->frame_count = frame_count; + + gst_adapter_copy(sbcpay->adapter, payload_data + + RTP_SBC_PAYLOAD_HEADER_SIZE, 0, payload_length); + gst_adapter_flush(sbcpay->adapter, payload_length); + + GST_BUFFER_TIMESTAMP(outbuf) = sbcpay->timestamp; + GST_DEBUG_OBJECT(sbcpay, "Pushing %d bytes", payload_length); + + return gst_basertppayload_push(GST_BASE_RTP_PAYLOAD(sbcpay), outbuf); +} + +static GstFlowReturn gst_rtp_sbc_pay_handle_buffer(GstBaseRTPPayload *payload, + GstBuffer *buffer) +{ + GstRtpSBCPay *sbcpay; + guint available; + + /* FIXME check for negotiation */ + + sbcpay = GST_RTP_SBC_PAY(payload); + sbcpay->timestamp = GST_BUFFER_TIMESTAMP(buffer); + + gst_adapter_push(sbcpay->adapter, buffer); + + available = gst_adapter_available(sbcpay->adapter); + if (available + RTP_SBC_HEADER_TOTAL >= + GST_BASE_RTP_PAYLOAD_MTU(sbcpay) || + (available > + (sbcpay->min_frames * sbcpay->frame_length))) + return gst_rtp_sbc_pay_flush_buffers(sbcpay); + + return GST_FLOW_OK; +} + +static gboolean gst_rtp_sbc_pay_handle_event(GstPad *pad, + GstEvent *event) +{ + GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(GST_PAD_PARENT(pad)); + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_EOS: + gst_rtp_sbc_pay_flush_buffers(sbcpay); + break; + default: + break; + } + + return FALSE; +} + +static void gst_rtp_sbc_pay_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&gst_rtp_sbc_pay_sink_factory)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&gst_rtp_sbc_pay_src_factory)); + + gst_element_class_set_details(element_class, &gst_rtp_sbc_pay_details); +} + +static void gst_rtp_sbc_pay_finalize(GObject *object) +{ + GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(object); + g_object_unref(sbcpay->adapter); + + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} + +static void gst_rtp_sbc_pay_class_init(GstRtpSBCPayClass *klass) +{ + GObjectClass *gobject_class; + GstBaseRTPPayloadClass *payload_class = + GST_BASE_RTP_PAYLOAD_CLASS(klass); + + gobject_class = G_OBJECT_CLASS(klass); + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_finalize); + gobject_class->set_property = GST_DEBUG_FUNCPTR( + gst_rtp_sbc_pay_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR( + gst_rtp_sbc_pay_get_property); + + payload_class->set_caps = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_set_caps); + payload_class->handle_buffer = GST_DEBUG_FUNCPTR( + gst_rtp_sbc_pay_handle_buffer); + payload_class->handle_event = GST_DEBUG_FUNCPTR( + gst_rtp_sbc_pay_handle_event); + + /* properties */ + g_object_class_install_property(G_OBJECT_CLASS(klass), + PROP_MIN_FRAMES, + g_param_spec_int("min-frames", "minimum frame number", + "Minimum quantity of frames to send in one packet " + "(-1 for maximum allowed by the mtu)", + -1, G_MAXINT, DEFAULT_MIN_FRAMES, G_PARAM_READWRITE)); + + GST_DEBUG_CATEGORY_INIT(gst_rtp_sbc_pay_debug, "rtpsbcpay", 0, + "RTP SBC payloader"); +} + +static void gst_rtp_sbc_pay_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstRtpSBCPay *sbcpay; + + sbcpay = GST_RTP_SBC_PAY(object); + + switch (prop_id) { + case PROP_MIN_FRAMES: + sbcpay->min_frames = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_rtp_sbc_pay_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstRtpSBCPay *sbcpay; + + sbcpay = GST_RTP_SBC_PAY(object); + + switch (prop_id) { + case PROP_MIN_FRAMES: + g_value_set_int(value, sbcpay->min_frames); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_rtp_sbc_pay_init(GstRtpSBCPay *self, GstRtpSBCPayClass *klass) +{ + self->adapter = gst_adapter_new(); + self->frame_length = 0; + self->timestamp = 0; + + self->min_frames = DEFAULT_MIN_FRAMES; +} + +gboolean gst_rtp_sbc_pay_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "rtpsbcpay", GST_RANK_NONE, + GST_TYPE_RTP_SBC_PAY); +} + diff --git a/audio/gstrtpsbcpay.h b/audio/gstrtpsbcpay.h new file mode 100644 index 000000000..7b314aa91 --- /dev/null +++ b/audio/gstrtpsbcpay.h @@ -0,0 +1,66 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_RTP_SBC_PAY \ + (gst_rtp_sbc_pay_get_type()) +#define GST_RTP_SBC_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_SBC_PAY,\ + GstRtpSBCPay)) +#define GST_RTP_SBC_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_SBC_PAY,\ + GstRtpSBCPayClass)) +#define GST_IS_RTP_SBC_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_SBC_PAY)) +#define GST_IS_RTP_SBC_PAY_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_SBC_PAY)) + +typedef struct _GstRtpSBCPay GstRtpSBCPay; +typedef struct _GstRtpSBCPayClass GstRtpSBCPayClass; + +struct _GstRtpSBCPay { + GstBaseRTPPayload base; + + GstAdapter *adapter; + GstClockTime timestamp; + + guint frame_length; + + guint min_frames; +}; + +struct _GstRtpSBCPayClass { + GstBaseRTPPayloadClass parent_class; +}; + +//GType gst_rtp_sbc_pay_get_type(void); + +gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin); + +G_END_DECLS diff --git a/audio/gstsbcdec.c b/audio/gstsbcdec.c new file mode 100644 index 000000000..4b46f5231 --- /dev/null +++ b/audio/gstsbcdec.c @@ -0,0 +1,222 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstsbcutil.h" +#include "gstsbcdec.h" + +GST_DEBUG_CATEGORY_STATIC(sbc_dec_debug); +#define GST_CAT_DEFAULT sbc_dec_debug + +GST_BOILERPLATE(GstSbcDec, gst_sbc_dec, GstElement, GST_TYPE_ELEMENT); + +static const GstElementDetails sbc_dec_details = + GST_ELEMENT_DETAILS("Bluetooth SBC decoder", + "Codec/Decoder/Audio", + "Decode a SBC audio stream", + "Marcel Holtmann "); + +static GstStaticPadTemplate sbc_dec_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc")); + +static GstStaticPadTemplate sbc_dec_src_factory = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-raw-int, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "endianness = (int) BYTE_ORDER, " + "signed = (boolean) true, " + "width = (int) 16, " + "depth = (int) 16")); + +static GstFlowReturn sbc_dec_chain(GstPad *pad, GstBuffer *buffer) +{ + GstSbcDec *dec = GST_SBC_DEC(gst_pad_get_parent(pad)); + GstFlowReturn res = GST_FLOW_OK; + guint size, codesize, offset = 0; + guint8 *data; + + codesize = sbc_get_codesize(&dec->sbc); + + if (dec->buffer) { + GstBuffer *temp = buffer; + buffer = gst_buffer_span(dec->buffer, 0, buffer, + GST_BUFFER_SIZE(dec->buffer) + GST_BUFFER_SIZE(buffer)); + gst_buffer_unref(temp); + gst_buffer_unref(dec->buffer); + dec->buffer = NULL; + } + + data = GST_BUFFER_DATA(buffer); + size = GST_BUFFER_SIZE(buffer); + + while (offset < size) { + GstBuffer *output; + GstPadTemplate *template; + GstCaps *caps; + int consumed; + + res = gst_pad_alloc_buffer_and_set_caps(dec->srcpad, + GST_BUFFER_OFFSET_NONE, + codesize, NULL, &output); + + if (res != GST_FLOW_OK) + goto done; + + consumed = sbc_decode(&dec->sbc, data + offset, size - offset, + GST_BUFFER_DATA(output), codesize, + NULL); + if (consumed <= 0) + break; + + /* we will reuse the same caps object */ + if (dec->outcaps == NULL) { + caps = gst_caps_new_simple("audio/x-raw-int", + "rate", G_TYPE_INT, + gst_sbc_parse_rate_from_sbc( + dec->sbc.frequency), + "channels", G_TYPE_INT, + gst_sbc_get_channel_number( + dec->sbc.mode), + NULL); + + template = gst_static_pad_template_get(&sbc_dec_src_factory); + + dec->outcaps = gst_caps_intersect(caps, + gst_pad_template_get_caps(template)); + + gst_caps_unref(caps); + } + + gst_buffer_set_caps(output, dec->outcaps); + + /* FIXME get a real timestamp */ + GST_BUFFER_TIMESTAMP(output) = GST_CLOCK_TIME_NONE; + + res = gst_pad_push(dec->srcpad, output); + if (res != GST_FLOW_OK) + goto done; + + offset += consumed; + } + + if (offset < size) + dec->buffer = gst_buffer_create_sub(buffer, + offset, size - offset); + +done: + gst_buffer_unref(buffer); + gst_object_unref(dec); + + return res; +} + +static GstStateChangeReturn sbc_dec_change_state(GstElement *element, + GstStateChange transition) +{ + GstSbcDec *dec = GST_SBC_DEC(element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG("Setup subband codec"); + if (dec->buffer) { + gst_buffer_unref(dec->buffer); + dec->buffer = NULL; + } + sbc_init(&dec->sbc, 0); + dec->outcaps = NULL; + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG("Finish subband codec"); + if (dec->buffer) { + gst_buffer_unref(dec->buffer); + dec->buffer = NULL; + } + sbc_finish(&dec->sbc); + if (dec->outcaps) { + gst_caps_unref(dec->outcaps); + dec->outcaps = NULL; + } + break; + + default: + break; + } + + return parent_class->change_state(element, transition); +} + +static void gst_sbc_dec_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_dec_sink_factory)); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_dec_src_factory)); + + gst_element_class_set_details(element_class, &sbc_dec_details); +} + +static void gst_sbc_dec_class_init(GstSbcDecClass *klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + element_class->change_state = GST_DEBUG_FUNCPTR(sbc_dec_change_state); + + GST_DEBUG_CATEGORY_INIT(sbc_dec_debug, "sbcdec", 0, + "SBC decoding element"); +} + +static void gst_sbc_dec_init(GstSbcDec *self, GstSbcDecClass *klass) +{ + self->sinkpad = gst_pad_new_from_static_template( + &sbc_dec_sink_factory, "sink"); + gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR( + sbc_dec_chain)); + gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); + + self->srcpad = gst_pad_new_from_static_template( + &sbc_dec_src_factory, "src"); + gst_element_add_pad(GST_ELEMENT(self), self->srcpad); + + self->outcaps = NULL; +} + +gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "sbcdec", GST_RANK_PRIMARY, + GST_TYPE_SBC_DEC); +} + + diff --git a/audio/gstsbcdec.h b/audio/gstsbcdec.h new file mode 100644 index 000000000..383a3bf6f --- /dev/null +++ b/audio/gstsbcdec.h @@ -0,0 +1,66 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include "sbc.h" + +G_BEGIN_DECLS + +#define GST_TYPE_SBC_DEC \ + (gst_sbc_dec_get_type()) +#define GST_SBC_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_DEC,GstSbcDec)) +#define GST_SBC_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_DEC,GstSbcDecClass)) +#define GST_IS_SBC_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_DEC)) +#define GST_IS_SBC_DEC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_DEC)) + +typedef struct _GstSbcDec GstSbcDec; +typedef struct _GstSbcDecClass GstSbcDecClass; + +struct _GstSbcDec { + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + + GstBuffer *buffer; + + /* caps for outgoing buffers */ + GstCaps *outcaps; + + sbc_t sbc; +}; + +struct _GstSbcDecClass { + GstElementClass parent_class; +}; + +//GType gst_sbc_dec_get_type(void); + +gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin); + +G_END_DECLS diff --git a/audio/gstsbcenc.c b/audio/gstsbcenc.c new file mode 100644 index 000000000..0ecf39c80 --- /dev/null +++ b/audio/gstsbcenc.c @@ -0,0 +1,602 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstsbcutil.h" +#include "gstsbcenc.h" + +#define SBC_ENC_DEFAULT_MODE SBC_MODE_AUTO +#define SBC_ENC_DEFAULT_BLOCKS 0 +#define SBC_ENC_DEFAULT_SUB_BANDS 0 +#define SBC_ENC_DEFAULT_ALLOCATION SBC_AM_AUTO +#define SBC_ENC_DEFAULT_RATE 0 +#define SBC_ENC_DEFAULT_CHANNELS 0 + +#define SBC_ENC_BITPOOL_AUTO 1 +#define SBC_ENC_BITPOOL_MIN 2 +#define SBC_ENC_BITPOOL_MIN_STR "2" +#define SBC_ENC_BITPOOL_MAX 64 +#define SBC_ENC_BITPOOL_MAX_STR "64" + +GST_DEBUG_CATEGORY_STATIC(sbc_enc_debug); +#define GST_CAT_DEFAULT sbc_enc_debug + +#define GST_TYPE_SBC_MODE (gst_sbc_mode_get_type()) + +static GType gst_sbc_mode_get_type(void) +{ + static GType sbc_mode_type = 0; + static GEnumValue sbc_modes[] = { + { SBC_MODE_MONO, "Mono", "mono" }, + { SBC_MODE_DUAL_CHANNEL, "Dual Channel", "dual" }, + { SBC_MODE_STEREO, "Stereo", "stereo"}, + { SBC_MODE_JOINT_STEREO, "Joint Stereo", "joint" }, + { SBC_MODE_AUTO, "Auto", "auto" }, + { -1, NULL, NULL} + }; + + if (!sbc_mode_type) + sbc_mode_type = g_enum_register_static("GstSbcMode", sbc_modes); + + return sbc_mode_type; +} + +#define GST_TYPE_SBC_ALLOCATION (gst_sbc_allocation_get_type()) + +static GType gst_sbc_allocation_get_type(void) +{ + static GType sbc_allocation_type = 0; + static GEnumValue sbc_allocations[] = { + { SBC_AM_LOUDNESS, "Loudness", "loudness" }, + { SBC_AM_SNR, "SNR", "snr" }, + { SBC_AM_AUTO, "Auto", "auto" }, + { -1, NULL, NULL} + }; + + if (!sbc_allocation_type) + sbc_allocation_type = g_enum_register_static( + "GstSbcAllocation", sbc_allocations); + + return sbc_allocation_type; +} + +#define GST_TYPE_SBC_BLOCKS (gst_sbc_blocks_get_type()) + +static GType gst_sbc_blocks_get_type(void) +{ + static GType sbc_blocks_type = 0; + static GEnumValue sbc_blocks[] = { + { 0, "Auto", "auto" }, + { 4, "4", "4" }, + { 8, "8", "8" }, + { 12, "12", "12" }, + { 16, "16", "16" }, + { -1, NULL, NULL} + }; + + if (!sbc_blocks_type) + sbc_blocks_type = g_enum_register_static( + "GstSbcBlocks", sbc_blocks); + + return sbc_blocks_type; +} + +#define GST_TYPE_SBC_SUBBANDS (gst_sbc_subbands_get_type()) + +static GType gst_sbc_subbands_get_type(void) +{ + static GType sbc_subbands_type = 0; + static GEnumValue sbc_subbands[] = { + { 0, "Auto", "auto" }, + { 4, "4 subbands", "4" }, + { 8, "8 subbands", "8" }, + { -1, NULL, NULL} + }; + + if (!sbc_subbands_type) + sbc_subbands_type = g_enum_register_static( + "GstSbcSubbands", sbc_subbands); + + return sbc_subbands_type; +} + +enum { + PROP_0, + PROP_MODE, + PROP_ALLOCATION, + PROP_BLOCKS, + PROP_SUBBANDS, + PROP_BITPOOL +}; + +GST_BOILERPLATE(GstSbcEnc, gst_sbc_enc, GstElement, GST_TYPE_ELEMENT); + +static const GstElementDetails sbc_enc_details = + GST_ELEMENT_DETAILS("Bluetooth SBC encoder", + "Codec/Encoder/Audio", + "Encode a SBC audio stream", + "Marcel Holtmann "); + +static GstStaticPadTemplate sbc_enc_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-raw-int, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "endianness = (int) BYTE_ORDER, " + "signed = (boolean) true, " + "width = (int) 16, " + "depth = (int) 16")); + +static GstStaticPadTemplate sbc_enc_src_factory = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " + "blocks = (int) { 4, 8, 12, 16 }, " + "subbands = (int) { 4, 8 }, " + "allocation = (string) { \"snr\", \"loudness\" }, " + "bitpool = (int) [ " SBC_ENC_BITPOOL_MIN_STR + ", " SBC_ENC_BITPOOL_MAX_STR " ]")); + +gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps); + +static GstCaps *sbc_enc_generate_srcpad_caps(GstSbcEnc *enc) +{ + GstCaps *src_caps; + GstStructure *structure; + GEnumValue *enum_value; + GEnumClass *enum_class; + GValue *value; + + src_caps = gst_caps_copy(gst_pad_get_pad_template_caps(enc->srcpad)); + structure = gst_caps_get_structure(src_caps, 0); + + value = g_new0(GValue, 1); + + if (enc->rate != 0) + gst_sbc_util_set_structure_int_param(structure, "rate", + enc->rate, value); + + if (enc->channels != 0) + gst_sbc_util_set_structure_int_param(structure, "channels", + enc->channels, value); + + if (enc->subbands != 0) + gst_sbc_util_set_structure_int_param(structure, "subbands", + enc->subbands, value); + + if (enc->blocks != 0) + gst_sbc_util_set_structure_int_param(structure, "blocks", + enc->blocks, value); + + if (enc->bitpool != SBC_ENC_BITPOOL_AUTO) + gst_sbc_util_set_structure_int_param(structure, "bitpool", + enc->bitpool, value); + + if (enc->mode != SBC_ENC_DEFAULT_MODE) { + enum_class = g_type_class_ref(GST_TYPE_SBC_MODE); + enum_value = g_enum_get_value(enum_class, enc->mode); + gst_sbc_util_set_structure_string_param(structure, "mode", + enum_value->value_nick, value); + g_type_class_unref(enum_class); + } + + if (enc->allocation != SBC_AM_AUTO) { + enum_class = g_type_class_ref(GST_TYPE_SBC_ALLOCATION); + enum_value = g_enum_get_value(enum_class, enc->allocation); + gst_sbc_util_set_structure_string_param(structure, "allocation", + enum_value->value_nick, value); + g_type_class_unref(enum_class); + } + + g_free(value); + + return src_caps; +} + +static GstCaps *sbc_enc_src_getcaps(GstPad *pad) +{ + GstSbcEnc *enc; + + enc = GST_SBC_ENC(GST_PAD_PARENT(pad)); + + return sbc_enc_generate_srcpad_caps(enc); +} + +static gboolean sbc_enc_src_setcaps(GstPad *pad, GstCaps *caps) +{ + GstSbcEnc *enc = GST_SBC_ENC(GST_PAD_PARENT(pad)); + + GST_LOG_OBJECT(enc, "setting srcpad caps"); + + return gst_sbc_enc_fill_sbc_params(enc, caps); +} + +static GstCaps *sbc_enc_src_caps_fixate(GstSbcEnc *enc, GstCaps *caps) +{ + gchar *error_message = NULL; + GstCaps *result; + + result = gst_sbc_util_caps_fixate(caps, &error_message); + + if (!result) { + GST_WARNING_OBJECT(enc, "Invalid input caps caused parsing " + "error: %s", error_message); + g_free(error_message); + return NULL; + } + + return result; +} + +static GstCaps *sbc_enc_get_fixed_srcpad_caps(GstSbcEnc *enc) +{ + GstCaps *caps; + gboolean res = TRUE; + GstCaps *result_caps = NULL; + + caps = gst_pad_get_allowed_caps(enc->srcpad); + if (caps == NULL) + caps = sbc_enc_src_getcaps(enc->srcpad); + + if (caps == GST_CAPS_NONE || gst_caps_is_empty(caps)) { + res = FALSE; + goto done; + } + + result_caps = sbc_enc_src_caps_fixate(enc, caps); + +done: + gst_caps_unref(caps); + + if (!res) + return NULL; + + return result_caps; +} + +static gboolean sbc_enc_sink_setcaps(GstPad *pad, GstCaps *caps) +{ + GstSbcEnc *enc; + GstStructure *structure; + GstCaps *src_caps; + gint rate, channels; + gboolean res; + + enc = GST_SBC_ENC(GST_PAD_PARENT(pad)); + structure = gst_caps_get_structure(caps, 0); + + if (!gst_structure_get_int(structure, "rate", &rate)) + return FALSE; + if (!gst_structure_get_int(structure, "channels", &channels)) + return FALSE; + + enc->rate = rate; + enc->channels = channels; + + src_caps = sbc_enc_get_fixed_srcpad_caps(enc); + if (!src_caps) + return FALSE; + res = gst_pad_set_caps(enc->srcpad, src_caps); + gst_caps_unref(src_caps); + + return res; +} + +gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps) +{ + if (!gst_caps_is_fixed(caps)) { + GST_DEBUG_OBJECT(enc, "didn't receive fixed caps, " + "returning false"); + return FALSE; + } + + if (!gst_sbc_util_fill_sbc_params(&enc->sbc, caps)) + return FALSE; + + if (enc->rate != 0 && gst_sbc_parse_rate_from_sbc(enc->sbc.frequency) + != enc->rate) + goto fail; + + if (enc->channels != 0 && gst_sbc_get_channel_number(enc->sbc.mode) + != enc->channels) + goto fail; + + if (enc->blocks != 0 && gst_sbc_parse_blocks_from_sbc(enc->sbc.blocks) + != enc->blocks) + goto fail; + + if (enc->subbands != 0 && gst_sbc_parse_subbands_from_sbc( + enc->sbc.subbands) != enc->subbands) + goto fail; + + if (enc->mode != SBC_ENC_DEFAULT_MODE && enc->sbc.mode != enc->mode) + goto fail; + + if (enc->allocation != SBC_AM_AUTO && + enc->sbc.allocation != enc->allocation) + goto fail; + + if (enc->bitpool != SBC_ENC_BITPOOL_AUTO && + enc->sbc.bitpool != enc->bitpool) + goto fail; + + enc->codesize = sbc_get_codesize(&enc->sbc); + enc->frame_length = sbc_get_frame_length(&enc->sbc); + enc->frame_duration = sbc_get_frame_duration(&enc->sbc); + + GST_DEBUG_OBJECT(enc, "codesize: %d, frame_length: %d, frame_duration:" + " %d", enc->codesize, enc->frame_length, + enc->frame_duration); + + return TRUE; + +fail: + memset(&enc->sbc, 0, sizeof(sbc_t)); + return FALSE; +} + +static GstFlowReturn sbc_enc_chain(GstPad *pad, GstBuffer *buffer) +{ + GstSbcEnc *enc = GST_SBC_ENC(gst_pad_get_parent(pad)); + GstAdapter *adapter = enc->adapter; + GstFlowReturn res = GST_FLOW_OK; + + gst_adapter_push(adapter, buffer); + + while (gst_adapter_available(adapter) >= enc->codesize && + res == GST_FLOW_OK) { + GstBuffer *output; + GstCaps *caps; + const guint8 *data; + gint consumed; + + caps = GST_PAD_CAPS(enc->srcpad); + res = gst_pad_alloc_buffer_and_set_caps(enc->srcpad, + GST_BUFFER_OFFSET_NONE, + enc->frame_length, caps, + &output); + if (res != GST_FLOW_OK) + goto done; + + data = gst_adapter_peek(adapter, enc->codesize); + + consumed = sbc_encode(&enc->sbc, (gpointer) data, + enc->codesize, + GST_BUFFER_DATA(output), + GST_BUFFER_SIZE(output), NULL); + if (consumed <= 0) { + GST_DEBUG_OBJECT(enc, "comsumed < 0, codesize: %d", + enc->codesize); + break; + } + gst_adapter_flush(adapter, consumed); + + GST_BUFFER_TIMESTAMP(output) = GST_BUFFER_TIMESTAMP(buffer); + /* we have only 1 frame */ + GST_BUFFER_DURATION(output) = enc->frame_duration; + + res = gst_pad_push(enc->srcpad, output); + + if (res != GST_FLOW_OK) + goto done; + } + +done: + gst_object_unref(enc); + + return res; +} + +static GstStateChangeReturn sbc_enc_change_state(GstElement *element, + GstStateChange transition) +{ + GstSbcEnc *enc = GST_SBC_ENC(element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG("Setup subband codec"); + sbc_init(&enc->sbc, 0); + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG("Finish subband codec"); + sbc_finish(&enc->sbc); + break; + + default: + break; + } + + return parent_class->change_state(element, transition); +} + +static void gst_sbc_enc_dispose(GObject *object) +{ + GstSbcEnc *enc = GST_SBC_ENC(object); + + if (enc->adapter != NULL) + g_object_unref(G_OBJECT(enc->adapter)); + + enc->adapter = NULL; +} + +static void gst_sbc_enc_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_enc_sink_factory)); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_enc_src_factory)); + + gst_element_class_set_details(element_class, &sbc_enc_details); +} + +static void gst_sbc_enc_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstSbcEnc *enc = GST_SBC_ENC(object); + + /* changes to those properties will only happen on the next caps + * negotiation */ + + switch (prop_id) { + case PROP_MODE: + enc->mode = g_value_get_enum(value); + break; + case PROP_ALLOCATION: + enc->allocation = g_value_get_enum(value); + break; + case PROP_BLOCKS: + enc->blocks = g_value_get_enum(value); + break; + case PROP_SUBBANDS: + enc->subbands = g_value_get_enum(value); + break; + case PROP_BITPOOL: + enc->bitpool = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_sbc_enc_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstSbcEnc *enc = GST_SBC_ENC(object); + + switch (prop_id) { + case PROP_MODE: + g_value_set_enum(value, enc->mode); + break; + case PROP_ALLOCATION: + g_value_set_enum(value, enc->allocation); + break; + case PROP_BLOCKS: + g_value_set_enum(value, enc->blocks); + break; + case PROP_SUBBANDS: + g_value_set_enum(value, enc->subbands); + break; + case PROP_BITPOOL: + g_value_set_int(value, enc->bitpool); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_sbc_enc_class_init(GstSbcEncClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + object_class->set_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_get_property); + object_class->dispose = GST_DEBUG_FUNCPTR(gst_sbc_enc_dispose); + + element_class->change_state = GST_DEBUG_FUNCPTR(sbc_enc_change_state); + + g_object_class_install_property(object_class, PROP_MODE, + g_param_spec_enum("mode", "Mode", + "Encoding mode", GST_TYPE_SBC_MODE, + SBC_ENC_DEFAULT_MODE, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_ALLOCATION, + g_param_spec_enum("allocation", "Allocation", + "Allocation method", GST_TYPE_SBC_ALLOCATION, + SBC_ENC_DEFAULT_ALLOCATION, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_BLOCKS, + g_param_spec_enum("blocks", "Blocks", + "Blocks", GST_TYPE_SBC_BLOCKS, + SBC_ENC_DEFAULT_BLOCKS, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_SUBBANDS, + g_param_spec_enum("subbands", "Sub bands", + "Number of sub bands", GST_TYPE_SBC_SUBBANDS, + SBC_ENC_DEFAULT_SUB_BANDS, G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, PROP_BITPOOL, + g_param_spec_int("bitpool", "Bitpool", + "Bitpool (use 1 for automatic selection)", + SBC_ENC_BITPOOL_AUTO, SBC_ENC_BITPOOL_MAX, + SBC_ENC_BITPOOL_AUTO, G_PARAM_READWRITE)); + + GST_DEBUG_CATEGORY_INIT(sbc_enc_debug, "sbcenc", 0, + "SBC encoding element"); +} + +static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass) +{ + self->sinkpad = gst_pad_new_from_static_template( + &sbc_enc_sink_factory, "sink"); + gst_pad_set_setcaps_function(self->sinkpad, + GST_DEBUG_FUNCPTR(sbc_enc_sink_setcaps)); + gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); + + self->srcpad = gst_pad_new_from_static_template( + &sbc_enc_src_factory, "src"); + gst_pad_set_getcaps_function(self->srcpad, + GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps)); + gst_pad_set_setcaps_function(self->srcpad, + GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps)); + gst_element_add_pad(GST_ELEMENT(self), self->srcpad); + + gst_pad_set_chain_function(self->sinkpad, + GST_DEBUG_FUNCPTR(sbc_enc_chain)); + + self->subbands = SBC_ENC_DEFAULT_SUB_BANDS; + self->blocks = SBC_ENC_DEFAULT_BLOCKS; + self->mode = SBC_ENC_DEFAULT_MODE; + self->allocation = SBC_ENC_DEFAULT_ALLOCATION; + self->rate = SBC_ENC_DEFAULT_RATE; + self->channels = SBC_ENC_DEFAULT_CHANNELS; + self->bitpool = SBC_ENC_BITPOOL_AUTO; + + self->frame_length = 0; + self->frame_duration = 0; + + self->adapter = gst_adapter_new(); +} + +gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "sbcenc", + GST_RANK_NONE, GST_TYPE_SBC_ENC); +} + + diff --git a/audio/gstsbcenc.h b/audio/gstsbcenc.h new file mode 100644 index 000000000..6d69922ae --- /dev/null +++ b/audio/gstsbcenc.h @@ -0,0 +1,75 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#include "sbc.h" + +G_BEGIN_DECLS + +#define GST_TYPE_SBC_ENC \ + (gst_sbc_enc_get_type()) +#define GST_SBC_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_ENC,GstSbcEnc)) +#define GST_SBC_ENC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_ENC,GstSbcEncClass)) +#define GST_IS_SBC_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_ENC)) +#define GST_IS_SBC_ENC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_ENC)) + +typedef struct _GstSbcEnc GstSbcEnc; +typedef struct _GstSbcEncClass GstSbcEncClass; + +struct _GstSbcEnc { + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + GstAdapter *adapter; + + gint rate; + gint channels; + gint mode; + gint blocks; + gint allocation; + gint subbands; + gint bitpool; + + guint codesize; + gint frame_length; + gint frame_duration; + + sbc_t sbc; +}; + +struct _GstSbcEncClass { + GstElementClass parent_class; +}; + +//GType gst_sbc_enc_get_type(void); + +gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin); + +G_END_DECLS diff --git a/audio/gstsbcparse.c b/audio/gstsbcparse.c new file mode 100644 index 000000000..30d56baaa --- /dev/null +++ b/audio/gstsbcparse.c @@ -0,0 +1,219 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstsbcutil.h" +#include "gstsbcparse.h" + +GST_DEBUG_CATEGORY_STATIC(sbc_parse_debug); +#define GST_CAT_DEFAULT sbc_parse_debug + +GST_BOILERPLATE(GstSbcParse, gst_sbc_parse, GstElement, GST_TYPE_ELEMENT); + +static const GstElementDetails sbc_parse_details = + GST_ELEMENT_DETAILS("Bluetooth SBC parser", + "Codec/Parser/Audio", + "Parse a SBC audio stream", + "Marcel Holtmann "); + +static GstStaticPadTemplate sbc_parse_sink_factory = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc," + "parsed = (boolean) false")); + +static GstStaticPadTemplate sbc_parse_src_factory = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-sbc, " + "rate = (int) { 16000, 32000, 44100, 48000 }, " + "channels = (int) [ 1, 2 ], " + "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " + "blocks = (int) { 4, 8, 12, 16 }, " + "subbands = (int) { 4, 8 }, " + "allocation = (string) { \"snr\", \"loudness\" }," + "bitpool = (int) [ 2, 64 ]," + "parsed = (boolean) true")); + +static GstFlowReturn sbc_parse_chain(GstPad *pad, GstBuffer *buffer) +{ + GstSbcParse *parse = GST_SBC_PARSE(gst_pad_get_parent(pad)); + GstFlowReturn res = GST_FLOW_OK; + guint size, offset = 0; + guint8 *data; + + /* FIXME use a gstadpter */ + if (parse->buffer) { + GstBuffer *temp; + temp = buffer; + buffer = gst_buffer_span(parse->buffer, 0, buffer, + GST_BUFFER_SIZE(parse->buffer) + + GST_BUFFER_SIZE(buffer)); + gst_buffer_unref(parse->buffer); + gst_buffer_unref(temp); + parse->buffer = NULL; + } + + data = GST_BUFFER_DATA(buffer); + size = GST_BUFFER_SIZE(buffer); + + while (offset < size) { + GstBuffer *output; + int consumed; + + consumed = sbc_parse(&parse->new_sbc, data + offset, + size - offset); + if (consumed <= 0) + break; + + if (parse->first_parsing || (memcmp(&parse->sbc, + &parse->new_sbc, sizeof(sbc_t)) != 0)) { + + memcpy(&parse->sbc, &parse->new_sbc, sizeof(sbc_t)); + if (parse->outcaps != NULL) + gst_caps_unref(parse->outcaps); + + parse->outcaps = gst_sbc_parse_caps_from_sbc( + &parse->sbc); + + parse->first_parsing = FALSE; + } + + res = gst_pad_alloc_buffer_and_set_caps(parse->srcpad, + GST_BUFFER_OFFSET_NONE, + consumed, parse->outcaps, &output); + + if (res != GST_FLOW_OK) + goto done; + + memcpy(GST_BUFFER_DATA(output), data + offset, consumed); + + res = gst_pad_push(parse->srcpad, output); + if (res != GST_FLOW_OK) + goto done; + + offset += consumed; + } + + if (offset < size) + parse->buffer = gst_buffer_create_sub(buffer, + offset, size - offset); + +done: + gst_buffer_unref(buffer); + gst_object_unref(parse); + + return res; +} + +static GstStateChangeReturn sbc_parse_change_state(GstElement *element, + GstStateChange transition) +{ + GstSbcParse *parse = GST_SBC_PARSE(element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG("Setup subband codec"); + + parse->channels = -1; + parse->rate = -1; + parse->first_parsing = TRUE; + + sbc_init(&parse->sbc, 0); + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG("Finish subband codec"); + + if (parse->buffer) { + gst_buffer_unref(parse->buffer); + parse->buffer = NULL; + } + if (parse->outcaps != NULL) { + gst_caps_unref(parse->outcaps); + parse->outcaps = NULL; + } + + sbc_finish(&parse->sbc); + break; + + default: + break; + } + + return parent_class->change_state(element, transition); +} + +static void gst_sbc_parse_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_parse_sink_factory)); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sbc_parse_src_factory)); + + gst_element_class_set_details(element_class, &sbc_parse_details); +} + +static void gst_sbc_parse_class_init(GstSbcParseClass *klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parent_class = g_type_class_peek_parent(klass); + + element_class->change_state = GST_DEBUG_FUNCPTR(sbc_parse_change_state); + + GST_DEBUG_CATEGORY_INIT(sbc_parse_debug, "sbcparse", 0, + "SBC parsing element"); +} + +static void gst_sbc_parse_init(GstSbcParse *self, GstSbcParseClass *klass) +{ + self->sinkpad = gst_pad_new_from_static_template( + &sbc_parse_sink_factory, "sink"); + gst_pad_set_chain_function(self->sinkpad, + GST_DEBUG_FUNCPTR(sbc_parse_chain)); + gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); + + self->srcpad = gst_pad_new_from_static_template( + &sbc_parse_src_factory, "src"); + gst_element_add_pad(GST_ELEMENT(self), self->srcpad); + + self->outcaps = NULL; + self->buffer = NULL; + self->channels = -1; + self->rate = -1; + self->first_parsing = TRUE; +} + +gboolean gst_sbc_parse_plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "sbcparse", GST_RANK_NONE, + GST_TYPE_SBC_PARSE); +} + diff --git a/audio/gstsbcparse.h b/audio/gstsbcparse.h new file mode 100644 index 000000000..ef3d1d8e3 --- /dev/null +++ b/audio/gstsbcparse.h @@ -0,0 +1,69 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include "sbc.h" + +G_BEGIN_DECLS + +#define GST_TYPE_SBC_PARSE \ + (gst_sbc_parse_get_type()) +#define GST_SBC_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_PARSE,GstSbcParse)) +#define GST_SBC_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_PARSE,GstSbcParseClass)) +#define GST_IS_SBC_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_PARSE)) +#define GST_IS_SBC_PARSE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_PARSE)) + +typedef struct _GstSbcParse GstSbcParse; +typedef struct _GstSbcParseClass GstSbcParseClass; + +struct _GstSbcParse { + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + + GstBuffer *buffer; + + sbc_t sbc; + sbc_t new_sbc; + GstCaps *outcaps; + gboolean first_parsing; + + gint channels; + gint rate; +}; + +struct _GstSbcParseClass { + GstElementClass parent_class; +}; + +//GType gst_sbc_parse_get_type(void); + +gboolean gst_sbc_parse_plugin_init(GstPlugin *plugin); + +G_END_DECLS diff --git a/audio/gstsbcutil.c b/audio/gstsbcutil.c new file mode 100644 index 000000000..b102da3ad --- /dev/null +++ b/audio/gstsbcutil.c @@ -0,0 +1,521 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include "gstsbcutil.h" + +/* + * Selects one rate from a list of possible rates + * TODO - use a better approach to this (it is selecting the last element) + */ +gint gst_sbc_select_rate_from_list(const GValue *value) +{ + guint size = gst_value_list_get_size(value); + return g_value_get_int(gst_value_list_get_value(value, size-1)); +} + +/* + * Selects one number of channels option from a range of possible numbers + * TODO - use a better approach to this (it is selecting the maximum value) + */ +gint gst_sbc_select_channels_from_range(const GValue *value) +{ + return gst_value_get_int_range_max(value); +} + +/* + * Selects one number of blocks from a list of possible blocks + * TODO - use a better approach to this (it is selecting the last element) + */ +gint gst_sbc_select_blocks_from_list(const GValue *value) +{ + guint size = gst_value_list_get_size(value); + return g_value_get_int(gst_value_list_get_value(value, size-1)); +} + +/* + * Selects one number of subbands from a list + * TODO - use a better approach to this (it is selecting the last element) + */ +gint gst_sbc_select_subbands_from_list(const GValue *value) +{ + guint size = gst_value_list_get_size(value); + return g_value_get_int(gst_value_list_get_value(value, size-1)); +} + +/* + * Selects one bitpool option from a range + * TODO - use a better approach to this (it is selecting the maximum value) + */ +gint gst_sbc_select_bitpool_from_range(const GValue *value) +{ + return gst_value_get_int_range_max(value); +} + +/* + * Selects one allocation mode from the ones on the list + * TODO - use a better approach + */ +const gchar *gst_sbc_get_allocation_from_list(const GValue *value) +{ + guint size = gst_value_list_get_size(value); + return g_value_get_string(gst_value_list_get_value(value, size-1)); +} + +/* + * Selects one mode from the ones on the list + */ +const gchar *gst_sbc_get_mode_from_list(const GValue *list, gint channels) +{ + unsigned int i; + const GValue *value; + const gchar *aux; + gboolean joint, stereo, dual, mono; + guint size = gst_value_list_get_size(list); + + joint = stereo = dual = mono = FALSE; + + for (i = 0; i < size; i++) { + value = gst_value_list_get_value(list, i); + aux = g_value_get_string(value); + if (strcmp("joint", aux) == 0) + joint = TRUE; + else if (strcmp("stereo", aux) == 0) + stereo = TRUE; + else if (strcmp("dual", aux) == 0) + dual = TRUE; + else if (strcmp("mono", aux) == 0) + mono = TRUE; + } + + if (channels == 1 && mono) + return "mono"; + else if (channels == 2) { + if (joint) + return "joint"; + else if (stereo) + return "stereo"; + else if (dual) + return "dual"; + } + + return NULL; +} + +gint gst_sbc_parse_rate_from_sbc(gint frequency) +{ + switch (frequency) { + case SBC_FREQ_16000: + return 16000; + case SBC_FREQ_32000: + return 32000; + case SBC_FREQ_44100: + return 44100; + case SBC_FREQ_48000: + return 48000; + default: + return 0; + } +} + +gint gst_sbc_parse_rate_to_sbc(gint rate) +{ + switch (rate) { + case 16000: + return SBC_FREQ_16000; + case 32000: + return SBC_FREQ_32000; + case 44100: + return SBC_FREQ_44100; + case 48000: + return SBC_FREQ_48000; + default: + return -1; + } +} + +gint gst_sbc_get_channel_number(gint mode) +{ + switch (mode) { + case SBC_MODE_JOINT_STEREO: + case SBC_MODE_STEREO: + case SBC_MODE_DUAL_CHANNEL: + return 2; + case SBC_MODE_MONO: + return 1; + default: + return 0; + } +} + +gint gst_sbc_parse_subbands_from_sbc(gint subbands) +{ + switch (subbands) { + case SBC_SB_4: + return 4; + case SBC_SB_8: + return 8; + default: + return 0; + } +} + +gint gst_sbc_parse_subbands_to_sbc(gint subbands) +{ + switch (subbands) { + case 4: + return SBC_SB_4; + case 8: + return SBC_SB_8; + default: + return -1; + } +} + +gint gst_sbc_parse_blocks_from_sbc(gint blocks) +{ + switch (blocks) { + case SBC_BLK_4: + return 4; + case SBC_BLK_8: + return 8; + case SBC_BLK_12: + return 12; + case SBC_BLK_16: + return 16; + default: + return 0; + } +} + +gint gst_sbc_parse_blocks_to_sbc(gint blocks) +{ + switch (blocks) { + case 4: + return SBC_BLK_4; + case 8: + return SBC_BLK_8; + case 12: + return SBC_BLK_12; + case 16: + return SBC_BLK_16; + default: + return -1; + } +} + +const gchar *gst_sbc_parse_mode_from_sbc(gint mode) +{ + switch (mode) { + case SBC_MODE_MONO: + return "mono"; + case SBC_MODE_DUAL_CHANNEL: + return "dual"; + case SBC_MODE_STEREO: + return "stereo"; + case SBC_MODE_JOINT_STEREO: + case SBC_MODE_AUTO: + return "joint"; + default: + return NULL; + } +} + +gint gst_sbc_parse_mode_to_sbc(const gchar *mode) +{ + if (g_ascii_strcasecmp(mode, "joint") == 0) + return SBC_MODE_JOINT_STEREO; + else if (g_ascii_strcasecmp(mode, "stereo") == 0) + return SBC_MODE_STEREO; + else if (g_ascii_strcasecmp(mode, "dual") == 0) + return SBC_MODE_DUAL_CHANNEL; + else if (g_ascii_strcasecmp(mode, "mono") == 0) + return SBC_MODE_MONO; + else if (g_ascii_strcasecmp(mode, "auto") == 0) + return SBC_MODE_JOINT_STEREO; + else + return -1; +} + +const gchar *gst_sbc_parse_allocation_from_sbc(gint alloc) +{ + switch (alloc) { + case SBC_AM_LOUDNESS: + return "loudness"; + case SBC_AM_SNR: + return "snr"; + case SBC_AM_AUTO: + return "loudness"; + default: + return NULL; + } +} + +gint gst_sbc_parse_allocation_to_sbc(const gchar *allocation) +{ + if (g_ascii_strcasecmp(allocation, "loudness") == 0) + return SBC_AM_LOUDNESS; + else if (g_ascii_strcasecmp(allocation, "snr") == 0) + return SBC_AM_SNR; + else + return SBC_AM_LOUDNESS; +} + +GstCaps *gst_sbc_parse_caps_from_sbc(sbc_t *sbc) +{ + GstCaps *caps; + const gchar *mode_str; + const gchar *allocation_str; + + mode_str = gst_sbc_parse_mode_from_sbc(sbc->mode); + allocation_str = gst_sbc_parse_allocation_from_sbc(sbc->allocation); + caps = gst_caps_new_simple("audio/x-sbc", + "rate", G_TYPE_INT, + gst_sbc_parse_rate_from_sbc(sbc->frequency), + "channels", G_TYPE_INT, + gst_sbc_get_channel_number(sbc->mode), + "mode", G_TYPE_STRING, mode_str, + "subbands", G_TYPE_INT, + gst_sbc_parse_subbands_from_sbc(sbc->subbands), + "blocks", G_TYPE_INT, + gst_sbc_parse_blocks_from_sbc(sbc->blocks), + "allocation", G_TYPE_STRING, allocation_str, + "bitpool", G_TYPE_INT, sbc->bitpool, + NULL); + + return caps; +} + +/* + * Given a GstCaps, this will return a fixed GstCaps on sucessfull conversion. + * If an error occurs, it will return NULL and error_message will contain the + * error message. + * + * error_message must be passed NULL, if an error occurs, the caller has the + * ownership of the error_message, it must be freed after use. + */ +GstCaps *gst_sbc_util_caps_fixate(GstCaps *caps, gchar **error_message) +{ + GstCaps *result; + GstStructure *structure; + const GValue *value; + gboolean error = FALSE; + gint temp, rate, channels, blocks, subbands, bitpool; + const gchar *allocation = NULL; + const gchar *mode = NULL; + + g_assert(*error_message == NULL); + + structure = gst_caps_get_structure(caps, 0); + + if (!gst_structure_has_field(structure, "rate")) { + error = TRUE; + *error_message = g_strdup("no rate"); + goto error; + } else { + value = gst_structure_get_value(structure, "rate"); + if (GST_VALUE_HOLDS_LIST(value)) + temp = gst_sbc_select_rate_from_list(value); + else + temp = g_value_get_int(value); + rate = temp; + } + + if (!gst_structure_has_field(structure, "channels")) { + error = TRUE; + *error_message = g_strdup("no channels"); + goto error; + } else { + value = gst_structure_get_value(structure, "channels"); + if (GST_VALUE_HOLDS_INT_RANGE(value)) + temp = gst_sbc_select_channels_from_range(value); + else + temp = g_value_get_int(value); + channels = temp; + } + + if (!gst_structure_has_field(structure, "blocks")) { + error = TRUE; + *error_message = g_strdup("no blocks."); + goto error; + } else { + value = gst_structure_get_value(structure, "blocks"); + if (GST_VALUE_HOLDS_LIST(value)) + temp = gst_sbc_select_blocks_from_list(value); + else + temp = g_value_get_int(value); + blocks = temp; + } + + if (!gst_structure_has_field(structure, "subbands")) { + error = TRUE; + *error_message = g_strdup("no subbands"); + goto error; + } else { + value = gst_structure_get_value(structure, "subbands"); + if (GST_VALUE_HOLDS_LIST(value)) + temp = gst_sbc_select_subbands_from_list(value); + else + temp = g_value_get_int(value); + subbands = temp; + } + + if (!gst_structure_has_field(structure, "bitpool")) { + error = TRUE; + *error_message = g_strdup("no bitpool"); + goto error; + } else { + value = gst_structure_get_value(structure, "bitpool"); + if (GST_VALUE_HOLDS_INT_RANGE(value)) + temp = gst_sbc_select_bitpool_from_range(value); + else + temp = g_value_get_int(value); + bitpool = temp; + } + + if (!gst_structure_has_field(structure, "allocation")) { + error = TRUE; + *error_message = g_strdup("no allocation"); + goto error; + } else { + value = gst_structure_get_value(structure, "allocation"); + if (GST_VALUE_HOLDS_LIST(value)) + allocation = gst_sbc_get_allocation_from_list(value); + else + allocation = g_value_get_string(value); + } + + if (!gst_structure_has_field(structure, "mode")) { + error = TRUE; + *error_message = g_strdup("no mode"); + goto error; + } else { + value = gst_structure_get_value(structure, "mode"); + if (GST_VALUE_HOLDS_LIST(value)) { + mode = gst_sbc_get_mode_from_list(value, channels); + } else + mode = g_value_get_string(value); + } + + /* perform validation + * if channels is 1, we must have channel mode = mono + * if channels is 2, we can't have channel mode = mono */ + if ( (channels == 1 && (strcmp(mode, "mono") != 0) ) || + ( channels == 2 && ( strcmp(mode, "mono") == 0))) { + *error_message = g_strdup_printf("Invalid combination of " + "channels (%d) and channel mode (%s)", + channels, mode); + error = TRUE; + } + +error: + if (error) + return NULL; + + result = gst_caps_new_simple("audio/x-sbc", + "rate", G_TYPE_INT, rate, + "channels", G_TYPE_INT, channels, + "mode", G_TYPE_STRING, mode, + "blocks", G_TYPE_INT, blocks, + "subbands", G_TYPE_INT, subbands, + "allocation", G_TYPE_STRING, allocation, + "bitpool", G_TYPE_INT, bitpool, + NULL); + + return result; +} + +/** + * Sets the int field_value to the param "field" on the structure. + * value is used to do the operation, it must be a uninitialized (zero-filled) + * GValue, it will be left unitialized at the end of the function. + */ +void gst_sbc_util_set_structure_int_param(GstStructure *structure, + const gchar *field, gint field_value, + GValue *value) +{ + value = g_value_init(value, G_TYPE_INT); + g_value_set_int(value, field_value); + gst_structure_set_value(structure, field, value); + g_value_unset(value); +} + +/** + * Sets the string field_value to the param "field" on the structure. + * value is used to do the operation, it must be a uninitialized (zero-filled) + * GValue, it will be left unitialized at the end of the function. + */ +void gst_sbc_util_set_structure_string_param(GstStructure *structure, + const gchar *field, const gchar *field_value, + GValue *value) +{ + value = g_value_init(value, G_TYPE_STRING); + g_value_set_string(value, field_value); + gst_structure_set_value(structure, field, value); + g_value_unset(value); +} + +gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps) +{ + GstStructure *structure; + gint rate, channels, subbands, blocks, bitpool; + const gchar *mode; + const gchar *allocation; + + g_assert(gst_caps_is_fixed(caps)); + + structure = gst_caps_get_structure(caps, 0); + + if (!gst_structure_get_int(structure, "rate", &rate)) + return FALSE; + if (!gst_structure_get_int(structure, "channels", &channels)) + return FALSE; + if (!gst_structure_get_int(structure, "subbands", &subbands)) + return FALSE; + if (!gst_structure_get_int(structure, "blocks", &blocks)) + return FALSE; + if (!gst_structure_get_int(structure, "bitpool", &bitpool)) + return FALSE; + + if (!(mode = gst_structure_get_string(structure, "mode"))) + return FALSE; + if (!(allocation = gst_structure_get_string(structure, "allocation"))) + return FALSE; + + if (channels == 1 && strcmp(mode, "mono") != 0) + return FALSE; + + sbc->frequency = gst_sbc_parse_rate_to_sbc(rate); + sbc->blocks = gst_sbc_parse_blocks_to_sbc(blocks); + sbc->subbands = gst_sbc_parse_subbands_to_sbc(subbands); + sbc->bitpool = bitpool; + sbc->mode = gst_sbc_parse_mode_to_sbc(mode); + sbc->allocation = gst_sbc_parse_allocation_to_sbc(allocation); + + return TRUE; +} + diff --git a/audio/gstsbcutil.h b/audio/gstsbcutil.h new file mode 100644 index 000000000..b8f626b6b --- /dev/null +++ b/audio/gstsbcutil.h @@ -0,0 +1,75 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include "sbc.h" +#include + +#define SBC_AM_AUTO 0x02 +#define SBC_MODE_AUTO 0x04 + +gint gst_sbc_select_rate_from_list(const GValue *value); + +gint gst_sbc_select_channels_from_range(const GValue *value); + +gint gst_sbc_select_blocks_from_list(const GValue *value); + +gint gst_sbc_select_subbands_from_list(const GValue *value); + +gint gst_sbc_select_bitpool_from_range(const GValue *value); + +const gchar *gst_sbc_get_allocation_from_list(const GValue *value); + +const gchar *gst_sbc_get_mode_from_list(const GValue *value, gint channels); + +gint gst_sbc_get_channel_number(gint mode); +gint gst_sbc_parse_rate_from_sbc(gint frequency); +gint gst_sbc_parse_rate_to_sbc(gint rate); + +gint gst_sbc_parse_subbands_from_sbc(gint subbands); +gint gst_sbc_parse_subbands_to_sbc(gint subbands); + +gint gst_sbc_parse_blocks_from_sbc(gint blocks); +gint gst_sbc_parse_blocks_to_sbc(gint blocks); + +const gchar *gst_sbc_parse_mode_from_sbc(gint mode); +gint gst_sbc_parse_mode_to_sbc(const gchar *mode); + +const gchar *gst_sbc_parse_allocation_from_sbc(gint alloc); +gint gst_sbc_parse_allocation_to_sbc(const gchar *allocation); + +GstCaps* gst_sbc_parse_caps_from_sbc(sbc_t *sbc); + +GstCaps* gst_sbc_util_caps_fixate(GstCaps *caps, gchar** error_message); + +void gst_sbc_util_set_structure_int_param(GstStructure *structure, + const gchar* field, gint field_value, + GValue *value); + +void gst_sbc_util_set_structure_string_param(GstStructure *structure, + const gchar* field, const gchar* field_value, + GValue *value); + +gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps); + diff --git a/audio/headset.c b/audio/headset.c new file mode 100644 index 000000000..2c852d1ab --- /dev/null +++ b/audio/headset.c @@ -0,0 +1,2850 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "logging.h" +#include "device.h" +#include "manager.h" +#include "error.h" +#include "telephony.h" +#include "headset.h" +#include "glib-helper.h" +#include "btio.h" +#include "dbus-common.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#define DC_TIMEOUT 3 + +#define RING_INTERVAL 3 + +#define BUF_SIZE 1024 + +#define HEADSET_GAIN_SPEAKER 'S' +#define HEADSET_GAIN_MICROPHONE 'M' + +static struct { + gboolean telephony_ready; /* Telephony plugin initialized */ + uint32_t features; /* HFP AG features */ + const struct indicator *indicators; /* Available HFP indicators */ + int er_mode; /* Event reporting mode */ + int er_ind; /* Event reporting for indicators */ + int rh; /* Response and Hold state */ + char *number; /* Incoming phone number */ + int number_type; /* Incoming number type */ + guint ring_timer; /* For incoming call indication */ + const char *chld; /* Response to AT+CHLD=? */ +} ag = { + .telephony_ready = FALSE, + .features = 0, + .er_mode = 3, + .er_ind = 0, + .rh = -1, + .number = NULL, + .number_type = 0, + .ring_timer = 0, +}; + +static gboolean sco_hci = TRUE; + +static GSList *active_devices = NULL; + +static char *str_state[] = { + "HEADSET_STATE_DISCONNECTED", + "HEADSET_STATE_CONNECT_IN_PROGRESS", + "HEADSET_STATE_CONNECTED", + "HEADSET_STATE_PLAY_IN_PROGRESS", + "HEADSET_STATE_PLAYING", +}; + +struct headset_state_callback { + headset_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct connect_cb { + unsigned int id; + headset_stream_cb_t cb; + void *cb_data; +}; + +struct pending_connect { + DBusMessage *msg; + DBusPendingCall *call; + GIOChannel *io; + int err; + headset_state_t target_state; + GSList *callbacks; + uint16_t svclass; +}; + +struct headset { + uint32_t hsp_handle; + uint32_t hfp_handle; + + int rfcomm_ch; + + GIOChannel *rfcomm; + GIOChannel *tmp_rfcomm; + GIOChannel *sco; + guint sco_id; + guint dc_id; + + gboolean auto_dc; + + guint dc_timer; + + char buf[BUF_SIZE]; + int data_start; + int data_length; + + gboolean hfp_active; + gboolean search_hfp; + gboolean cli_active; + gboolean cme_enabled; + gboolean cwa_enabled; + gboolean pending_ring; + gboolean inband_ring; + gboolean nrec; + gboolean nrec_req; + + headset_state_t state; + struct pending_connect *pending; + + int sp_gain; + int mic_gain; + + unsigned int hf_features; + headset_lock_t lock; +}; + +struct event { + const char *cmd; + int (*callback) (struct audio_device *device, const char *buf); +}; + +static GSList *headset_callbacks = NULL; + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static DBusHandlerResult error_not_supported(DBusConnection *conn, + DBusMessage *msg) +{ + return error_common_reply(conn, msg, ERROR_INTERFACE ".NotSupported", + "Not supported"); +} + +static DBusHandlerResult error_connection_attempt_failed(DBusConnection *conn, + DBusMessage *msg, int err) +{ + return error_common_reply(conn, msg, + ERROR_INTERFACE ".ConnectionAttemptFailed", + err > 0 ? strerror(err) : "Connection attempt failed"); +} + +static int rfcomm_connect(struct audio_device *device, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id); +static int get_records(struct audio_device *device, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id); + +static void print_ag_features(uint32_t features) +{ + GString *gstr; + char *str; + + if (features == 0) { + debug("HFP AG features: (none)"); + return; + } + + gstr = g_string_new("HFP AG features: "); + + if (features & AG_FEATURE_THREE_WAY_CALLING) + g_string_append(gstr, "\"Three-way calling\" "); + if (features & AG_FEATURE_EC_ANDOR_NR) + g_string_append(gstr, "\"EC and/or NR function\" "); + if (features & AG_FEATURE_VOICE_RECOGNITION) + g_string_append(gstr, "\"Voice recognition function\" "); + if (features & AG_FEATURE_INBAND_RINGTONE) + g_string_append(gstr, "\"In-band ring tone capability\" "); + if (features & AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG) + g_string_append(gstr, "\"Attach a number to a voice tag\" "); + if (features & AG_FEATURE_REJECT_A_CALL) + g_string_append(gstr, "\"Ability to reject a call\" "); + if (features & AG_FEATURE_ENHANCED_CALL_STATUS) + g_string_append(gstr, "\"Enhanced call status\" "); + if (features & AG_FEATURE_ENHANCED_CALL_CONTROL) + g_string_append(gstr, "\"Enhanced call control\" "); + if (features & AG_FEATURE_EXTENDED_ERROR_RESULT_CODES) + g_string_append(gstr, "\"Extended Error Result Codes\" "); + + str = g_string_free(gstr, FALSE); + + debug("%s", str); + + g_free(str); +} + +static void print_hf_features(uint32_t features) +{ + GString *gstr; + char *str; + + if (features == 0) { + debug("HFP HF features: (none)"); + return; + } + + gstr = g_string_new("HFP HF features: "); + + if (features & HF_FEATURE_EC_ANDOR_NR) + g_string_append(gstr, "\"EC and/or NR function\" "); + if (features & HF_FEATURE_CALL_WAITING_AND_3WAY) + g_string_append(gstr, "\"Call waiting and 3-way calling\" "); + if (features & HF_FEATURE_CLI_PRESENTATION) + g_string_append(gstr, "\"CLI presentation capability\" "); + if (features & HF_FEATURE_VOICE_RECOGNITION) + g_string_append(gstr, "\"Voice recognition activation\" "); + if (features & HF_FEATURE_REMOTE_VOLUME_CONTROL) + g_string_append(gstr, "\"Remote volume control\" "); + if (features & HF_FEATURE_ENHANCED_CALL_STATUS) + g_string_append(gstr, "\"Enhanced call status\" "); + if (features & HF_FEATURE_ENHANCED_CALL_CONTROL) + g_string_append(gstr, "\"Enhanced call control\" "); + + str = g_string_free(gstr, FALSE); + + debug("%s", str); + + g_free(str); +} + +static const char *state2str(headset_state_t state) +{ + switch (state) { + case HEADSET_STATE_DISCONNECTED: + return "disconnected"; + case HEADSET_STATE_CONNECT_IN_PROGRESS: + return "connecting"; + case HEADSET_STATE_CONNECTED: + case HEADSET_STATE_PLAY_IN_PROGRESS: + return "connected"; + case HEADSET_STATE_PLAYING: + return "playing"; + } + + return NULL; +} + +static int headset_send_valist(struct headset *hs, char *format, va_list ap) +{ + char rsp[BUF_SIZE]; + ssize_t total_written, count; + int fd; + + count = vsnprintf(rsp, sizeof(rsp), format, ap); + + if (count < 0) + return -EINVAL; + + if (!hs->rfcomm) { + error("headset_send: the headset is not connected"); + return -EIO; + } + + total_written = 0; + fd = g_io_channel_unix_get_fd(hs->rfcomm); + + while (total_written < count) { + ssize_t written; + + written = write(fd, rsp + total_written, + count - total_written); + if (written < 0) + return -errno; + + total_written += written; + } + + return 0; +} + +static int headset_send(struct headset *hs, char *format, ...) +{ + va_list ap; + int ret; + + va_start(ap, format); + ret = headset_send_valist(hs, format, ap); + va_end(ap); + + return ret; +} + +static int supported_features(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + int err; + + if (strlen(buf) < 9) + return -EINVAL; + + hs->hf_features = strtoul(&buf[8], NULL, 10); + + print_hf_features(hs->hf_features); + + err = headset_send(hs, "\r\n+BRSF: %u\r\n", ag.features); + if (err < 0) + return err; + + return headset_send(hs, "\r\nOK\r\n"); +} + +static char *indicator_ranges(const struct indicator *indicators) +{ + int i; + GString *gstr; + + gstr = g_string_new("\r\n+CIND: "); + + for (i = 0; indicators[i].desc != NULL; i++) { + if (i == 0) + g_string_append_printf(gstr, "(\"%s\",(%s))", + indicators[i].desc, + indicators[i].range); + else + g_string_append_printf(gstr, ",(\"%s\",(%s))", + indicators[i].desc, + indicators[i].range); + } + + g_string_append(gstr, "\r\n"); + + return g_string_free(gstr, FALSE); +} + +static char *indicator_values(const struct indicator *indicators) +{ + int i; + GString *gstr; + + gstr = g_string_new("\r\n+CIND: "); + + for (i = 0; indicators[i].desc != NULL; i++) { + if (i == 0) + g_string_append_printf(gstr, "%d", indicators[i].val); + else + g_string_append_printf(gstr, ",%d", indicators[i].val); + } + + g_string_append(gstr, "\r\n"); + + return g_string_free(gstr, FALSE); +} + +static int report_indicators(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + int err; + char *str; + + if (strlen(buf) < 8) + return -EINVAL; + + if (ag.indicators == NULL) { + error("HFP AG indicators not initialized"); + return headset_send(hs, "\r\nERROR\r\n"); + } + + if (buf[7] == '=') + str = indicator_ranges(ag.indicators); + else + str = indicator_values(ag.indicators); + + err = headset_send(hs, str); + + g_free(str); + + if (err < 0) + return err; + + return headset_send(hs, "\r\nOK\r\n"); +} + +static void pending_connect_complete(struct connect_cb *cb, struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (hs->pending->err) + cb->cb(NULL, cb->cb_data); + else + cb->cb(dev, cb->cb_data); +} + +static void pending_connect_finalize(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + + if (p == NULL) + return; + + if (p->svclass) + bt_cancel_discovery(&dev->src, &dev->dst); + + g_slist_foreach(p->callbacks, (GFunc) pending_connect_complete, dev); + + g_slist_foreach(p->callbacks, (GFunc) g_free, NULL); + g_slist_free(p->callbacks); + + if (p->io) { + g_io_channel_shutdown(p->io, TRUE, NULL); + g_io_channel_unref(p->io); + } + + if (p->msg) + dbus_message_unref(p->msg); + + if (p->call) { + dbus_pending_call_cancel(p->call); + dbus_pending_call_unref(p->call); + } + + g_free(p); + + hs->pending = NULL; +} + +static void pending_connect_init(struct headset *hs, headset_state_t target_state) +{ + if (hs->pending) { + if (hs->pending->target_state < target_state) + hs->pending->target_state = target_state; + return; + } + + hs->pending = g_new0(struct pending_connect, 1); + hs->pending->target_state = target_state; +} + +static unsigned int connect_cb_new(struct headset *hs, + headset_state_t target_state, + headset_stream_cb_t func, + void *user_data) +{ + struct connect_cb *cb; + unsigned int free_cb_id = 1; + + pending_connect_init(hs, target_state); + + if (!func) + return 0; + + cb = g_new(struct connect_cb, 1); + + cb->cb = func; + cb->cb_data = user_data; + cb->id = free_cb_id++; + + hs->pending->callbacks = g_slist_append(hs->pending->callbacks, + cb); + + return cb->id; +} + +static void send_foreach_headset(GSList *devices, + int (*cmp) (struct headset *hs), + char *format, ...) +{ + GSList *l; + va_list ap; + + for (l = devices; l != NULL; l = l->next) { + struct audio_device *device = l->data; + struct headset *hs = device->headset; + int ret; + + assert(hs != NULL); + + if (cmp && cmp(hs) != 0) + continue; + + va_start(ap, format); + ret = headset_send_valist(hs, format, ap); + if (ret < 0) + error("Failed to send to headset: %s (%d)", + strerror(-ret), -ret); + va_end(ap); + } +} + +static int cli_cmp(struct headset *hs) +{ + if (!hs->hfp_active) + return -1; + + if (hs->cli_active) + return 0; + else + return -1; +} + +static gboolean ring_timer_cb(gpointer data) +{ + send_foreach_headset(active_devices, NULL, "\r\nRING\r\n"); + + if (ag.number) + send_foreach_headset(active_devices, cli_cmp, + "\r\n+CLIP: \"%s\",%d\r\n", + ag.number, ag.number_type); + + return TRUE; +} + +static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + int sk; + struct audio_device *dev = user_data; + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + + if (err) { + error("%s", err->message); + + if (p && p->msg) + error_connection_attempt_failed(dev->conn, p->msg, p->err); + + pending_connect_finalize(dev); + + if (hs->rfcomm) + headset_set_state(dev, HEADSET_STATE_CONNECTED); + else + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); + + return; + } + + debug("SCO socket opened for headset %s", dev->path); + + sk = g_io_channel_unix_get_fd(chan); + + debug("SCO fd=%d", sk); + + if (p) { + p->io = NULL; + if (p->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(dev->conn, reply); + } + + pending_connect_finalize(dev); + } + + fcntl(sk, F_SETFL, 0); + + headset_set_state(dev, HEADSET_STATE_PLAYING); + + if (hs->pending_ring) { + ring_timer_cb(NULL); + ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, + ring_timer_cb, + NULL); + hs->pending_ring = FALSE; + } +} + +static int sco_connect(struct audio_device *dev, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id) +{ + struct headset *hs = dev->headset; + GError *err = NULL; + GIOChannel *io; + + if (hs->state != HEADSET_STATE_CONNECTED) + return -EINVAL; + + io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return -EIO; + } + + hs->sco = io; + + headset_set_state(dev, HEADSET_STATE_PLAY_IN_PROGRESS); + + pending_connect_init(hs, HEADSET_STATE_PLAYING); + + if (cb) { + unsigned int id = connect_cb_new(hs, HEADSET_STATE_PLAYING, + cb, user_data); + if (cb_id) + *cb_id = id; + } + + return 0; +} + +static int hfp_cmp(struct headset *hs) +{ + if (hs->hfp_active) + return 0; + else + return -1; +} + +static void hfp_slc_complete(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + + debug("HFP Service Level Connection established"); + + headset_set_state(dev, HEADSET_STATE_CONNECTED); + + if (p == NULL) + return; + + if (p->target_state == HEADSET_STATE_CONNECTED) { + if (p->msg) { + DBusMessage *reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(dev->conn, reply); + } + pending_connect_finalize(dev); + return; + } + + p->err = sco_connect(dev, NULL, NULL, NULL); + if (p->err < 0) { + if (p->msg) + error_connection_attempt_failed(dev->conn, p->msg, p->err); + pending_connect_finalize(dev); + } +} + +static int telephony_generic_rsp(struct audio_device *device, cme_error_t err) +{ + struct headset *hs = device->headset; + + if (err != CME_ERROR_NONE) { + if (hs->cme_enabled) + return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err); + else + return headset_send(hs, "\r\nERROR\r\n"); + } + + return headset_send(hs, "\r\nOK\r\n"); +} + +int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err) +{ + struct audio_device *device = telephony_device; + struct headset *hs = device->headset; + int ret; + + if (err != CME_ERROR_NONE) + return telephony_generic_rsp(telephony_device, err); + + ret = headset_send(hs, "\r\nOK\r\n"); + if (ret < 0) + return ret; + + if (hs->state != HEADSET_STATE_CONNECT_IN_PROGRESS) + return 0; + + if (hs->hf_features & HF_FEATURE_CALL_WAITING_AND_3WAY && + ag.features & AG_FEATURE_THREE_WAY_CALLING) + return 0; + + hfp_slc_complete(device); + + return 0; +} + +static int event_reporting(struct audio_device *dev, const char *buf) +{ + char **tokens; /* , , , , */ + + if (strlen(buf) < 13) + return -EINVAL; + + tokens = g_strsplit(&buf[8], ",", 5); + if (g_strv_length(tokens) < 4) { + g_strfreev(tokens); + return -EINVAL; + } + + ag.er_mode = atoi(tokens[0]); + ag.er_ind = atoi(tokens[3]); + + g_strfreev(tokens); + tokens = NULL; + + debug("Event reporting (CMER): mode=%d, ind=%d", + ag.er_mode, ag.er_ind); + + switch (ag.er_ind) { + case 0: + case 1: + telephony_event_reporting_req(dev, ag.er_ind); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int call_hold(struct audio_device *dev, const char *buf) +{ + struct headset *hs = dev->headset; + int err; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] != '?') { + telephony_call_hold_req(dev, &buf[8]); + return 0; + } + + err = headset_send(hs, "\r\n+CHLD: (%s)\r\n", ag.chld); + if (err < 0) + return err; + + err = headset_send(hs, "\r\nOK\r\n"); + if (err < 0) + return err; + + if (hs->state != HEADSET_STATE_CONNECT_IN_PROGRESS) + return 0; + + hfp_slc_complete(dev); + + return 0; +} + +int telephony_key_press_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int key_press(struct audio_device *device, const char *buf) +{ + if (strlen(buf) < 9) + return -EINVAL; + + g_dbus_emit_signal(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, "AnswerRequested", + DBUS_TYPE_INVALID); + + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } + + telephony_key_press_req(device, &buf[8]); + + return 0; +} + +int telephony_answer_call_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int answer_call(struct audio_device *device, const char *buf) +{ + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } + + if (ag.number) { + g_free(ag.number); + ag.number = NULL; + } + + telephony_answer_call_req(device); + + return 0; +} + +int telephony_terminate_call_rsp(void *telephony_device, + cme_error_t err) +{ + struct audio_device *device = telephony_device; + struct headset *hs = device->headset; + + if (err != CME_ERROR_NONE) + return telephony_generic_rsp(telephony_device, err); + + g_dbus_emit_signal(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, "CallTerminated", + DBUS_TYPE_INVALID); + + return headset_send(hs, "\r\nOK\r\n"); +} + +static int terminate_call(struct audio_device *device, const char *buf) +{ + if (ag.number) { + g_free(ag.number); + ag.number = NULL; + } + + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } + + telephony_terminate_call_req(device); + + return 0; +} + +static int cli_notification(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + + if (strlen(buf) < 9) + return -EINVAL; + + hs->cli_active = buf[8] == '1' ? TRUE : FALSE; + + return headset_send(hs, "\r\nOK\r\n"); +} + +int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int response_and_hold(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + + if (strlen(buf) < 8) + return -EINVAL; + + if (buf[7] == '=') { + telephony_response_and_hold_req(device, atoi(&buf[8]) < 0); + return 0; + } + + if (ag.rh >= 0) + headset_send(hs, "\r\n+BTRH: %d\r\n", ag.rh); + + return headset_send(hs, "\r\nOK\r\n", ag.rh); +} + +int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int last_dialed_number(struct audio_device *device, const char *buf) +{ + telephony_last_dialed_number_req(device); + + return 0; +} + +int telephony_dial_number_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int dial_number(struct audio_device *device, const char *buf) +{ + char number[BUF_SIZE]; + size_t buf_len; + + buf_len = strlen(buf); + + if (buf[buf_len - 1] != ';') { + debug("Rejecting non-voice call dial request"); + return -EINVAL; + } + + memset(number, 0, sizeof(number)); + strncpy(number, &buf[3], buf_len - 4); + + telephony_dial_number_req(device, number); + + return 0; +} + +static int signal_gain_setting(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + const char *property; + const char *name; + dbus_uint16_t gain; + + if (strlen(buf) < 8) { + error("Too short string for Gain setting"); + return -EINVAL; + } + + gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10); + + if (gain > 15) { + error("Invalid gain value received: %u", gain); + return -EINVAL; + } + + switch (buf[5]) { + case HEADSET_GAIN_SPEAKER: + if (hs->sp_gain == gain) + goto ok; + name = "SpeakerGainChanged"; + property = "SpeakerGain"; + hs->sp_gain = gain; + break; + case HEADSET_GAIN_MICROPHONE: + if (hs->mic_gain == gain) + goto ok; + name = "MicrophoneGainChanged"; + property = "MicrophoneGain"; + hs->mic_gain = gain; + break; + default: + error("Unknown gain setting"); + return -EINVAL; + } + + g_dbus_emit_signal(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, name, + DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + + emit_property_changed(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, property, + DBUS_TYPE_UINT16, &gain); + +ok: + return headset_send(hs, "\r\nOK\r\n"); +} + +int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int dtmf_tone(struct audio_device *device, const char *buf) +{ + if (strlen(buf) < 8) { + error("Too short string for DTMF tone"); + return -EINVAL; + } + + telephony_transmit_dtmf_req(device, buf[7]); + + return 0; +} + +int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int subscriber_number(struct audio_device *device, const char *buf) +{ + telephony_subscriber_number_req(device); + + return 0; +} + +int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +static int list_current_calls(struct audio_device *device, const char *buf) +{ + telephony_list_current_calls_req(device); + + return 0; +} + +static int extended_errors(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] == '1') { + hs->cme_enabled = TRUE; + debug("CME errors enabled for headset %p", hs); + } else { + hs->cme_enabled = FALSE; + debug("CME errors disabled for headset %p", hs); + } + + return headset_send(hs, "\r\nOK\r\n"); +} + +static int call_waiting_notify(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] == '1') { + hs->cwa_enabled = TRUE; + debug("Call waiting notification enabled for headset %p", hs); + } else { + hs->cwa_enabled = FALSE; + debug("Call waiting notification disabled for headset %p", hs); + } + + return headset_send(hs, "\r\nOK\r\n"); +} + +int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +int telephony_call_hold_rsp(void *telephony_device, cme_error_t err) +{ + return telephony_generic_rsp(telephony_device, err); +} + +int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err) +{ + struct audio_device *device = telephony_device; + struct headset *hs = device->headset; + + if (err == CME_ERROR_NONE) + hs->nrec = hs->nrec_req; + + return telephony_generic_rsp(telephony_device, err); +} + +int telephony_operator_selection_ind(int mode, const char *oper) +{ + if (!active_devices) + return -ENODEV; + + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+COPS: %d,0,\"%s\"\r\n", + mode, oper); + return 0; +} + +static int operator_selection(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + + if (strlen(buf) < 8) + return -EINVAL; + + switch (buf[7]) { + case '?': + telephony_operator_selection_req(device); + break; + case '=': + return headset_send(hs, "\r\nOK\r\n"); + default: + return -EINVAL; + } + + return 0; +} + +static int nr_and_ec(struct audio_device *device, const char *buf) +{ + struct headset *hs = device->headset; + + if (strlen(buf) < 9) + return -EINVAL; + + if (buf[8] == '0') + hs->nrec_req = FALSE; + else + hs->nrec_req = TRUE; + + telephony_nr_and_ec_req(device, hs->nrec_req); + + return 0; +} + +static struct event event_callbacks[] = { + { "ATA", answer_call }, + { "ATD", dial_number }, + { "AT+VG", signal_gain_setting }, + { "AT+BRSF", supported_features }, + { "AT+CIND", report_indicators }, + { "AT+CMER", event_reporting }, + { "AT+CHLD", call_hold }, + { "AT+CHUP", terminate_call }, + { "AT+CKPD", key_press }, + { "AT+CLIP", cli_notification }, + { "AT+BTRH", response_and_hold }, + { "AT+BLDN", last_dialed_number }, + { "AT+VTS", dtmf_tone }, + { "AT+CNUM", subscriber_number }, + { "AT+CLCC", list_current_calls }, + { "AT+CMEE", extended_errors }, + { "AT+CCWA", call_waiting_notify }, + { "AT+COPS", operator_selection }, + { "AT+NREC", nr_and_ec }, + { 0 } +}; + +static int handle_event(struct audio_device *device, const char *buf) +{ + struct event *ev; + + debug("Received %s", buf); + + for (ev = event_callbacks; ev->cmd; ev++) { + if (!strncmp(buf, ev->cmd, strlen(ev->cmd))) + return ev->callback(device, buf); + } + + return -EINVAL; +} + +static void close_sco(struct audio_device *device) +{ + struct headset *hs = device->headset; + + if (hs->sco) { + int sock = g_io_channel_unix_get_fd(hs->sco); + shutdown(sock, SHUT_RDWR); + g_io_channel_shutdown(hs->sco, TRUE, NULL); + g_io_channel_unref(hs->sco); + hs->sco = NULL; + } + + if (hs->sco_id) { + g_source_remove(hs->sco_id); + hs->sco_id = 0; + } +} + +static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *device) +{ + struct headset *hs; + unsigned char buf[BUF_SIZE]; + gsize bytes_read = 0; + gsize free_space; + + if (cond & G_IO_NVAL) + return FALSE; + + hs = device->headset; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + debug("ERR or HUP on RFCOMM socket"); + goto failed; + } + + if (g_io_channel_read(chan, (gchar *) buf, sizeof(buf) - 1, + &bytes_read) != G_IO_ERROR_NONE) + return TRUE; + + free_space = sizeof(hs->buf) - hs->data_start - hs->data_length - 1; + + if (free_space < bytes_read) { + /* Very likely that the HS is sending us garbage so + * just ignore the data and disconnect */ + error("Too much data to fit incomming buffer"); + goto failed; + } + + memcpy(&hs->buf[hs->data_start], buf, bytes_read); + hs->data_length += bytes_read; + + /* Make sure the data is null terminated so we can use string + * functions */ + hs->buf[hs->data_start + hs->data_length] = '\0'; + + while (hs->data_length > 0) { + char *cr; + int err; + off_t cmd_len; + + cr = strchr(&hs->buf[hs->data_start], '\r'); + if (!cr) + break; + + cmd_len = 1 + (off_t) cr - (off_t) &hs->buf[hs->data_start]; + *cr = '\0'; + + if (cmd_len > 1) + err = handle_event(device, &hs->buf[hs->data_start]); + else + /* Silently skip empty commands */ + err = 0; + + if (err == -EINVAL) { + error("Badly formated or unrecognized command: %s", + &hs->buf[hs->data_start]); + err = headset_send(hs, "\r\nERROR\r\n"); + } else if (err < 0) + error("Error handling command %s: %s (%d)", + &hs->buf[hs->data_start], + strerror(-err), -err); + + hs->data_start += cmd_len; + hs->data_length -= cmd_len; + + if (!hs->data_length) + hs->data_start = 0; + } + + return TRUE; + +failed: + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + + return FALSE; +} + +static gboolean sco_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *device) +{ + if (cond & G_IO_NVAL) + return FALSE; + + error("Audio connection got disconnected"); + + headset_set_state(device, HEADSET_STATE_CONNECTED); + + return FALSE; +} + +void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct audio_device *dev = user_data; + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + char hs_address[18]; + + if (err) { + error("%s", err->message); + goto failed; + } + + /* For HFP telephony isn't ready just disconnect */ + if (hs->hfp_active && !ag.telephony_ready) { + error("Unable to accept HFP connection since the telephony " + "subsystem isn't initialized"); + goto failed; + } + + hs->rfcomm = hs->tmp_rfcomm; + hs->tmp_rfcomm = NULL; + + ba2str(&dev->dst, hs_address); + + if (p) + p->io = NULL; + else + hs->auto_dc = FALSE; + + g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL, + (GIOFunc) rfcomm_io_cb, dev); + + debug("%s: Connected to %s", dev->path, hs_address); + + /* In HFP mode wait for Service Level Connection */ + if (hs->hfp_active) + return; + + headset_set_state(dev, HEADSET_STATE_CONNECTED); + + if (p && p->target_state == HEADSET_STATE_PLAYING) { + p->err = sco_connect(dev, NULL, NULL, NULL); + if (p->err < 0) + goto failed; + return; + } + + if (p && p->msg) { + DBusMessage *reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(dev->conn, reply); + } + + pending_connect_finalize(dev); + + return; + +failed: + if (p && p->msg) + error_connection_attempt_failed(dev->conn, p->msg, p->err); + pending_connect_finalize(dev); + if (hs->rfcomm) + headset_set_state(dev, HEADSET_STATE_CONNECTED); + else + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); +} + +static int headset_set_channel(struct headset *headset, + const sdp_record_t *record, uint16_t svc) +{ + int ch; + sdp_list_t *protos; + + if (sdp_get_access_protos(record, &protos) < 0) { + error("Unable to get access protos from headset record"); + return -1; + } + + 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); + + if (ch <= 0) { + error("Unable to get RFCOMM channel from Headset record"); + return -1; + } + + headset->rfcomm_ch = ch; + + if (svc == HANDSFREE_SVCLASS_ID) { + headset->hfp_handle = record->handle; + debug("Discovered Handsfree service on channel %d", ch); + } else { + headset->hsp_handle = record->handle; + debug("Discovered Headset service on channel %d", ch); + } + + return 0; +} + +static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct audio_device *dev = user_data; + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + sdp_record_t *record = NULL; + sdp_list_t *r; + + assert(hs->pending != NULL); + + if (err < 0) { + error("Unable to get service record: %s (%d)", + strerror(-err), -err); + p->err = -err; + error_connection_attempt_failed(dev->conn, p->msg, p->err); + goto failed; + } + + if (!recs || !recs->data) { + error("No records found"); + goto failed_not_supported; + } + + for (r = recs; r != NULL; r = r->next) { + sdp_list_t *classes; + uuid_t uuid; + + record = r->data; + + if (sdp_get_service_classes(record, &classes) < 0) { + error("Unable to get service classes from record"); + continue; + } + + memcpy(&uuid, classes->data, sizeof(uuid)); + + sdp_list_free(classes, free); + + if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16) { + error("Not a 16 bit UUID"); + continue; + } + + if (uuid.value.uuid16 == p->svclass) + break; + } + + if (r == NULL) { + error("No record found with UUID 0x%04x", p->svclass); + goto failed_not_supported; + } + + if (headset_set_channel(hs, record, p->svclass) < 0) { + error("Unable to extract RFCOMM channel from service record"); + goto failed_not_supported; + } + + /* Set svclass to 0 so we can easily check that SDP is no-longer + * going on (to know if bt_cancel_discovery needs to be called) */ + p->svclass = 0; + + err = rfcomm_connect(dev, NULL, NULL, NULL); + if (err < 0) { + error("Unable to connect: %s (%d)", strerror(-err), -err); + p->err = -err; + error_connection_attempt_failed(dev->conn, p->msg, p->err); + goto failed; + } + + return; + +failed_not_supported: + if (p->svclass == HANDSFREE_SVCLASS_ID && + get_records(dev, NULL, NULL, NULL) == 0) + return; + if (p->msg) + error_not_supported(dev->conn, p->msg); +failed: + p->svclass = 0; + pending_connect_finalize(dev); + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); +} + +static int get_records(struct audio_device *device, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id) +{ + struct headset *hs = device->headset; + uint16_t svclass; + uuid_t uuid; + int err; + + if (hs->pending && hs->pending->svclass == HANDSFREE_SVCLASS_ID) + svclass = HEADSET_SVCLASS_ID; + else + svclass = hs->search_hfp ? HANDSFREE_SVCLASS_ID : + HEADSET_SVCLASS_ID; + + sdp_uuid16_create(&uuid, svclass); + + err = bt_search_service(&device->src, &device->dst, &uuid, + get_record_cb, device, NULL); + if (err < 0) + return err; + + if (hs->pending) { + hs->pending->svclass = svclass; + return 0; + } + + headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS); + + pending_connect_init(hs, HEADSET_STATE_CONNECTED); + + hs->pending->svclass = svclass; + + if (cb) { + unsigned int id; + id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, + cb, user_data); + if (cb_id) + *cb_id = id; + } + + return 0; +} + +static int rfcomm_connect(struct audio_device *dev, headset_stream_cb_t cb, + void *user_data, unsigned int *cb_id) +{ + struct headset *hs = dev->headset; + char address[18]; + GError *err = NULL; + + if (!manager_allow_headset_connection(dev)) + return -ECONNREFUSED; + + if (hs->rfcomm_ch < 0) + return get_records(dev, cb, user_data, cb_id); + + ba2str(&dev->dst, address); + + debug("%s: Connecting to %s channel %d", dev->path, address, + hs->rfcomm_ch); + + hs->tmp_rfcomm = bt_io_connect(BT_IO_RFCOMM, headset_connect_cb, dev, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_CHANNEL, hs->rfcomm_ch, + BT_IO_OPT_INVALID); + + hs->rfcomm_ch = -1; + + if (!hs->tmp_rfcomm) { + error("%s", err->message); + g_error_free(err); + return -EIO; + } + + hs->hfp_active = hs->hfp_handle != 0 ? TRUE : FALSE; + + headset_set_state(dev, HEADSET_STATE_CONNECT_IN_PROGRESS); + + pending_connect_init(hs, HEADSET_STATE_CONNECTED); + + if (cb) { + unsigned int id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, + cb, user_data); + if (cb_id) + *cb_id = id; + } + + return 0; +} + +static DBusMessage *hs_stop(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply = NULL; + + if (hs->state < HEADSET_STATE_PLAY_IN_PROGRESS) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + headset_set_state(device, HEADSET_STATE_CONNECTED); + + return reply; +} + +static DBusMessage *hs_is_playing(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply; + dbus_bool_t playing; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + playing = (hs->state == HEADSET_STATE_PLAYING); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &playing, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *hs_disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + char hs_address[18]; + + if (hs->state == HEADSET_STATE_DISCONNECTED) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + headset_shutdown(device); + ba2str(&device->dst, hs_address); + info("Disconnected from %s, %s", hs_address, device->path); + + return dbus_message_new_method_return(msg); + +} + +static DBusMessage *hs_is_connected(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + DBusMessage *reply; + dbus_bool_t connected; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + connected = (device->headset->state >= HEADSET_STATE_CONNECTED); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *hs_connect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + int err; + + if (hs->state == HEADSET_STATE_CONNECT_IN_PROGRESS) + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", + "Connect in Progress"); + else if (hs->state > HEADSET_STATE_CONNECT_IN_PROGRESS) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".AlreadyConnected", + "Already Connected"); + + if (hs->hfp_handle && !ag.telephony_ready) + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", + "Telephony subsystem not ready"); + + device->auto_connect = FALSE; + + err = rfcomm_connect(device, NULL, NULL, NULL); + if (err == -ECONNREFUSED) + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAllowed", + "Too many connected devices"); + else if (err < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".ConnectAttemptFailed", + "Connect Attempt Failed"); + + hs->auto_dc = FALSE; + + hs->pending->msg = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *hs_ring(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply = NULL; + int err; + + if (hs->state < HEADSET_STATE_CONNECTED) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (ag.ring_timer) { + debug("IndicateCall received when already indicating"); + goto done; + } + + err = headset_send(hs, "\r\nRING\r\n"); + if (err < 0) { + dbus_message_unref(reply); + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(-err)); + } + + ring_timer_cb(NULL); + ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb, + NULL); + +done: + return reply; +} + +static DBusMessage *hs_cancel_call(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply = NULL; + + if (hs->state < HEADSET_STATE_CONNECTED) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } else + debug("Got CancelCall method call but no call is active"); + + return reply; +} + +static DBusMessage *hs_play(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + int err; + + if (sco_hci) { + error("Refusing Headset.Play() because SCO HCI routing " + "is enabled"); + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", + "Operation not Available"); + } + + switch (hs->state) { + case HEADSET_STATE_DISCONNECTED: + case HEADSET_STATE_CONNECT_IN_PROGRESS: + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + case HEADSET_STATE_PLAY_IN_PROGRESS: + if (hs->pending && hs->pending->msg == NULL) { + hs->pending->msg = dbus_message_ref(msg); + return NULL; + } + return g_dbus_create_error(msg, ERROR_INTERFACE + ".InProgress", + "Play in Progress"); + case HEADSET_STATE_PLAYING: + return g_dbus_create_error(msg, ERROR_INTERFACE + ".AlreadyConnected", + "Device Already Connected"); + case HEADSET_STATE_CONNECTED: + default: + break; + } + + err = sco_connect(device, NULL, NULL, NULL); + if (err < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(-err)); + + hs->pending->msg = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *hs_get_speaker_gain(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply; + dbus_uint16_t gain; + + if (hs->state < HEADSET_STATE_CONNECTED || hs->sp_gain < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", + "Operation not Available"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + gain = (dbus_uint16_t) hs->sp_gain; + + dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *hs_get_mic_gain(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply; + dbus_uint16_t gain; + + if (hs->state < HEADSET_STATE_CONNECTED || hs->mic_gain < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", + "Operation not Available"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + gain = (dbus_uint16_t) hs->mic_gain; + + dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *hs_set_gain(DBusConnection *conn, + DBusMessage *msg, + void *data, uint16_t gain, + char type) +{ + struct audio_device *device = data; + struct headset *hs = device->headset; + DBusMessage *reply; + int err; + + if (hs->state < HEADSET_STATE_CONNECTED) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + if (gain > 15) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".InvalidArgument", + "Must be less than or equal to 15"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (hs->state != HEADSET_STATE_PLAYING) + goto done; + + err = headset_send(hs, "\r\n+VG%c=%u\r\n", type, gain); + if (err < 0) { + dbus_message_unref(reply); + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(-err)); + } + +done: + if (type == HEADSET_GAIN_SPEAKER) { + hs->sp_gain = gain; + g_dbus_emit_signal(conn, device->path, + AUDIO_HEADSET_INTERFACE, + "SpeakerGainChanged", + DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + } else { + hs->mic_gain = gain; + g_dbus_emit_signal(conn, device->path, + AUDIO_HEADSET_INTERFACE, + "MicrophoneGainChanged", + DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + } + + return reply; +} + +static DBusMessage *hs_set_speaker_gain(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + uint16_t gain; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID)) + return NULL; + + return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_SPEAKER); +} + +static DBusMessage *hs_set_mic_gain(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + uint16_t gain; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID)) + return NULL; + + return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_MICROPHONE); +} + +static DBusMessage *hs_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + gboolean value; + const char *state; + + 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); + + + /* Playing */ + value = (device->headset->state == HEADSET_STATE_PLAYING); + dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value); + + /* State */ + state = state2str(device->headset->state); + if (state) + dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state); + + /* Connected */ + value = (device->headset->state >= HEADSET_STATE_CONNECTED); + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); + + if (!value) + goto done; + + /* SpeakerGain */ + dict_append_entry(&dict, "SpeakerGain", + DBUS_TYPE_UINT16, &device->headset->sp_gain); + + /* MicrophoneGain */ + dict_append_entry(&dict, "MicrophoneGain", + DBUS_TYPE_UINT16, &device->headset->mic_gain); + +done: + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *hs_set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *property; + DBusMessageIter iter; + DBusMessageIter sub; + uint16_t gain; + + if (!dbus_message_iter_init(msg, &iter)) + return invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &property); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return invalid_args(msg); + dbus_message_iter_recurse(&iter, &sub); + + if (g_str_equal("SpeakerGain", property)) { + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16) + return invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &gain); + return hs_set_gain(conn, msg, data, gain, + HEADSET_GAIN_SPEAKER); + } else if (g_str_equal("MicrophoneGain", property)) { + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16) + return invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &gain); + return hs_set_gain(conn, msg, data, gain, + HEADSET_GAIN_MICROPHONE); + } + + return invalid_args(msg); +} +static GDBusMethodTable headset_methods[] = { + { "Connect", "", "", hs_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", hs_disconnect }, + { "IsConnected", "", "b", hs_is_connected }, + { "IndicateCall", "", "", hs_ring }, + { "CancelCall", "", "", hs_cancel_call }, + { "Play", "", "", hs_play, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Stop", "", "", hs_stop }, + { "IsPlaying", "", "b", hs_is_playing, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetSpeakerGain", "", "q", hs_get_speaker_gain, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetMicrophoneGain", "", "q", hs_get_mic_gain, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "SetSpeakerGain", "q", "", hs_set_speaker_gain, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "SetMicrophoneGain", "q", "", hs_set_mic_gain, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetProperties", "", "a{sv}",hs_get_properties }, + { "SetProperty", "sv", "", hs_set_property }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable headset_signals[] = { + { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "AnswerRequested", "" }, + { "Stopped", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Playing", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "SpeakerGainChanged", "q", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "MicrophoneGainChanged", "q", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "CallTerminated", "" }, + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +void headset_update(struct audio_device *dev, uint16_t svc, + const char *uuidstr) +{ + struct headset *headset = dev->headset; + const sdp_record_t *record; + + record = btd_device_get_record(dev->btd_dev, uuidstr); + if (!record) + return; + + switch (svc) { + case HANDSFREE_SVCLASS_ID: + if (headset->hfp_handle && + (headset->hfp_handle != record->handle)) { + error("More than one HFP record found on device"); + return; + } + + headset->hfp_handle = record->handle; + break; + + case HEADSET_SVCLASS_ID: + if (headset->hsp_handle && + (headset->hsp_handle != record->handle)) { + error("More than one HSP record found on device"); + return; + } + + headset->hsp_handle = record->handle; + + /* Ignore this record if we already have access to HFP */ + if (headset->hfp_handle) + return; + + break; + + default: + debug("Invalid record passed to headset_update"); + return; + } +} + +static int headset_close_rfcomm(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + GIOChannel *rfcomm = hs->tmp_rfcomm ? hs->tmp_rfcomm : hs->rfcomm; + + if (rfcomm) { + g_io_channel_shutdown(rfcomm, TRUE, NULL); + g_io_channel_unref(rfcomm); + hs->tmp_rfcomm = NULL; + hs->rfcomm = NULL; + } + + hs->data_start = 0; + hs->data_length = 0; + + hs->nrec = TRUE; + + return 0; +} + +static void headset_free(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (hs->dc_timer) { + g_source_remove(hs->dc_timer); + hs->dc_timer = 0; + } + + if (hs->dc_id) + device_remove_disconnect_watch(dev->btd_dev, hs->dc_id); + + close_sco(dev); + + headset_close_rfcomm(dev); + + g_free(hs); + dev->headset = NULL; +} + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + struct headset *hs = dev->headset; + + if (hs->state > HEADSET_STATE_DISCONNECTED) { + debug("Headset unregistered while device was connected!"); + headset_shutdown(dev); + } + + debug("Unregistered interface %s on path %s", + AUDIO_HEADSET_INTERFACE, dev->path); + + headset_free(dev); +} + +void headset_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE); +} + +struct headset *headset_init(struct audio_device *dev, uint16_t svc, + const char *uuidstr) +{ + struct headset *hs; + const sdp_record_t *record; + + hs = g_new0(struct headset, 1); + hs->rfcomm_ch = -1; + hs->sp_gain = -1; + hs->mic_gain = -1; + hs->search_hfp = server_is_enabled(&dev->src, HANDSFREE_SVCLASS_ID); + hs->hfp_active = FALSE; + hs->cli_active = FALSE; + hs->nrec = TRUE; + + record = btd_device_get_record(dev->btd_dev, uuidstr); + if (!record) + goto register_iface; + + switch (svc) { + case HANDSFREE_SVCLASS_ID: + hs->hfp_handle = record->handle; + break; + + case HEADSET_SVCLASS_ID: + hs->hsp_handle = record->handle; + break; + + default: + debug("Invalid record passed to headset_init"); + g_free(hs); + return NULL; + } + +register_iface: + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + headset_methods, headset_signals, NULL, + dev, path_unregister)) { + g_free(hs); + return NULL; + } + + debug("Registered interface %s on path %s", + AUDIO_HEADSET_INTERFACE, dev->path); + + return hs; +} + +uint32_t headset_config_init(GKeyFile *config) +{ + GError *err = NULL; + char *str; + + /* Use the default values if there is no config file */ + if (config == NULL) + return ag.features; + + str = g_key_file_get_string(config, "General", "SCORouting", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else { + if (strcmp(str, "PCM") == 0) + sco_hci = FALSE; + else if (strcmp(str, "HCI") == 0) + sco_hci = TRUE; + else + error("Invalid Headset Routing value: %s", str); + g_free(str); + } + + return ag.features; +} + +static gboolean hs_dc_timeout(struct audio_device *dev) +{ + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); + return FALSE; +} + +gboolean headset_cancel_stream(struct audio_device *dev, unsigned int id) +{ + struct headset *hs = dev->headset; + struct pending_connect *p = hs->pending; + GSList *l; + struct connect_cb *cb = NULL; + + if (!p) + return FALSE; + + for (l = p->callbacks; l != NULL; l = l->next) { + struct connect_cb *tmp = l->data; + + if (tmp->id == id) { + cb = tmp; + break; + } + } + + if (!cb) + return FALSE; + + p->callbacks = g_slist_remove(p->callbacks, cb); + g_free(cb); + + if (p->callbacks || p->msg) + return TRUE; + + if (hs->auto_dc) { + if (hs->rfcomm) + hs->dc_timer = g_timeout_add_seconds(DC_TIMEOUT, + (GSourceFunc) hs_dc_timeout, + dev); + else + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); + } + + return TRUE; +} + +static gboolean dummy_connect_complete(struct audio_device *dev) +{ + pending_connect_finalize(dev); + return FALSE; +} + +unsigned int headset_request_stream(struct audio_device *dev, + headset_stream_cb_t cb, + void *user_data) +{ + struct headset *hs = dev->headset; + unsigned int id; + + if (hs->state == HEADSET_STATE_PLAYING) { + id = connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data); + g_idle_add((GSourceFunc) dummy_connect_complete, dev); + return id; + } + + if (hs->dc_timer) { + g_source_remove(hs->dc_timer); + hs->dc_timer = 0; + } + + if (hs->state == HEADSET_STATE_CONNECT_IN_PROGRESS || + hs->state == HEADSET_STATE_PLAY_IN_PROGRESS) + return connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data); + + if (hs->rfcomm == NULL) { + if (rfcomm_connect(dev, cb, user_data, &id) < 0) + return 0; + hs->auto_dc = TRUE; + } else if (sco_connect(dev, cb, user_data, &id) < 0) + return 0; + + hs->pending->target_state = HEADSET_STATE_PLAYING; + + return id; +} + +unsigned int headset_config_stream(struct audio_device *dev, + gboolean auto_dc, + headset_stream_cb_t cb, + void *user_data) +{ + struct headset *hs = dev->headset; + unsigned int id = 0; + + if (hs->dc_timer) { + g_source_remove(hs->dc_timer); + hs->dc_timer = 0; + } + + if (hs->state == HEADSET_STATE_CONNECT_IN_PROGRESS) + return connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, + user_data); + + if (hs->rfcomm) + goto done; + + if (rfcomm_connect(dev, cb, user_data, &id) < 0) + return 0; + + hs->auto_dc = auto_dc; + hs->pending->target_state = HEADSET_STATE_CONNECTED; + + return id; + +done: + id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data); + g_idle_add((GSourceFunc) dummy_connect_complete, dev); + return id; +} + +unsigned int headset_suspend_stream(struct audio_device *dev, + headset_stream_cb_t cb, + void *user_data) +{ + struct headset *hs = dev->headset; + unsigned int id; + + if (hs->dc_timer) { + g_source_remove(hs->dc_timer); + hs->dc_timer = 0; + } + + headset_set_state(dev, HEADSET_STATE_CONNECTED); + + id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data); + g_idle_add((GSourceFunc) dummy_connect_complete, dev); + + return id; +} + +gboolean get_hfp_active(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->hfp_active; +} + +void set_hfp_active(struct audio_device *dev, gboolean active) +{ + struct headset *hs = dev->headset; + + hs->hfp_active = active; +} + +GIOChannel *headset_get_rfcomm(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->tmp_rfcomm; +} + +int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *io) +{ + struct headset *hs = dev->headset; + + if (hs->tmp_rfcomm) + return -EALREADY; + + hs->tmp_rfcomm = g_io_channel_ref(io); + + return 0; +} + +int headset_connect_sco(struct audio_device *dev, GIOChannel *io) +{ + struct headset *hs = dev->headset; + + if (hs->sco) + return -EISCONN; + + hs->sco = g_io_channel_ref(io); + + if (hs->pending_ring) { + ring_timer_cb(NULL); + ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, + ring_timer_cb, + NULL); + hs->pending_ring = FALSE; + } + + return 0; +} + +static void disconnect_cb(struct btd_device *btd_dev, gboolean removal, + void *user_data) +{ + struct audio_device *device = user_data; + + info("Headset: disconnect %s", device->path); + + headset_shutdown(device); +} + +void headset_set_state(struct audio_device *dev, headset_state_t state) +{ + struct headset *hs = dev->headset; + gboolean value; + const char *state_str; + headset_state_t old_state = hs->state; + GSList *l; + + if (old_state == state) + return; + + state_str = state2str(state); + + switch (state) { + case HEADSET_STATE_DISCONNECTED: + value = FALSE; + close_sco(dev); + headset_close_rfcomm(dev); + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Disconnected", + DBUS_TYPE_INVALID); + if (hs->state > HEADSET_STATE_CONNECT_IN_PROGRESS) + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + + telephony_device_disconnected(dev); + active_devices = g_slist_remove(active_devices, dev); + device_remove_disconnect_watch(dev->btd_dev, hs->dc_id); + hs->dc_id = 0; + break; + case HEADSET_STATE_CONNECT_IN_PROGRESS: + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + break; + case HEADSET_STATE_CONNECTED: + close_sco(dev); + if (hs->state != HEADSET_STATE_PLAY_IN_PROGRESS) + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + if (hs->state < state) { + if (ag.features & AG_FEATURE_INBAND_RINGTONE) + hs->inband_ring = TRUE; + else + hs->inband_ring = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Connected", + DBUS_TYPE_INVALID); + value = TRUE; + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Connected", + DBUS_TYPE_BOOLEAN, &value); + active_devices = g_slist_append(active_devices, dev); + telephony_device_connected(dev); + hs->dc_id = device_add_disconnect_watch(dev->btd_dev, + disconnect_cb, + dev, NULL); + } else if (hs->state == HEADSET_STATE_PLAYING) { + value = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Stopped", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, + "Playing", + DBUS_TYPE_BOOLEAN, &value); + } + break; + case HEADSET_STATE_PLAY_IN_PROGRESS: + break; + case HEADSET_STATE_PLAYING: + value = TRUE; + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + hs->sco_id = g_io_add_watch(hs->sco, + G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) sco_cb, dev); + + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "Playing", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_HEADSET_INTERFACE, "Playing", + DBUS_TYPE_BOOLEAN, &value); + + if (hs->sp_gain >= 0) + headset_send(hs, "\r\n+VGS=%u\r\n", hs->sp_gain); + if (hs->mic_gain >= 0) + headset_send(hs, "\r\n+VGM=%u\r\n", hs->mic_gain); + break; + } + + hs->state = state; + + debug("State changed %s: %s -> %s", dev->path, str_state[old_state], + str_state[state]); + + for (l = headset_callbacks; l != NULL; l = l->next) { + struct headset_state_callback *cb = l->data; + cb->cb(dev, old_state, state, cb->user_data); + } +} + +headset_state_t headset_get_state(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->state; +} + +int headset_get_channel(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->rfcomm_ch; +} + +gboolean headset_is_active(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (hs->state != HEADSET_STATE_DISCONNECTED) + return TRUE; + + return FALSE; +} + +headset_lock_t headset_get_lock(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->lock; +} + +gboolean headset_lock(struct audio_device *dev, headset_lock_t lock) +{ + struct headset *hs = dev->headset; + + if (hs->lock & lock) + return FALSE; + + hs->lock |= lock; + + return TRUE; +} + +gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock) +{ + struct headset *hs = dev->headset; + + if (!(hs->lock & lock)) + return FALSE; + + hs->lock &= ~lock; + + if (hs->lock) + return TRUE; + + if (hs->state == HEADSET_STATE_PLAYING) + headset_set_state(dev, HEADSET_STATE_CONNECTED); + + if (hs->auto_dc) { + if (hs->state == HEADSET_STATE_CONNECTED) + hs->dc_timer = g_timeout_add_seconds(DC_TIMEOUT, + (GSourceFunc) hs_dc_timeout, + dev); + else + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); + } + + return TRUE; +} + +gboolean headset_suspend(struct audio_device *dev, void *data) +{ + return TRUE; +} + +gboolean headset_play(struct audio_device *dev, void *data) +{ + return TRUE; +} + +int headset_get_sco_fd(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + if (!hs->sco) + return -1; + + return g_io_channel_unix_get_fd(hs->sco); +} + +gboolean headset_get_nrec(struct audio_device *dev) +{ + struct headset *hs = dev->headset; + + return hs->nrec; +} + +gboolean headset_get_sco_hci(struct audio_device *dev) +{ + return sco_hci; +} + +void headset_shutdown(struct audio_device *dev) +{ + struct pending_connect *p = dev->headset->pending; + + if (p && p->msg) + error_connection_attempt_failed(dev->conn, p->msg, ECANCELED); + + pending_connect_finalize(dev); + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); +} + +int telephony_event_ind(int index) +{ + if (!active_devices) + return -ENODEV; + + if (!ag.er_ind) { + debug("telephony_report_event called but events are disabled"); + return -EINVAL; + } + + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+CIEV: %d,%d\r\n", index + 1, + ag.indicators[index].val); + + return 0; +} + +int telephony_response_and_hold_ind(int rh) +{ + if (!active_devices) + return -ENODEV; + + ag.rh = rh; + + /* If we aren't in any response and hold state don't send anything */ + if (ag.rh < 0) + return 0; + + send_foreach_headset(active_devices, hfp_cmp, "\r\n+BTRH: %d\r\n", + ag.rh); + + return 0; +} + +int telephony_incoming_call_ind(const char *number, int type) +{ + struct audio_device *dev; + struct headset *hs; + + if (!active_devices) + return -ENODEV; + + /* Get the latest connected device */ + dev = active_devices->data; + hs = dev->headset; + + if (ag.ring_timer) { + debug("telephony_incoming_call_ind: already calling"); + return -EBUSY; + } + + /* With HSP 1.2 the RING messages should *not* be sent if inband + * ringtone is being used */ + if (!hs->hfp_active && hs->inband_ring) + return 0; + + g_free(ag.number); + ag.number = g_strdup(number); + ag.number_type = type; + + if (hs->inband_ring && hs->hfp_active && + hs->state != HEADSET_STATE_PLAYING) { + hs->pending_ring = TRUE; + return 0; + } + + ring_timer_cb(NULL); + ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb, + NULL); + + return 0; +} + +int telephony_calling_stopped_ind(void) +{ + struct audio_device *dev; + + if (ag.ring_timer) { + g_source_remove(ag.ring_timer); + ag.ring_timer = 0; + } + + if (!active_devices) + return 0; + + /* In case SCO isn't fully up yet */ + dev = active_devices->data; + + if (!dev->headset->pending_ring && !ag.ring_timer) + return -EINVAL; + + dev->headset->pending_ring = FALSE; + + return 0; +} + +int telephony_ready_ind(uint32_t features, + const struct indicator *indicators, int rh, + const char *chld) +{ + ag.telephony_ready = TRUE; + ag.features = features; + ag.indicators = indicators; + ag.rh = rh; + ag.chld = g_strdup(chld); + + debug("Telephony plugin initialized"); + + print_ag_features(ag.features); + + return 0; +} + +int telephony_list_current_call_ind(int idx, int dir, int status, int mode, + int mprty, const char *number, + int type) +{ + if (!active_devices) + return -ENODEV; + + if (number && strlen(number) > 0) + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n", + idx, dir, status, mode, mprty, number, type); + else + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+CLCC: %d,%d,%d,%d,%d\r\n", + idx, dir, status, mode, mprty); + + return 0; +} + +int telephony_subscriber_number_ind(const char *number, int type, int service) +{ + if (!active_devices) + return -ENODEV; + + send_foreach_headset(active_devices, hfp_cmp, + "\r\n+CNUM: ,%s,%d,,%d\r\n", + number, type, service); + + return 0; +} + +static int cwa_cmp(struct headset *hs) +{ + if (!hs->hfp_active) + return -1; + + if (hs->cwa_enabled) + return 0; + else + return -1; +} + +int telephony_call_waiting_ind(const char *number, int type) +{ + if (!active_devices) + return -ENODEV; + + send_foreach_headset(active_devices, cwa_cmp, + "\r\n+CCWA: \"%s\",%d\r\n", + number, type); + + return 0; +} + +unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data) +{ + struct headset_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct headset_state_callback, 1); + state_cb->cb = cb; + state_cb->user_data = user_data; + state_cb->id = ++id; + + headset_callbacks = g_slist_append(headset_callbacks, state_cb); + + return state_cb->id; +} + +gboolean headset_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = headset_callbacks; l != NULL; l = l->next) { + struct headset_state_callback *cb = l->data; + if (cb && cb->id == id) { + headset_callbacks = g_slist_remove(headset_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/audio/headset.h b/audio/headset.h new file mode 100644 index 000000000..ace1edf79 --- /dev/null +++ b/audio/headset.h @@ -0,0 +1,102 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#define AUDIO_HEADSET_INTERFACE "org.bluez.Headset" + +#define DEFAULT_HS_AG_CHANNEL 12 +#define DEFAULT_HF_AG_CHANNEL 13 + +typedef enum { + HEADSET_STATE_DISCONNECTED, + HEADSET_STATE_CONNECT_IN_PROGRESS, + HEADSET_STATE_CONNECTED, + HEADSET_STATE_PLAY_IN_PROGRESS, + HEADSET_STATE_PLAYING +} headset_state_t; + +typedef enum { + HEADSET_LOCK_READ = 1, + HEADSET_LOCK_WRITE = 1 << 1, +} headset_lock_t; + +typedef void (*headset_state_cb) (struct audio_device *dev, + headset_state_t old_state, + headset_state_t new_state, + void *user_data); + +unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data); +gboolean headset_remove_state_cb(unsigned int id); + +typedef void (*headset_stream_cb_t) (struct audio_device *dev, void *user_data); + +void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data); + +GIOChannel *headset_get_rfcomm(struct audio_device *dev); + +struct headset *headset_init(struct audio_device *dev, uint16_t svc, + const char *uuidstr); + +void headset_unregister(struct audio_device *dev); + +uint32_t headset_config_init(GKeyFile *config); + +void headset_update(struct audio_device *dev, uint16_t svc, + const char *uuidstr); + +unsigned int headset_config_stream(struct audio_device *dev, + gboolean auto_dc, + headset_stream_cb_t cb, + void *user_data); +unsigned int headset_request_stream(struct audio_device *dev, + headset_stream_cb_t cb, + void *user_data); +unsigned int headset_suspend_stream(struct audio_device *dev, + headset_stream_cb_t cb, + void *user_data); +gboolean headset_cancel_stream(struct audio_device *dev, unsigned int id); + +gboolean get_hfp_active(struct audio_device *dev); +void set_hfp_active(struct audio_device *dev, gboolean active); + +void headset_set_authorized(struct audio_device *dev); +int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *chan); +int headset_connect_sco(struct audio_device *dev, GIOChannel *io); + +headset_state_t headset_get_state(struct audio_device *dev); +void headset_set_state(struct audio_device *dev, headset_state_t state); + +int headset_get_channel(struct audio_device *dev); + +int headset_get_sco_fd(struct audio_device *dev); +gboolean headset_get_nrec(struct audio_device *dev); +gboolean headset_get_sco_hci(struct audio_device *dev); + +gboolean headset_is_active(struct audio_device *dev); + +headset_lock_t headset_get_lock(struct audio_device *dev); +gboolean headset_lock(struct audio_device *dev, headset_lock_t lock); +gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock); +gboolean headset_suspend(struct audio_device *dev, void *data); +gboolean headset_play(struct audio_device *dev, void *data); +void headset_shutdown(struct audio_device *dev); diff --git a/audio/ipc.c b/audio/ipc.c new file mode 100644 index 000000000..28569dc14 --- /dev/null +++ b/audio/ipc.c @@ -0,0 +1,133 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "ipc.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/* This table contains the string representation for messages types */ +static const char *strtypes[] = { + "BT_REQUEST", + "BT_RESPONSE", + "BT_INDICATION", + "BT_ERROR", +}; + +/* This table contains the string representation for messages names */ +static const char *strnames[] = { + "BT_GET_CAPABILITIES", + "BT_OPEN", + "BT_SET_CONFIGURATION", + "BT_NEW_STREAM", + "BT_START_STREAM", + "BT_STOP_STREAM", + "BT_SUSPEND_STREAM", + "BT_RESUME_STREAM", + "BT_CONTROL", +}; + +int bt_audio_service_open(void) +{ + int sk; + int err; + struct sockaddr_un addr = { + AF_UNIX, BT_IPC_SOCKET_NAME + }; + + sk = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sk < 0) { + err = errno; + fprintf(stderr, "%s: Cannot open socket: %s (%d)\n", + __FUNCTION__, strerror(err), err); + errno = err; + return -1; + } + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = errno; + fprintf(stderr, "%s: connect() failed: %s (%d)\n", + __FUNCTION__, strerror(err), err); + close(sk); + errno = err; + return -1; + } + + return sk; +} + +int bt_audio_service_close(int sk) +{ + return close(sk); +} + +int bt_audio_service_get_data_fd(int sk) +{ + char cmsg_b[CMSG_SPACE(sizeof(int))], m; + int err, ret; + struct iovec iov = { &m, sizeof(m) }; + struct msghdr msgh; + struct cmsghdr *cmsg; + + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = &cmsg_b; + msgh.msg_controllen = CMSG_LEN(sizeof(int)); + + ret = recvmsg(sk, &msgh, 0); + if (ret < 0) { + err = errno; + fprintf(stderr, "%s: Unable to receive fd: %s (%d)\n", + __FUNCTION__, strerror(err), err); + errno = err; + return -1; + } + + /* Receive auxiliary data in msgh */ + for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + memcpy(&ret, CMSG_DATA(cmsg), sizeof(int)); + return ret; + } + } + + errno = EINVAL; + return -1; +} + +const char *bt_audio_strtype(uint8_t type) +{ + if (type >= ARRAY_SIZE(strtypes)) + return NULL; + + return strtypes[type]; +} + +const char *bt_audio_strname(uint8_t name) +{ + if (name >= ARRAY_SIZE(strnames)) + return NULL; + + return strnames[name]; +} diff --git a/audio/ipc.h b/audio/ipc.h new file mode 100644 index 000000000..2e170f504 --- /dev/null +++ b/audio/ipc.h @@ -0,0 +1,349 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + Message sequence chart of streaming sequence for A2DP transport + + Audio daemon User + on snd_pcm_open + <--BT_GET_CAPABILITIES_REQ + + BT_GET_CAPABILITIES_RSP--> + + on snd_pcm_hw_params + <--BT_SETCONFIGURATION_REQ + + BT_SET_CONFIGURATION_RSP--> + + on snd_pcm_prepare + <--BT_START_STREAM_REQ + + + BT_START_STREAM_RSP--> + + BT_NEW_STREAM_IND --> + + < streams data > + .......... + + on snd_pcm_drop/snd_pcm_drain + + <--BT_STOP_STREAM_REQ + + + BT_STOP_STREAM_RSP--> + + on IPC close or appl crash + + + */ + +#ifndef BT_AUDIOCLIENT_H +#define BT_AUDIOCLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include + +#define BT_SUGGESTED_BUFFER_SIZE 512 +#define BT_IPC_SOCKET_NAME "\0/org/bluez/audio" + +/* Generic message header definition, except for RESPONSE messages */ +typedef struct { + uint8_t type; + uint8_t name; + uint16_t length; +} __attribute__ ((packed)) bt_audio_msg_header_t; + +typedef struct { + bt_audio_msg_header_t h; + uint8_t posix_errno; +} __attribute__ ((packed)) bt_audio_error_t; + +/* Message types */ +#define BT_REQUEST 0 +#define BT_RESPONSE 1 +#define BT_INDICATION 2 +#define BT_ERROR 3 + +/* Messages names */ +#define BT_GET_CAPABILITIES 0 +#define BT_OPEN 1 +#define BT_SET_CONFIGURATION 2 +#define BT_NEW_STREAM 3 +#define BT_START_STREAM 4 +#define BT_STOP_STREAM 5 +#define BT_CLOSE 6 +#define BT_CONTROL 7 + +#define BT_CAPABILITIES_TRANSPORT_A2DP 0 +#define BT_CAPABILITIES_TRANSPORT_SCO 1 +#define BT_CAPABILITIES_TRANSPORT_ANY 2 + +#define BT_CAPABILITIES_ACCESS_MODE_READ 1 +#define BT_CAPABILITIES_ACCESS_MODE_WRITE 2 +#define BT_CAPABILITIES_ACCESS_MODE_READWRITE 3 + +#define BT_FLAG_AUTOCONNECT 1 + +struct bt_get_capabilities_req { + bt_audio_msg_header_t h; + char source[18]; /* Address of the local Device */ + char destination[18];/* Address of the remote Device */ + char object[128]; /* DBus object path */ + uint8_t transport; /* Requested transport */ + uint8_t flags; /* Requested flags */ + uint8_t seid; /* Requested capability configuration */ +} __attribute__ ((packed)); + +/** + * SBC Codec parameters as per A2DP profile 1.0 § 4.3 + */ + +/* A2DP seid are 6 bytes long so HSP/HFP are assigned to 7-8 bits */ +#define BT_A2DP_SEID_RANGE (1 << 6) - 1 + +#define BT_A2DP_SBC_SOURCE 0x00 +#define BT_A2DP_SBC_SINK 0x01 +#define BT_A2DP_MPEG12_SOURCE 0x02 +#define BT_A2DP_MPEG12_SINK 0x03 +#define BT_A2DP_MPEG24_SOURCE 0x04 +#define BT_A2DP_MPEG24_SINK 0x05 +#define BT_A2DP_ATRAC_SOURCE 0x06 +#define BT_A2DP_ATRAC_SINK 0x07 +#define BT_A2DP_UNKNOWN_SOURCE 0x08 +#define BT_A2DP_UNKNOWN_SINK 0x09 + +#define BT_SBC_SAMPLING_FREQ_16000 (1 << 3) +#define BT_SBC_SAMPLING_FREQ_32000 (1 << 2) +#define BT_SBC_SAMPLING_FREQ_44100 (1 << 1) +#define BT_SBC_SAMPLING_FREQ_48000 1 + +#define BT_A2DP_CHANNEL_MODE_MONO (1 << 3) +#define BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define BT_A2DP_CHANNEL_MODE_STEREO (1 << 1) +#define BT_A2DP_CHANNEL_MODE_JOINT_STEREO 1 + +#define BT_A2DP_BLOCK_LENGTH_4 (1 << 3) +#define BT_A2DP_BLOCK_LENGTH_8 (1 << 2) +#define BT_A2DP_BLOCK_LENGTH_12 (1 << 1) +#define BT_A2DP_BLOCK_LENGTH_16 1 + +#define BT_A2DP_SUBBANDS_4 (1 << 1) +#define BT_A2DP_SUBBANDS_8 1 + +#define BT_A2DP_ALLOCATION_SNR (1 << 1) +#define BT_A2DP_ALLOCATION_LOUDNESS 1 + +#define BT_MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define BT_MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define BT_MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define BT_MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define BT_MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define BT_MPEG_SAMPLING_FREQ_48000 1 + +#define BT_MPEG_LAYER_1 (1 << 2) +#define BT_MPEG_LAYER_2 (1 << 1) +#define BT_MPEG_LAYER_3 1 + +#define BT_HFP_CODEC_PCM 0x00 + +#define BT_PCM_FLAG_NREC 0x01 +#define BT_PCM_FLAG_PCM_ROUTING 0x02 + +#define BT_WRITE_LOCK (1 << 1) +#define BT_READ_LOCK 1 + +typedef struct { + uint8_t seid; + uint8_t transport; + uint8_t type; + uint8_t length; + uint8_t configured; + uint8_t lock; + uint8_t data[0]; +} __attribute__ ((packed)) codec_capabilities_t; + +typedef struct { + codec_capabilities_t capability; + uint8_t channel_mode; + uint8_t frequency; + uint8_t allocation_method; + uint8_t subbands; + uint8_t block_length; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) sbc_capabilities_t; + +typedef struct { + codec_capabilities_t capability; + uint8_t channel_mode; + uint8_t crc; + uint8_t layer; + uint8_t frequency; + uint8_t mpf; + uint16_t bitrate; +} __attribute__ ((packed)) mpeg_capabilities_t; + +typedef struct { + codec_capabilities_t capability; + uint8_t flags; + uint16_t sampling_rate; +} __attribute__ ((packed)) pcm_capabilities_t; + +struct bt_get_capabilities_rsp { + bt_audio_msg_header_t h; + char source[18]; /* Address of the local Device */ + char destination[18];/* Address of the remote Device */ + char object[128]; /* DBus object path */ + uint8_t data[0]; /* First codec_capabilities_t */ +} __attribute__ ((packed)); + +struct bt_open_req { + bt_audio_msg_header_t h; + char source[18]; /* Address of the local Device */ + char destination[18];/* Address of the remote Device */ + char object[128]; /* DBus object path */ + uint8_t seid; /* Requested capability configuration to lock */ + uint8_t lock; /* Requested lock */ +} __attribute__ ((packed)); + +struct bt_open_rsp { + bt_audio_msg_header_t h; + char source[18]; /* Address of the local Device */ + char destination[18];/* Address of the remote Device */ + char object[128]; /* DBus object path */ +} __attribute__ ((packed)); + +struct bt_set_configuration_req { + bt_audio_msg_header_t h; + codec_capabilities_t codec; /* Requested codec */ +} __attribute__ ((packed)); + +struct bt_set_configuration_rsp { + bt_audio_msg_header_t h; + uint16_t link_mtu; /* Max length that transport supports */ +} __attribute__ ((packed)); + +#define BT_STREAM_ACCESS_READ 0 +#define BT_STREAM_ACCESS_WRITE 1 +#define BT_STREAM_ACCESS_READWRITE 2 +struct bt_start_stream_req { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_start_stream_rsp { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +/* This message is followed by one byte of data containing the stream data fd + as ancilliary data */ +struct bt_new_stream_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_stop_stream_req { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_stop_stream_rsp { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_close_req { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_close_rsp { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_suspend_stream_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +struct bt_resume_stream_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +#define BT_CONTROL_KEY_POWER 0x40 +#define BT_CONTROL_KEY_VOL_UP 0x41 +#define BT_CONTROL_KEY_VOL_DOWN 0x42 +#define BT_CONTROL_KEY_MUTE 0x43 +#define BT_CONTROL_KEY_PLAY 0x44 +#define BT_CONTROL_KEY_STOP 0x45 +#define BT_CONTROL_KEY_PAUSE 0x46 +#define BT_CONTROL_KEY_RECORD 0x47 +#define BT_CONTROL_KEY_REWIND 0x48 +#define BT_CONTROL_KEY_FAST_FORWARD 0x49 +#define BT_CONTROL_KEY_EJECT 0x4A +#define BT_CONTROL_KEY_FORWARD 0x4B +#define BT_CONTROL_KEY_BACKWARD 0x4C + +struct bt_control_req { + bt_audio_msg_header_t h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +struct bt_control_rsp { + bt_audio_msg_header_t h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +struct bt_control_ind { + bt_audio_msg_header_t h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +/* Function declaration */ + +/* Opens a connection to the audio service: return a socket descriptor */ +int bt_audio_service_open(void); + +/* Closes a connection to the audio service */ +int bt_audio_service_close(int sk); + +/* Receives stream data file descriptor : must be called after a +BT_STREAMFD_IND message is returned */ +int bt_audio_service_get_data_fd(int sk); + +/* Human readable message type string */ +const char *bt_audio_strtype(uint8_t type); + +/* Human readable message name string */ +const char *bt_audio_strname(uint8_t name); + +#ifdef __cplusplus +} +#endif + +#endif /* BT_AUDIOCLIENT_H */ diff --git a/audio/ipctest-a2dp-easy.test b/audio/ipctest-a2dp-easy.test new file mode 100644 index 000000000..ba62d9336 --- /dev/null +++ b/audio/ipctest-a2dp-easy.test @@ -0,0 +1,8 @@ +profile a2dp +init_bt +init_profile +start_stream +sleep 2 +stop_stream +quit + diff --git a/audio/ipctest-a2dp-resume-fast.test b/audio/ipctest-a2dp-resume-fast.test new file mode 100644 index 000000000..bbc99e2b1 --- /dev/null +++ b/audio/ipctest-a2dp-resume-fast.test @@ -0,0 +1,82 @@ +#!./ipctest + +profile a2dp +init_bt + +init_profile + +start_stream +sleep 1 +stop_stream + +stop_stream +start_stream + +stop_stream +start_stream + +stop_stream +start_stream + +stop_stream +start_stream + +stop_stream +start_stream + +stop_stream +stop_stream +stop_stream + +start_stream +start_stream +start_stream + +stop_stream + +sleep 1 + +start_stream +start_stream +start_stream +stop_stream +stop_stream + +start_stream +sleep 1 +stop_stream + +start_stream +stop_stream + +start_stream +stop_stream + +shutdown_bt + +init_bt + +init_profile + +start_stream +stop_stream +stop_stream +start_stream +stop_stream +start_stream +stop_stream +start_stream +stop_stream +start_stream +stop_stream +start_stream +stop_stream +stop_stream +stop_stream + +start_stream +start_stream +start_stream +stop_stream + +quit diff --git a/audio/ipctest-hsp-a2dp-switch.test b/audio/ipctest-hsp-a2dp-switch.test new file mode 100644 index 000000000..e99878f53 --- /dev/null +++ b/audio/ipctest-hsp-a2dp-switch.test @@ -0,0 +1,50 @@ +init_bt + +profile a2dp +init_profile +start_stream +sleep 2 +stop_stream +start_stream +sleep 2 +stop_stream + +shutdown_bt +init_bt + +profile hsp +init_profile +start_stream +sleep 2 +stop_stream +start_stream +sleep 2 +stop_stream + +shutdown_bt +init_bt + +profile a2dp +init_profile +start_stream +sleep 2 +stop_stream +start_stream +sleep 2 +stop_stream + +shutdown_bt +init_bt + +profile hsp +init_profile +start_stream +sleep 2 +stop_stream +start_stream +sleep 2 +stop_stream + +shutdown_bt + +quit diff --git a/audio/ipctest-hsp-easy.test b/audio/ipctest-hsp-easy.test new file mode 100644 index 000000000..0756a7873 --- /dev/null +++ b/audio/ipctest-hsp-easy.test @@ -0,0 +1,7 @@ +profile hsp +init_bt +init_profile +start_stream +sleep 2 +stop_stream +quit diff --git a/audio/ipctest-init-shutdown.test b/audio/ipctest-init-shutdown.test new file mode 100644 index 000000000..578883abe --- /dev/null +++ b/audio/ipctest-init-shutdown.test @@ -0,0 +1,59 @@ +#!./ipctest + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + + +init_bt +sleep 1 +shutdown_bt + +init_bt +sleep 1 +shutdown_bt + +init_bt +sleep 1 +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +init_bt +shutdown_bt + +quit + diff --git a/audio/ipctest.c b/audio/ipctest.c new file mode 100644 index 000000000..d18f62e1a --- /dev/null +++ b/audio/ipctest.c @@ -0,0 +1,1131 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2009 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2009 Lennart Poettering + * Copyright (C) 2008 Joao Paulo Rechi Vita + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ipc.h" +#include "sbc.h" + +#define DBG(fmt, arg...) \ + printf("debug %s: " fmt "\n" , __FUNCTION__ , ## arg) +#define ERR(fmt, arg...) \ + fprintf(stderr, "ERROR %s: " fmt "\n" , __FUNCTION__ , ## arg) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#ifndef MIN +# define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef MAX +# define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#ifndef TRUE +# define TRUE (1) +#endif + +#ifndef FALSE +# define FALSE (0) +#endif + +#define YES_NO(t) ((t) ? "yes" : "no") + +#define BUFFER_SIZE 2048 +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +struct a2dp_info { + sbc_capabilities_t sbc_capabilities; + sbc_t sbc; /* Codec data */ + int sbc_initialized; /* Keep track if the encoder is initialized */ + size_t codesize; /* SBC codesize */ + + void* buffer; /* Codec transfer buffer */ + size_t buffer_size; /* Size of the buffer */ + + uint16_t seq_num; /* Cumulative packet sequence */ +}; + +struct hsp_info { + pcm_capabilities_t pcm_capabilities; +}; + +struct userdata { + int service_fd; + int stream_fd; + GIOChannel *stream_channel; + guint stream_watch; + GIOChannel *gin; /* dude, I am thirsty now */ + guint gin_watch; + int transport; + uint32_t rate; + int channels; + char *address; + struct a2dp_info a2dp; + struct hsp_info hsp; + size_t link_mtu; + size_t block_size; + gboolean debug_stream_read : 1; + gboolean debug_stream_write : 1; +}; + +static struct userdata data = { + .service_fd = -1, + .stream_fd = -1, + .transport = BT_CAPABILITIES_TRANSPORT_A2DP, + .rate = 48000, + .channels = 2, + .address = NULL +}; + +static int start_stream(struct userdata *u); +static int stop_stream(struct userdata *u); +static gboolean input_cb(GIOChannel *gin, GIOCondition condition, gpointer data); + +static GMainLoop *main_loop; + +static int service_send(struct userdata *u, const bt_audio_msg_header_t *msg) +{ + int err; + uint16_t length; + + assert(u); + + length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE; + + DBG("sending %s:%s", bt_audio_strtype(msg->type), + bt_audio_strname(msg->name)); + + if (send(u->service_fd, msg, length, 0) > 0) + err = 0; + else { + err = -errno; + ERR("Error sending data to audio service: %s(%d)", + strerror(errno), errno); + } + + return err; +} + +static int service_recv(struct userdata *u, bt_audio_msg_header_t *rsp) +{ + int err; + const char *type, *name; + uint16_t length; + + assert(u); + + length = rsp->length ? : BT_SUGGESTED_BUFFER_SIZE; + + DBG("trying to receive msg from audio service..."); + if (recv(u->service_fd, rsp, length, 0) > 0) { + type = bt_audio_strtype(rsp->type); + name = bt_audio_strname(rsp->name); + if (type && name) { + DBG("Received %s - %s", type, name); + err = 0; + } else { + err = -EINVAL; + ERR("Bogus message type %d - name %d" + "received from audio service", + rsp->type, rsp->name); + } + } else { + err = -errno; + ERR("Error receiving data from audio service: %s(%d)", + strerror(errno), errno); + } + + return err; +} + +static ssize_t service_expect(struct userdata *u, bt_audio_msg_header_t *rsp, + uint8_t expected_name) +{ + int r; + + assert(u); + assert(u->service_fd >= 0); + assert(rsp); + + if ((r = service_recv(u, rsp)) < 0) + return r; + + if ((rsp->type != BT_INDICATION && rsp->type != BT_RESPONSE) || + (rsp->name != expected_name)) { + if (rsp->type == BT_ERROR && rsp->length == sizeof(bt_audio_error_t)) + ERR("Received error condition: %s", + strerror(((bt_audio_error_t*) rsp)->posix_errno)); + else + ERR("Bogus message %s received while %s was expected", + bt_audio_strname(rsp->name), + bt_audio_strname(expected_name)); + return -1; + } + + return 0; +} + +static int init_bt(struct userdata *u) +{ + assert(u); + + if (u->service_fd != -1) + return 0; + + DBG("bt_audio_service_open"); + + u->service_fd = bt_audio_service_open(); + if (u->service_fd <= 0) { + perror(strerror(errno)); + return errno; + } + + return 0; +} + +static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *rsp) +{ + unsigned char *ptr; + uint16_t bytes_left; + codec_capabilities_t codec; + + assert(u); + assert(rsp); + + bytes_left = rsp->h.length - sizeof(*rsp); + + if (bytes_left < sizeof(codec_capabilities_t)) { + ERR("Packet too small to store codec information."); + return -1; + } + + ptr = ((void *) rsp) + sizeof(*rsp); + + memcpy(&codec, ptr, sizeof(codec)); /** ALIGNMENT? **/ + + DBG("Payload size is %lu %lu", + (unsigned long) bytes_left, (unsigned long) sizeof(codec)); + + if (u->transport != codec.transport) { + ERR("Got capabilities for wrong codec."); + return -1; + } + + if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO) { + + if (bytes_left <= 0 || + codec.length != sizeof(u->hsp.pcm_capabilities)) + return -1; + + assert(codec.type == BT_HFP_CODEC_PCM); + + memcpy(&u->hsp.pcm_capabilities, + &codec, sizeof(u->hsp.pcm_capabilities)); + + DBG("Has NREC: %s", + YES_NO(u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC)); + + } else if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + + while (bytes_left > 0) { + if (codec.type == BT_A2DP_SBC_SINK && + !(codec.lock & BT_WRITE_LOCK)) + break; + + bytes_left -= codec.length; + ptr += codec.length; + memcpy(&codec, ptr, sizeof(codec)); + } + + DBG("bytes_left = %d, codec.length = %d", + bytes_left, codec.length); + + if (bytes_left <= 0 || + codec.length != sizeof(u->a2dp.sbc_capabilities)) + return -1; + + assert(codec.type == BT_A2DP_SBC_SINK); + + memcpy(&u->a2dp.sbc_capabilities, &codec, + sizeof(u->a2dp.sbc_capabilities)); + } else { + assert(0); + } + + return 0; +} + +static int get_caps(struct userdata *u) +{ + union { + struct bt_get_capabilities_req getcaps_req; + struct bt_get_capabilities_rsp getcaps_rsp; + bt_audio_error_t error; + uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; + } msg; + + assert(u); + + memset(&msg, 0, sizeof(msg)); + msg.getcaps_req.h.type = BT_REQUEST; + msg.getcaps_req.h.name = BT_GET_CAPABILITIES; + msg.getcaps_req.h.length = sizeof(msg.getcaps_req); + + strncpy(msg.getcaps_req.destination, u->address, + sizeof(msg.getcaps_req.destination)); + msg.getcaps_req.transport = u->transport; + msg.getcaps_req.flags = BT_FLAG_AUTOCONNECT; + + if (service_send(u, &msg.getcaps_req.h) < 0) + return -1; + + msg.getcaps_rsp.h.length = 0; + if (service_expect(u, &msg.getcaps_rsp.h, BT_GET_CAPABILITIES) < 0) + return -1; + + return parse_caps(u, &msg.getcaps_rsp); +} + +static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case BT_SBC_SAMPLING_FREQ_16000: + case BT_SBC_SAMPLING_FREQ_32000: + return 53; + + case BT_SBC_SAMPLING_FREQ_44100: + + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + + default: + DBG("Invalid channel mode %u", mode); + return 53; + } + + case BT_SBC_SAMPLING_FREQ_48000: + + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + + default: + DBG("Invalid channel mode %u", mode); + return 51; + } + + default: + DBG("Invalid sampling freq %u", freq); + return 53; + } +} + +static int setup_a2dp(struct userdata *u) +{ + sbc_capabilities_t *cap; + int i; + + static const struct { + uint32_t rate; + uint8_t cap; + } freq_table[] = { + { 16000U, BT_SBC_SAMPLING_FREQ_16000 }, + { 32000U, BT_SBC_SAMPLING_FREQ_32000 }, + { 44100U, BT_SBC_SAMPLING_FREQ_44100 }, + { 48000U, BT_SBC_SAMPLING_FREQ_48000 } + }; + + assert(u); + assert(u->transport == BT_CAPABILITIES_TRANSPORT_A2DP); + + cap = &u->a2dp.sbc_capabilities; + + /* Find the lowest freq that is at least as high as the requested + * sampling rate */ + for (i = 0; (unsigned) i < ARRAY_SIZE(freq_table); i++) + if (freq_table[i].rate >= u->rate && + (cap->frequency & freq_table[i].cap)) { + u->rate = freq_table[i].rate; + cap->frequency = freq_table[i].cap; + break; + } + + if ((unsigned) i >= ARRAY_SIZE(freq_table)) { + for (; i >= 0; i--) { + if (cap->frequency & freq_table[i].cap) { + u->rate = freq_table[i].rate; + cap->frequency = freq_table[i].cap; + break; + } + } + + if (i < 0) { + DBG("Not suitable sample rate"); + return -1; + } + } + + if (u->channels <= 1) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { + cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + u->channels = 1; + } else + u->channels = 2; + } + + if (u->channels >= 2) { + u->channels = 2; + + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { + cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + u->channels = 1; + } else { + DBG("No supported channel modes"); + return -1; + } + } + + if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) + cap->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) + cap->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) + cap->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) + cap->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + DBG("No supported block lengths"); + return -1; + } + + if (cap->subbands & BT_A2DP_SUBBANDS_8) + cap->subbands = BT_A2DP_SUBBANDS_8; + else if (cap->subbands & BT_A2DP_SUBBANDS_4) + cap->subbands = BT_A2DP_SUBBANDS_4; + else { + DBG("No supported subbands"); + return -1; + } + + if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) + cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) + cap->allocation_method = BT_A2DP_ALLOCATION_SNR; + + cap->min_bitpool = (uint8_t) MAX(MIN_BITPOOL, cap->min_bitpool); + cap->max_bitpool = (uint8_t) MIN( + a2dp_default_bitpool(cap->frequency, cap->channel_mode), + cap->max_bitpool); + + return 0; +} + +static void setup_sbc(struct a2dp_info *a2dp) +{ + sbc_capabilities_t *active_capabilities; + + assert(a2dp); + + active_capabilities = &a2dp->sbc_capabilities; + + if (a2dp->sbc_initialized) + sbc_reinit(&a2dp->sbc, 0); + else + sbc_init(&a2dp->sbc, 0); + a2dp->sbc_initialized = TRUE; + + switch (active_capabilities->frequency) { + case BT_SBC_SAMPLING_FREQ_16000: + a2dp->sbc.frequency = SBC_FREQ_16000; + break; + case BT_SBC_SAMPLING_FREQ_32000: + a2dp->sbc.frequency = SBC_FREQ_32000; + break; + case BT_SBC_SAMPLING_FREQ_44100: + a2dp->sbc.frequency = SBC_FREQ_44100; + break; + case BT_SBC_SAMPLING_FREQ_48000: + a2dp->sbc.frequency = SBC_FREQ_48000; + break; + default: + assert(0); + } + + switch (active_capabilities->channel_mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + a2dp->sbc.mode = SBC_MODE_MONO; + break; + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; + break; + case BT_A2DP_CHANNEL_MODE_STEREO: + a2dp->sbc.mode = SBC_MODE_STEREO; + break; + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; + break; + default: + assert(0); + } + + switch (active_capabilities->allocation_method) { + case BT_A2DP_ALLOCATION_SNR: + a2dp->sbc.allocation = SBC_AM_SNR; + break; + case BT_A2DP_ALLOCATION_LOUDNESS: + a2dp->sbc.allocation = SBC_AM_LOUDNESS; + break; + default: + assert(0); + } + + switch (active_capabilities->subbands) { + case BT_A2DP_SUBBANDS_4: + a2dp->sbc.subbands = SBC_SB_4; + break; + case BT_A2DP_SUBBANDS_8: + a2dp->sbc.subbands = SBC_SB_8; + break; + default: + assert(0); + } + + switch (active_capabilities->block_length) { + case BT_A2DP_BLOCK_LENGTH_4: + a2dp->sbc.blocks = SBC_BLK_4; + break; + case BT_A2DP_BLOCK_LENGTH_8: + a2dp->sbc.blocks = SBC_BLK_8; + break; + case BT_A2DP_BLOCK_LENGTH_12: + a2dp->sbc.blocks = SBC_BLK_12; + break; + case BT_A2DP_BLOCK_LENGTH_16: + a2dp->sbc.blocks = SBC_BLK_16; + break; + default: + assert(0); + } + + a2dp->sbc.bitpool = active_capabilities->max_bitpool; + a2dp->codesize = (uint16_t) sbc_get_codesize(&a2dp->sbc); +} + +static int bt_open(struct userdata *u) +{ + union { + struct bt_open_req open_req; + struct bt_open_rsp open_rsp; + bt_audio_error_t error; + uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; + } msg; + + memset(&msg, 0, sizeof(msg)); + msg.open_req.h.type = BT_REQUEST; + msg.open_req.h.name = BT_OPEN; + msg.open_req.h.length = sizeof(msg.open_req); + + strncpy(msg.open_req.destination, u->address, + sizeof(msg.open_req.destination)); + msg.open_req.seid = u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ? + u->a2dp.sbc_capabilities.capability.seid : + BT_A2DP_SEID_RANGE + 1; + msg.open_req.lock = u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ? + BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK; + + if (service_send(u, &msg.open_req.h) < 0) + return -1; + + msg.open_rsp.h.length = sizeof(msg.open_rsp); + if (service_expect(u, &msg.open_rsp.h, BT_OPEN) < 0) + return -1; + + return 0; +} + +static int set_conf(struct userdata *u) +{ + union { + struct bt_set_configuration_req setconf_req; + struct bt_set_configuration_rsp setconf_rsp; + bt_audio_error_t error; + uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; + } msg; + + if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + if (setup_a2dp(u) < 0) + return -1; + } + + memset(&msg, 0, sizeof(msg)); + msg.setconf_req.h.type = BT_REQUEST; + msg.setconf_req.h.name = BT_SET_CONFIGURATION; + msg.setconf_req.h.length = sizeof(msg.setconf_req); + + if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, + sizeof(u->a2dp.sbc_capabilities)); + msg.setconf_req.h.length += msg.setconf_req.codec.length - + sizeof(msg.setconf_req.codec); + } else { + msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO; + msg.setconf_req.codec.seid = BT_A2DP_SEID_RANGE + 1; + msg.setconf_req.codec.length = sizeof(pcm_capabilities_t); + } + + if (service_send(u, &msg.setconf_req.h) < 0) + return -1; + + msg.setconf_rsp.h.length = sizeof(msg.setconf_rsp); + if (service_expect(u, &msg.setconf_rsp.h, BT_SET_CONFIGURATION) < 0) + return -1; + + u->link_mtu = msg.setconf_rsp.link_mtu; + + /* setup SBC encoder now we agree on parameters */ + if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + setup_sbc(&u->a2dp); + u->block_size = u->a2dp.codesize; + DBG("SBC parameters:\n\tallocation=%u\n" + "\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", + u->a2dp.sbc.allocation, u->a2dp.sbc.subbands, + u->a2dp.sbc.blocks, u->a2dp.sbc.bitpool); + } else + u->block_size = u->link_mtu; + + return 0; +} + +static int setup_bt(struct userdata *u) +{ + assert(u); + + if (get_caps(u) < 0) + return -1; + + DBG("Got device caps"); + + if (bt_open(u) < 0) + return -1; + + if (set_conf(u) < 0) + return -1; + + return 0; +} + +static int init_profile(struct userdata *u) +{ + assert(u); + + return setup_bt(u); +} + +static void shutdown_bt(struct userdata *u) +{ + assert(u); + + if (u->stream_fd != -1) { + stop_stream(u); + DBG("close(stream_fd)"); + close(u->stream_fd); + u->stream_fd = -1; + } + + if (u->service_fd != -1) { + DBG("bt_audio_service_close"); + bt_audio_service_close(u->service_fd); + u->service_fd = -1; + } +} + +static void make_fd_nonblock(int fd) +{ + int v; + + assert(fd >= 0); + assert((v = fcntl(fd, F_GETFL)) >= 0); + + if (!(v & O_NONBLOCK)) + assert(fcntl(fd, F_SETFL, v|O_NONBLOCK) >= 0); +} + +static void make_socket_low_delay(int fd) +{ +/* FIXME: is this widely supported? */ +#ifdef SO_PRIORITY + int priority; + assert(fd >= 0); + + priority = 6; + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, (void*)&priority, + sizeof(priority)) < 0) + ERR("SO_PRIORITY failed: %s", strerror(errno)); +#endif +} + +static int read_stream(struct userdata *u) +{ + int ret = 0; + ssize_t l; + char *buf; + + assert(u); + assert(u->stream_fd >= 0); + + buf = alloca(u->link_mtu); + + for (;;) { + l = read(u->stream_fd, buf, u->link_mtu); + if (u->debug_stream_read) + DBG("read from socket: %lli bytes", (long long) l); + if (l <= 0) { + if (l < 0 && errno == EINTR) + continue; + else { + ERR("Failed to read date from stream_fd: %s", + ret < 0 ? strerror(errno) : "EOF"); + return -1; + } + } else { + break; + } + } + + return ret; +} + +/* It's what PulseAudio is doing, not sure it's necessary for this + * test */ +static ssize_t pa_write(int fd, const void *buf, size_t count) +{ + ssize_t r; + + if ((r = send(fd, buf, count, MSG_NOSIGNAL)) >= 0) + return r; + + if (errno != ENOTSOCK) + return r; + + return write(fd, buf, count); +} + +static int write_stream(struct userdata *u) +{ + int ret = 0; + ssize_t l; + char *buf; + + assert(u); + assert(u->stream_fd >= 0); + buf = alloca(u->link_mtu); + + for (;;) { + l = pa_write(u->stream_fd, buf, u->link_mtu); + if (u->debug_stream_write) + DBG("written to socket: %lli bytes", (long long) l); + assert(l != 0); + if (l < 0) { + if (errno == EINTR) + continue; + else { + ERR("Failed to write data: %s", strerror(errno)); + ret = -1; + break; + } + } else { + assert((size_t)l <= u->link_mtu); + break; + } + } + + return ret; +} + +static gboolean stream_cb(GIOChannel *gin, GIOCondition condition, gpointer data) +{ + struct userdata *u; + + assert(u = data); + + if (condition & G_IO_IN) { + if (read_stream(u) < 0) + goto fail; + } else if (condition & G_IO_OUT) { + if (write_stream(u) < 0) + goto fail; + } else { + DBG("Got %d", condition); + g_main_loop_quit(main_loop); + return FALSE; + } + + return TRUE; + +fail: + stop_stream(u); + return FALSE; +} + +static int start_stream(struct userdata *u) +{ + union { + bt_audio_msg_header_t rsp; + struct bt_start_stream_req start_req; + struct bt_start_stream_rsp start_rsp; + struct bt_new_stream_ind streamfd_ind; + bt_audio_error_t error; + uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; + } msg; + + assert(u); + + if (u->stream_fd >= 0) + return 0; + if (u->stream_watch != 0) { + g_source_remove(u->stream_watch); + u->stream_watch = 0; + } + if (u->stream_channel != 0) { + g_io_channel_unref(u->stream_channel); + u->stream_channel = NULL; + } + + memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE); + msg.start_req.h.type = BT_REQUEST; + msg.start_req.h.name = BT_START_STREAM; + msg.start_req.h.length = sizeof(msg.start_req); + + if (service_send(u, &msg.start_req.h) < 0) + return -1; + + msg.rsp.length = sizeof(msg.start_rsp); + if (service_expect(u, &msg.rsp, BT_START_STREAM) < 0) + return -1; + + msg.rsp.length = sizeof(msg.streamfd_ind); + if (service_expect(u, &msg.rsp, BT_NEW_STREAM) < 0) + return -1; + + if ((u->stream_fd = bt_audio_service_get_data_fd(u->service_fd)) < 0) { + DBG("Failed to get stream fd from audio service."); + return -1; + } + + make_fd_nonblock(u->stream_fd); + make_socket_low_delay(u->stream_fd); + + assert(u->stream_channel = g_io_channel_unix_new(u->stream_fd)); + + u->stream_watch = g_io_add_watch(u->stream_channel, + G_IO_IN|G_IO_OUT|G_IO_ERR|G_IO_HUP|G_IO_NVAL, + stream_cb, u); + + return 0; +} + +static int stop_stream(struct userdata *u) +{ + union { + bt_audio_msg_header_t rsp; + struct bt_stop_stream_req stop_req; + struct bt_stop_stream_rsp stop_rsp; + bt_audio_error_t error; + uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; + } msg; + int r = 0; + + if (u->stream_fd < 0) + return 0; + + assert(u); + assert(u->stream_channel); + + g_source_remove(u->stream_watch); + u->stream_watch = 0; + g_io_channel_unref(u->stream_channel); + u->stream_channel = NULL; + + memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE); + msg.stop_req.h.type = BT_REQUEST; + msg.stop_req.h.name = BT_STOP_STREAM; + msg.stop_req.h.length = sizeof(msg.stop_req); + + if (service_send(u, &msg.stop_req.h) < 0) { + r = -1; + goto done; + } + + msg.rsp.length = sizeof(msg.stop_rsp); + if (service_expect(u, &msg.rsp, BT_STOP_STREAM) < 0) + r = -1; + +done: + close(u->stream_fd); + u->stream_fd = -1; + + return r; +} + +static gboolean sleep_cb(gpointer data) +{ + struct userdata *u; + + assert(u = data); + + u->gin_watch = g_io_add_watch(u->gin, + G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, data); + + printf(">>> "); + fflush(stdout); + + return FALSE; +} + +static gboolean input_cb(GIOChannel *gin, GIOCondition condition, gpointer data) +{ + char *line, *tmp; + gsize term_pos; + GError *error = NULL; + struct userdata *u; + int success; + + assert(u = data); + if (!(condition & G_IO_IN)) { + DBG("Got %d", condition); + g_main_loop_quit(main_loop); + return FALSE; + } + + if (g_io_channel_read_line(gin, &line, NULL, &term_pos, &error) != + G_IO_STATUS_NORMAL) + return FALSE; + + line[term_pos] = '\0'; + g_strstrip(line); + if ((tmp = strchr(line, '#'))) + *tmp = '\0'; + success = FALSE; + +#define IF_CMD(cmd) \ + if (!success && (success = (strncmp(line, #cmd, strlen(#cmd)) == 0))) + + IF_CMD(quit) { + g_main_loop_quit(main_loop); + return FALSE; + } + + IF_CMD(sleep) { + unsigned int seconds; + if (sscanf(line, "%*s %d", &seconds) != 1) + DBG("sleep SECONDS"); + else { + g_source_remove(u->gin_watch); + g_timeout_add_seconds(seconds, sleep_cb, u); + return FALSE; + } + } + + IF_CMD(debug) { + char *what = NULL; + int enable; + + if (sscanf(line, "%*s %as %d", &what, &enable) != 1) + DBG("debug [stream_read|stream_write] [0|1]"); + if (strncmp(what, "stream_read", 12) == 0) { + u->debug_stream_read = enable; + } else if (strncmp(what, "stream_write", 13) == 0) { + u->debug_stream_write = enable; + } else { + DBG("debug [stream_read|stream_write] [0|1]"); + } + } + + IF_CMD(init_bt) { + DBG("%d", init_bt(u)); + } + + IF_CMD(init_profile) { + DBG("%d", init_profile(u)); + } + + IF_CMD(start_stream) { + DBG("%d", start_stream(u)); + } + + IF_CMD(stop_stream) { + DBG("%d", stop_stream(u)); + } + + IF_CMD(shutdown_bt) { + shutdown_bt(u); + } + + IF_CMD(rate) { + if (sscanf(line, "%*s %d", &u->rate) != 1) + DBG("set with rate RATE"); + DBG("rate %d", u->rate); + } + + IF_CMD(bdaddr) { + char *address; + + if (sscanf(line, "%*s %as", &address) != 1) + DBG("set with bdaddr BDADDR"); + + if (u->address) + free(u->address); + + u->address = address; + DBG("bdaddr %s", u->address); + } + + IF_CMD(profile) { + char *profile = NULL; + + if (sscanf(line, "%*s %as", &profile) != 1) + DBG("set with profile [hsp|a2dp]"); + if (strncmp(profile, "hsp", 4) == 0) { + u->transport = BT_CAPABILITIES_TRANSPORT_SCO; + } else if (strncmp(profile, "a2dp", 5) == 0) { + u->transport = BT_CAPABILITIES_TRANSPORT_A2DP; + } else { + DBG("set with profile [hsp|a2dp]"); + } + + if (profile) + free(profile); + DBG("profile %s", u->transport == BT_CAPABILITIES_TRANSPORT_SCO ? + "hsp" : "a2dp"); + } + + if (!success && strlen(line) != 0) { + DBG("%s, unknown command", line); + } + + printf(">>> "); + fflush(stdout); + return TRUE; +} + + +static void show_usage(char* prgname) +{ + printf("%s: ipctest [--interactive] BDADDR\n", basename(prgname)); +} + +static void sig_term(int sig) +{ + g_main_loop_quit(main_loop); +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + show_usage(argv[0]); + exit(EXIT_FAILURE); + } + + assert(main_loop = g_main_loop_new(NULL, FALSE)); + + if (strncmp("--interactive", argv[1], 14) == 0) { + if (argc < 3) { + show_usage(argv[0]); + exit(EXIT_FAILURE); + } + + data.address = strdup(argv[2]); + + signal(SIGTERM, sig_term); + signal(SIGINT, sig_term); + + assert(data.gin = g_io_channel_unix_new(fileno(stdin))); + + data.gin_watch = g_io_add_watch(data.gin, + G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, &data); + + printf(">>> "); + fflush(stdout); + + g_main_loop_run(main_loop); + + } else { + data.address = strdup(argv[1]); + + assert(init_bt(&data) == 0); + + assert(init_profile(&data) == 0); + + assert(start_stream(&data) == 0); + + g_main_loop_run(main_loop); + + assert(stop_stream(&data) == 0); + + shutdown_bt(&data); + } + + g_main_loop_unref(main_loop); + + printf("\nExiting\n"); + + exit(EXIT_SUCCESS); + + return 0; +} diff --git a/audio/liba2dp.c b/audio/liba2dp.c new file mode 100755 index 000000000..740388660 --- /dev/null +++ b/audio/liba2dp.c @@ -0,0 +1,1204 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ipc.h" +#include "sbc.h" +#include "rtp.h" +#include "liba2dp.h" + +#define LOG_NDEBUG 0 +#define LOG_TAG "A2DP" +#include + +#define ENABLE_DEBUG +/* #define ENABLE_VERBOSE */ +/* #define ENABLE_TIMING */ + +#define BUFFER_SIZE 2048 + +#ifdef ENABLE_DEBUG +#define DBG LOGD +#else +#define DBG(fmt, arg...) +#endif + +#ifdef ENABLE_VERBOSE +#define VDBG LOGV +#else +#define VDBG(fmt, arg...) +#endif + +#ifndef MIN +# define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef MAX +# define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +#define ERR LOGE + +/* Number of packets to buffer in the stream socket */ +#define PACKET_BUFFER_COUNT 10 + +/* timeout in milliseconds to prevent poll() from hanging indefinitely */ +#define POLL_TIMEOUT 1000 + +/* timeout in milliseconds for a2dp_write */ +#define WRITE_TIMEOUT 1000 + +/* timeout in seconds for command socket recv() */ +#define RECV_TIMEOUT 5 + + +typedef enum { + A2DP_STATE_NONE = 0, + A2DP_STATE_INITIALIZED, + A2DP_STATE_CONFIGURING, + A2DP_STATE_CONFIGURED, + A2DP_STATE_STARTING, + A2DP_STATE_STARTED, + A2DP_STATE_STOPPING, +} a2dp_state_t; + +typedef enum { + A2DP_CMD_NONE = 0, + A2DP_CMD_INIT, + A2DP_CMD_CONFIGURE, + A2DP_CMD_START, + A2DP_CMD_STOP, + A2DP_CMD_QUIT, +} a2dp_command_t; + +struct bluetooth_data { + int link_mtu; /* MTU for transport channel */ + struct pollfd stream; /* Audio stream filedescriptor */ + struct pollfd server; /* Audio daemon filedescriptor */ + a2dp_state_t state; /* Current A2DP state */ + a2dp_command_t command; /* Current command for a2dp_thread */ + pthread_t thread; + pthread_mutex_t mutex; + int started; + pthread_cond_t thread_start; + pthread_cond_t thread_wait; + pthread_cond_t client_wait; + + sbc_capabilities_t sbc_capabilities; + sbc_t sbc; /* Codec data */ + int frame_duration; /* length of an SBC frame in microseconds */ + int codesize; /* SBC codesize */ + int samples; /* Number of encoded samples */ + uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */ + int count; /* Codec transfer buffer counter */ + + int nsamples; /* Cumulative number of codec samples */ + uint16_t seq_num; /* Cumulative packet sequence */ + int frame_count; /* Current frames in buffer*/ + + char address[20]; + int rate; + int channels; + + /* used for pacing our writes to the output socket */ + uint64_t next_write; +}; + +static uint64_t get_microseconds() +{ + struct timeval now; + gettimeofday(&now, NULL); + return (now.tv_sec * 1000000UL + now.tv_usec); +} + +#ifdef ENABLE_TIMING +static void print_time(const char* message, uint64_t then, uint64_t now) +{ + DBG("%s: %lld us", message, now - then); +} +#endif + +static int audioservice_send(struct bluetooth_data *data, const bt_audio_msg_header_t *msg); +static int audioservice_expect(struct bluetooth_data *data, bt_audio_msg_header_t *outmsg, + int expected_type); +static int bluetooth_a2dp_hw_params(struct bluetooth_data *data); +static void set_state(struct bluetooth_data *data, a2dp_state_t state); + + +static void bluetooth_close(struct bluetooth_data *data) +{ + DBG("bluetooth_close"); + if (data->server.fd >= 0) { + bt_audio_service_close(data->server.fd); + data->server.fd = -1; + } + + if (data->stream.fd >= 0) { + close(data->stream.fd); + data->stream.fd = -1; + } + + data->state = A2DP_STATE_NONE; +} + +static int bluetooth_start(struct bluetooth_data *data) +{ + char c = 'w'; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_req *start_req = (void*) buf; + struct bt_start_stream_rsp *start_rsp = (void*) buf; + struct bt_new_stream_ind *streamfd_ind = (void*) buf; + int opt_name, err, bytes; + + DBG("bluetooth_start"); + data->state = A2DP_STATE_STARTING; + /* send start */ + memset(start_req, 0, BT_SUGGESTED_BUFFER_SIZE); + start_req->h.type = BT_REQUEST; + start_req->h.name = BT_START_STREAM; + start_req->h.length = sizeof(*start_req); + + + err = audioservice_send(data, &start_req->h); + if (err < 0) + goto error; + + start_rsp->h.length = sizeof(*start_rsp); + err = audioservice_expect(data, &start_rsp->h, BT_START_STREAM); + if (err < 0) + goto error; + + streamfd_ind->h.length = sizeof(*streamfd_ind); + err = audioservice_expect(data, &streamfd_ind->h, BT_NEW_STREAM); + if (err < 0) + goto error; + + data->stream.fd = bt_audio_service_get_data_fd(data->server.fd); + if (data->stream.fd < 0) { + ERR("bt_audio_service_get_data_fd failed, errno: %d", errno); + err = -errno; + goto error; + } + data->stream.events = POLLOUT; + + /* set our socket buffer to the size of PACKET_BUFFER_COUNT packets */ + bytes = data->link_mtu * PACKET_BUFFER_COUNT; + setsockopt(data->stream.fd, SOL_SOCKET, SO_SNDBUF, &bytes, + sizeof(bytes)); + + data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + data->frame_count = 0; + data->samples = 0; + data->nsamples = 0; + data->seq_num = 0; + data->frame_count = 0; + data->next_write = 0; + + set_state(data, A2DP_STATE_STARTED); + return 0; + +error: + /* set state to A2DP_STATE_INITIALIZED to force reconfiguration */ + if (data->state == A2DP_STATE_STARTING) + set_state(data, A2DP_STATE_INITIALIZED); + return err; +} + +static int bluetooth_stop(struct bluetooth_data *data) +{ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_stop_stream_req *stop_req = (void*) buf; + struct bt_stop_stream_rsp *stop_rsp = (void*) buf; + int err; + + DBG("bluetooth_stop"); + + data->state = A2DP_STATE_STOPPING; + if (data->stream.fd >= 0) { + close(data->stream.fd); + data->stream.fd = -1; + } + + /* send stop request */ + memset(stop_req, 0, BT_SUGGESTED_BUFFER_SIZE); + stop_req->h.type = BT_REQUEST; + stop_req->h.name = BT_STOP_STREAM; + stop_req->h.length = sizeof(*stop_req); + + err = audioservice_send(data, &stop_req->h); + if (err < 0) + goto error; + + stop_rsp->h.length = sizeof(*stop_rsp); + err = audioservice_expect(data, &stop_rsp->h, BT_STOP_STREAM); + if (err < 0) + goto error; + +error: + if (data->state == A2DP_STATE_STOPPING) + set_state(data, A2DP_STATE_CONFIGURED); + return err; +} + +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case BT_SBC_SAMPLING_FREQ_16000: + case BT_SBC_SAMPLING_FREQ_32000: + return 53; + case BT_SBC_SAMPLING_FREQ_44100: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + ERR("Invalid channel mode %u", mode); + return 53; + } + case BT_SBC_SAMPLING_FREQ_48000: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + ERR("Invalid channel mode %u", mode); + return 51; + } + default: + ERR("Invalid sampling freq %u", freq); + return 53; + } +} + +static int bluetooth_a2dp_init(struct bluetooth_data *data) +{ + sbc_capabilities_t *cap = &data->sbc_capabilities; + unsigned int max_bitpool, min_bitpool; + int dir; + + switch (data->rate) { + case 48000: + cap->frequency = BT_SBC_SAMPLING_FREQ_48000; + break; + case 44100: + cap->frequency = BT_SBC_SAMPLING_FREQ_44100; + break; + case 32000: + cap->frequency = BT_SBC_SAMPLING_FREQ_32000; + break; + case 16000: + cap->frequency = BT_SBC_SAMPLING_FREQ_16000; + break; + default: + ERR("Rate %d not supported", data->rate); + return -1; + } + + if (data->channels == 2) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + } else { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } + + if (!cap->channel_mode) { + ERR("No supported channel modes"); + return -1; + } + + if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) + cap->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) + cap->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) + cap->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) + cap->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + ERR("No supported block lengths"); + return -1; + } + + if (cap->subbands & BT_A2DP_SUBBANDS_8) + cap->subbands = BT_A2DP_SUBBANDS_8; + else if (cap->subbands & BT_A2DP_SUBBANDS_4) + cap->subbands = BT_A2DP_SUBBANDS_4; + else { + ERR("No supported subbands"); + return -1; + } + + if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) + cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) + cap->allocation_method = BT_A2DP_ALLOCATION_SNR; + + min_bitpool = MAX(MIN_BITPOOL, cap->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, + cap->channel_mode), + cap->max_bitpool); + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return 0; +} + +static void bluetooth_a2dp_setup(struct bluetooth_data *data) +{ + sbc_capabilities_t active_capabilities = data->sbc_capabilities; + + sbc_reinit(&data->sbc, 0); + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000) + data->sbc.frequency = SBC_FREQ_16000; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000) + data->sbc.frequency = SBC_FREQ_32000; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100) + data->sbc.frequency = SBC_FREQ_44100; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000) + data->sbc.frequency = SBC_FREQ_48000; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + data->sbc.mode = SBC_MODE_MONO; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + data->sbc.mode = SBC_MODE_DUAL_CHANNEL; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + data->sbc.mode = SBC_MODE_STEREO; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + data->sbc.mode = SBC_MODE_JOINT_STEREO; + + data->sbc.allocation = active_capabilities.allocation_method + == BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR + : SBC_AM_LOUDNESS; + + switch (active_capabilities.subbands) { + case BT_A2DP_SUBBANDS_4: + data->sbc.subbands = SBC_SB_4; + break; + case BT_A2DP_SUBBANDS_8: + data->sbc.subbands = SBC_SB_8; + break; + } + + switch (active_capabilities.block_length) { + case BT_A2DP_BLOCK_LENGTH_4: + data->sbc.blocks = SBC_BLK_4; + break; + case BT_A2DP_BLOCK_LENGTH_8: + data->sbc.blocks = SBC_BLK_8; + break; + case BT_A2DP_BLOCK_LENGTH_12: + data->sbc.blocks = SBC_BLK_12; + break; + case BT_A2DP_BLOCK_LENGTH_16: + data->sbc.blocks = SBC_BLK_16; + break; + } + + data->sbc.bitpool = active_capabilities.max_bitpool; + data->codesize = sbc_get_codesize(&data->sbc); + data->frame_duration = sbc_get_frame_duration(&data->sbc); + DBG("frame_duration: %d us", data->frame_duration); +} + +static int bluetooth_a2dp_hw_params(struct bluetooth_data *data) +{ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_req *open_req = (void *) buf; + struct bt_open_rsp *open_rsp = (void *) buf; + struct bt_set_configuration_req *setconf_req = (void*) buf; + struct bt_set_configuration_rsp *setconf_rsp = (void*) buf; + int err; + + memset(open_req, 0, BT_SUGGESTED_BUFFER_SIZE); + open_req->h.type = BT_REQUEST; + open_req->h.name = BT_OPEN; + open_req->h.length = sizeof(*open_req); + strncpy(open_req->destination, data->address, 18); + open_req->seid = data->sbc_capabilities.capability.seid; + open_req->lock = BT_WRITE_LOCK; + + err = audioservice_send(data, &open_req->h); + if (err < 0) + return err; + + open_rsp->h.length = sizeof(*open_rsp); + err = audioservice_expect(data, &open_rsp->h, BT_OPEN); + if (err < 0) + return err; + + err = bluetooth_a2dp_init(data); + if (err < 0) + return err; + + + memset(setconf_req, 0, BT_SUGGESTED_BUFFER_SIZE); + setconf_req->h.type = BT_REQUEST; + setconf_req->h.name = BT_SET_CONFIGURATION; + setconf_req->h.length = sizeof(*setconf_req); + memcpy(&setconf_req->codec, &data->sbc_capabilities, + sizeof(data->sbc_capabilities)); + + setconf_req->codec.transport = BT_CAPABILITIES_TRANSPORT_A2DP; + setconf_req->codec.length = sizeof(data->sbc_capabilities); + setconf_req->h.length += setconf_req->codec.length - sizeof(setconf_req->codec); + + DBG("bluetooth_a2dp_hw_params sending configuration:\n"); + switch (data->sbc_capabilities.channel_mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + DBG("\tchannel_mode: MONO\n"); + break; + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + DBG("\tchannel_mode: DUAL CHANNEL\n"); + break; + case BT_A2DP_CHANNEL_MODE_STEREO: + DBG("\tchannel_mode: STEREO\n"); + break; + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + DBG("\tchannel_mode: JOINT STEREO\n"); + break; + default: + DBG("\tchannel_mode: UNKNOWN (%d)\n", + data->sbc_capabilities.channel_mode); + } + switch (data->sbc_capabilities.frequency) { + case BT_SBC_SAMPLING_FREQ_16000: + DBG("\tfrequency: 16000\n"); + break; + case BT_SBC_SAMPLING_FREQ_32000: + DBG("\tfrequency: 32000\n"); + break; + case BT_SBC_SAMPLING_FREQ_44100: + DBG("\tfrequency: 44100\n"); + break; + case BT_SBC_SAMPLING_FREQ_48000: + DBG("\tfrequency: 48000\n"); + break; + default: + DBG("\tfrequency: UNKNOWN (%d)\n", + data->sbc_capabilities.frequency); + } + switch (data->sbc_capabilities.allocation_method) { + case BT_A2DP_ALLOCATION_SNR: + DBG("\tallocation_method: SNR\n"); + break; + case BT_A2DP_ALLOCATION_LOUDNESS: + DBG("\tallocation_method: LOUDNESS\n"); + break; + default: + DBG("\tallocation_method: UNKNOWN (%d)\n", + data->sbc_capabilities.allocation_method); + } + switch (data->sbc_capabilities.subbands) { + case BT_A2DP_SUBBANDS_4: + DBG("\tsubbands: 4\n"); + break; + case BT_A2DP_SUBBANDS_8: + DBG("\tsubbands: 8\n"); + break; + default: + DBG("\tsubbands: UNKNOWN (%d)\n", + data->sbc_capabilities.subbands); + } + switch (data->sbc_capabilities.block_length) { + case BT_A2DP_BLOCK_LENGTH_4: + DBG("\tblock_length: 4\n"); + break; + case BT_A2DP_BLOCK_LENGTH_8: + DBG("\tblock_length: 8\n"); + break; + case BT_A2DP_BLOCK_LENGTH_12: + DBG("\tblock_length: 12\n"); + break; + case BT_A2DP_BLOCK_LENGTH_16: + DBG("\tblock_length: 16\n"); + break; + default: + DBG("\tblock_length: UNKNOWN (%d)\n", + data->sbc_capabilities.block_length); + } + DBG("\tmin_bitpool: %d\n", data->sbc_capabilities.min_bitpool); + DBG("\tmax_bitpool: %d\n", data->sbc_capabilities.max_bitpool); + + err = audioservice_send(data, &setconf_req->h); + if (err < 0) + return err; + + err = audioservice_expect(data, &setconf_rsp->h, BT_SET_CONFIGURATION); + if (err < 0) + return err; + + data->link_mtu = setconf_rsp->link_mtu; + DBG("MTU: %d", data->link_mtu); + + /* Setup SBC encoder now we agree on parameters */ + bluetooth_a2dp_setup(data); + + DBG("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", + data->sbc.allocation, data->sbc.subbands, data->sbc.blocks, + data->sbc.bitpool); + + return 0; +} + +static int avdtp_write(struct bluetooth_data *data) +{ + int ret = 0; + struct rtp_header *header; + struct rtp_payload *payload; + + uint64_t now; + long duration = data->frame_duration * data->frame_count; +#ifdef ENABLE_TIMING + uint64_t begin, end, begin2, end2; + begin = get_microseconds(); +#endif + + header = (struct rtp_header *)data->buffer; + payload = (struct rtp_payload *)(data->buffer + sizeof(*header)); + + memset(data->buffer, 0, sizeof(*header) + sizeof(*payload)); + + payload->frame_count = data->frame_count; + header->v = 2; + header->pt = 1; + header->sequence_number = htons(data->seq_num); + header->timestamp = htonl(data->nsamples); + header->ssrc = htonl(1); + + data->stream.revents = 0; +#ifdef ENABLE_TIMING + begin2 = get_microseconds(); +#endif + ret = poll(&data->stream, 1, POLL_TIMEOUT); +#ifdef ENABLE_TIMING + end2 = get_microseconds(); + print_time("poll", begin2, end2); +#endif + if (ret == 1 && data->stream.revents == POLLOUT) { + long ahead = 0; + now = get_microseconds(); + + if (data->next_write) { + ahead = data->next_write - now; +#ifdef ENABLE_TIMING + DBG("duration: %ld, ahead: %ld", duration, ahead); +#endif + if (ahead > 0) { + /* too fast, need to throttle */ + usleep(ahead); + } + } else { + data->next_write = now; + } + if (ahead < 0) { + /* fallen too far behind, don't try to catch up */ + VDBG("ahead < 0, resetting next_write"); + data->next_write = 0; + } else { + /* advance next_write by duration */ + data->next_write += duration; + } + +#ifdef ENABLE_TIMING + begin2 = get_microseconds(); +#endif + ret = send(data->stream.fd, data->buffer, data->count, MSG_NOSIGNAL); +#ifdef ENABLE_TIMING + end2 = get_microseconds(); + print_time("send", begin2, end2); +#endif + if (ret < 0) { + /* can happen during normal remote disconnect */ + VDBG("send() failed: %d (errno %s)", ret, strerror(errno)); + } + if (ret == -EPIPE) { + bluetooth_close(data); + } + } else { + /* can happen during normal remote disconnect */ + VDBG("poll() failed: %d (revents = %d, errno %s)", + ret, data->stream.revents, strerror(errno)); + } + + /* Reset buffer of data to send */ + data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + data->frame_count = 0; + data->samples = 0; + data->seq_num++; + +#ifdef ENABLE_TIMING + end = get_microseconds(); + print_time("avdtp_write", begin, end); +#endif + return 0; /* always return success */ +} + +static int audioservice_send(struct bluetooth_data *data, + const bt_audio_msg_header_t *msg) +{ + int err; + uint16_t length; + + length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE; + + VDBG("sending %s", bt_audio_strmsg(msg->msg_type)); + if (send(data->server.fd, msg, length, + MSG_NOSIGNAL) > 0) + err = 0; + else { + err = -errno; + ERR("Error sending data to audio service: %s(%d)", + strerror(errno), errno); + if (err == -EPIPE) + bluetooth_close(data); + } + + return err; +} + +static int audioservice_recv(struct bluetooth_data *data, + bt_audio_msg_header_t *inmsg) +{ + int err, ret; + const char *type, *name; + uint16_t length; + + length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE; + + ret = recv(data->server.fd, inmsg, length, 0); + if (ret < 0) { + err = -errno; + ERR("Error receiving IPC data from bluetoothd: %s (%d)", + strerror(errno), errno); + if (err == -EPIPE) + bluetooth_close(data); + } else if ((size_t) ret < sizeof(bt_audio_msg_header_t)) { + ERR("Too short (%d bytes) IPC packet from bluetoothd", ret); + err = -EINVAL; + } else if (inmsg->type == BT_ERROR) { + bt_audio_error_t *error = (bt_audio_error_t *)inmsg; + ret = recv(data->server.fd, &error->posix_errno, + sizeof(error->posix_errno), 0); + if (ret < 0) { + err = -errno; + ERR("Error receiving error code for BT_ERROR: %s (%d)", + strerror(errno), errno); + if (err == -EPIPE) + bluetooth_close(data); + } else { + ERR("%s failed : %s(%d)", + bt_audio_strname(error->h.name), + strerror(error->posix_errno), + error->posix_errno); + err = -error->posix_errno; + } + } else { + type = bt_audio_strtype(inmsg->type); + name = bt_audio_strname(inmsg->name); + if (type && name) { + DBG("Received %s - %s", type, name); + err = 0; + } else { + err = -EINVAL; + ERR("Bogus message type %d - name %d" + " received from audio service", + inmsg->type, inmsg->name); + } + + } + return err; +} + +static int audioservice_expect(struct bluetooth_data *data, + bt_audio_msg_header_t *rsp_hdr, int expected_name) +{ + int err = audioservice_recv(data, rsp_hdr); + + if (err != 0) + return err; + + if (rsp_hdr->name != expected_name) { + err = -EINVAL; + ERR("Bogus message %s received while %s was expected", + bt_audio_strname(rsp_hdr->name), + bt_audio_strname(expected_name)); + } + return err; + +} + +static int bluetooth_init(struct bluetooth_data *data) +{ + int sk, err; + struct timeval tv = {.tv_sec = RECV_TIMEOUT}; + + DBG("bluetooth_init"); + + sk = bt_audio_service_open(); + if (sk <= 0) { + ERR("bt_audio_service_open failed\n"); + return -errno; + } + + setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + data->server.fd = sk; + data->server.events = POLLIN; + data->state = A2DP_STATE_INITIALIZED; + + return 0; +} + +static int bluetooth_parse_capabilities(struct bluetooth_data *data, + struct bt_get_capabilities_rsp *rsp) +{ + int bytes_left = rsp->h.length - sizeof(*rsp); + codec_capabilities_t *codec = (void *) rsp->data; + + if (codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) + return 0; + + while (bytes_left > 0) { + if ((codec->type == BT_A2DP_SBC_SINK) && + !(codec->lock & BT_WRITE_LOCK)) + break; + + bytes_left -= codec->length; + codec = (void *) codec + codec->length; + } + + if (bytes_left <= 0 || + codec->length != sizeof(data->sbc_capabilities)) + return -EINVAL; + + memcpy(&data->sbc_capabilities, codec, codec->length); + return 0; +} + + +static int bluetooth_configure(struct bluetooth_data *data) +{ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_req *getcaps_req = (void*) buf; + struct bt_get_capabilities_rsp *getcaps_rsp = (void*) buf; + int err; + + DBG("bluetooth_configure"); + + data->state = A2DP_STATE_CONFIGURING; + memset(getcaps_req, 0, BT_SUGGESTED_BUFFER_SIZE); + getcaps_req->h.type = BT_REQUEST; + getcaps_req->h.name = BT_GET_CAPABILITIES; + + getcaps_req->flags = 0; + getcaps_req->flags |= BT_FLAG_AUTOCONNECT; + strncpy(getcaps_req->destination, data->address, 18); + getcaps_req->transport = BT_CAPABILITIES_TRANSPORT_A2DP; + getcaps_req->h.length = sizeof(*getcaps_req); + + err = audioservice_send(data, &getcaps_req->h); + if (err < 0) { + ERR("audioservice_send failed for BT_GETCAPABILITIES_REQ\n"); + goto error; + } + + getcaps_rsp->h.length = 0; + err = audioservice_expect(data, &getcaps_rsp->h, BT_GET_CAPABILITIES); + if (err < 0) { + ERR("audioservice_expect failed for BT_GETCAPABILITIES_RSP\n"); + goto error; + } + + bluetooth_parse_capabilities(data, getcaps_rsp); + err = bluetooth_a2dp_hw_params(data); + if (err < 0) { + ERR("bluetooth_a2dp_hw_params failed err: %d", err); + goto error; + } + + set_state(data, A2DP_STATE_CONFIGURED); + return 0; + +error: + if (data->state == A2DP_STATE_CONFIGURING) + set_state(data, A2DP_STATE_INITIALIZED); + return err; +} + +static void set_state(struct bluetooth_data *data, a2dp_state_t state) +{ + data->state = state; + pthread_cond_signal(&data->client_wait); +} + +static void __set_command(struct bluetooth_data *data, a2dp_command_t command) +{ + VDBG("set_command %d\n", command); + data->command = command; + pthread_cond_signal(&data->thread_wait); + return; +} + +static void set_command(struct bluetooth_data *data, a2dp_command_t command) +{ + pthread_mutex_lock(&data->mutex); + __set_command(data, command); + pthread_mutex_unlock(&data->mutex); +} + +/* timeout is in milliseconds */ +static int wait_for_start(struct bluetooth_data *data, int timeout) +{ + a2dp_state_t state = data->state; + struct timeval tv; + struct timespec ts; + int err = 0; + +#ifdef ENABLE_TIMING + uint64_t begin, end; + begin = get_microseconds(); +#endif + + gettimeofday(&tv, (struct timezone *) NULL); + ts.tv_sec = tv.tv_sec + (timeout / 1000); + ts.tv_nsec = (tv.tv_usec + (timeout % 1000) * 1000L ) * 1000L; + + pthread_mutex_lock(&data->mutex); + while (state != A2DP_STATE_STARTED) { + if (state == A2DP_STATE_NONE) + __set_command(data, A2DP_CMD_INIT); + else if (state == A2DP_STATE_INITIALIZED) + __set_command(data, A2DP_CMD_CONFIGURE); + else if (state == A2DP_STATE_CONFIGURED) { + __set_command(data, A2DP_CMD_START); + } +again: + err = pthread_cond_timedwait(&data->client_wait, &data->mutex, &ts); + if (err) { + /* don't timeout if we're done */ + if (data->state == A2DP_STATE_STARTED) { + err = 0; + break; + } + if (err == ETIMEDOUT) + break; + goto again; + } + + if (state == data->state) + goto again; + + state = data->state; + + if (state == A2DP_STATE_NONE) { + err = ENODEV; + break; + } + } + pthread_mutex_unlock(&data->mutex); + +#ifdef ENABLE_TIMING + end = get_microseconds(); + print_time("wait_for_start", begin, end); +#endif + + /* pthread_cond_timedwait returns positive errors */ + return -err; +} + +static void a2dp_free(struct bluetooth_data *data) +{ + pthread_cond_destroy(&data->client_wait); + pthread_cond_destroy(&data->thread_wait); + pthread_cond_destroy(&data->thread_start); + pthread_mutex_destroy(&data->mutex); + free(data); + return; +} + +static void* a2dp_thread(void *d) +{ + struct bluetooth_data* data = (struct bluetooth_data*)d; + a2dp_command_t command = A2DP_CMD_NONE; + + DBG("a2dp_thread started"); + prctl(PR_SET_NAME, "a2dp_thread", 0, 0, 0); + + pthread_mutex_lock(&data->mutex); + + data->started = 1; + pthread_cond_signal(&data->thread_start); + + while (1) + { + while (1) { + pthread_cond_wait(&data->thread_wait, &data->mutex); + + /* Initialization needed */ + if (data->state == A2DP_STATE_NONE && + data->command != A2DP_CMD_QUIT) { + bluetooth_init(data); + } + + /* New state command signaled */ + if (command != data->command) { + command = data->command; + break; + } + } + + switch (command) { + case A2DP_CMD_CONFIGURE: + if (data->state != A2DP_STATE_INITIALIZED) + break; + bluetooth_configure(data); + break; + + case A2DP_CMD_START: + if (data->state != A2DP_STATE_CONFIGURED) + break; + bluetooth_start(data); + break; + + case A2DP_CMD_STOP: + if (data->state != A2DP_STATE_STARTED) + break; + bluetooth_stop(data); + break; + + case A2DP_CMD_QUIT: + bluetooth_close(data); + sbc_finish(&data->sbc); + a2dp_free(data); + goto done; + + case A2DP_CMD_INIT: + /* already called bluetooth_init() */ + default: + break; + } + } + +done: + pthread_mutex_unlock(&data->mutex); + DBG("a2dp_thread finished"); + return NULL; +} + +int a2dp_init(int rate, int channels, a2dpData* dataPtr) +{ + struct bluetooth_data* data; + pthread_attr_t attr; + int err; + + DBG("a2dp_init rate: %d channels: %d", rate, channels); + *dataPtr = NULL; + data = malloc(sizeof(struct bluetooth_data)); + if (!data) + return -1; + + memset(data, 0, sizeof(struct bluetooth_data)); + data->server.fd = -1; + data->stream.fd = -1; + data->state = A2DP_STATE_NONE; + data->command = A2DP_CMD_NONE; + + strncpy(data->address, "00:00:00:00:00:00", 18); + data->rate = rate; + data->channels = channels; + + sbc_init(&data->sbc, 0); + + pthread_mutex_init(&data->mutex, NULL); + pthread_cond_init(&data->thread_start, NULL); + pthread_cond_init(&data->thread_wait, NULL); + pthread_cond_init(&data->client_wait, NULL); + + pthread_mutex_lock(&data->mutex); + data->started = 0; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + err = pthread_create(&data->thread, &attr, a2dp_thread, data); + if (err) { + /* If the thread create fails we must not wait */ + pthread_mutex_unlock(&data->mutex); + err = -err; + goto error; + } + + /* Make sure the state machine is ready and waiting */ + while (!data->started) { + pthread_cond_wait(&data->thread_start, &data->mutex); + } + + /* Poke the state machine to get it going */ + pthread_cond_signal(&data->thread_wait); + + pthread_mutex_unlock(&data->mutex); + pthread_attr_destroy(&attr); + + *dataPtr = data; + return 0; +error: + bluetooth_close(data); + sbc_finish(&data->sbc); + pthread_attr_destroy(&attr); + a2dp_free(data); + + return err; +} + +void a2dp_set_sink(a2dpData d, const char* address) +{ + struct bluetooth_data* data = (struct bluetooth_data*)d; + if (strncmp(data->address, address, 18)) { + strncpy(data->address, address, 18); + set_command(data, A2DP_CMD_INIT); + } +} + +int a2dp_write(a2dpData d, const void* buffer, int count) +{ + struct bluetooth_data* data = (struct bluetooth_data*)d; + uint8_t* src = (uint8_t *)buffer; + int codesize; + int err, ret = 0; + long frames_left = count; + int encoded, written; + const char *buff; + int did_configure = 0; +#ifdef ENABLE_TIMING + uint64_t begin, end; + DBG("********** a2dp_write **********"); + begin = get_microseconds(); +#endif + + err = wait_for_start(data, WRITE_TIMEOUT); + if (err < 0) + return err; + + codesize = data->codesize; + + while (frames_left >= codesize) { + /* Enough data to encode (sbc wants 512 byte blocks) */ + encoded = sbc_encode(&(data->sbc), src, codesize, + data->buffer + data->count, + sizeof(data->buffer) - data->count, + &written); + if (encoded <= 0) { + ERR("Encoding error %d", encoded); + goto done; + } + VDBG("sbc_encode returned %d, codesize: %d, written: %d\n", + encoded, codesize, written); + + src += encoded; + data->count += written; + data->frame_count++; + data->samples += encoded; + data->nsamples += encoded; + + /* No space left for another frame then send */ + if ((data->count + written >= data->link_mtu) || + (data->count + written >= BUFFER_SIZE)) { + VDBG("sending packet %d, count %d, link_mtu %u", + data->seq_num, data->count, + data->link_mtu); + err = avdtp_write(data); + if (err < 0) + return err; + } + + ret += encoded; + frames_left -= encoded; + } + + if (frames_left > 0) + ERR("%ld bytes left at end of a2dp_write\n", frames_left); + +done: +#ifdef ENABLE_TIMING + end = get_microseconds(); + print_time("a2dp_write total", begin, end); +#endif + return ret; +} + +int a2dp_stop(a2dpData d) +{ + struct bluetooth_data* data = (struct bluetooth_data*)d; + DBG("a2dp_stop\n"); + if (!data) + return 0; + + set_command(data, A2DP_CMD_STOP); + return 0; +} + +void a2dp_cleanup(a2dpData d) +{ + struct bluetooth_data* data = (struct bluetooth_data*)d; + DBG("a2dp_cleanup\n"); + set_command(data, A2DP_CMD_QUIT); +} diff --git a/audio/liba2dp.h b/audio/liba2dp.h new file mode 100644 index 000000000..5a9ef343b --- /dev/null +++ b/audio/liba2dp.h @@ -0,0 +1,39 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* a2dpData; + +int a2dp_init(int rate, int channels, a2dpData* dataPtr); +void a2dp_set_sink(a2dpData data, const char* address); +int a2dp_write(a2dpData data, const void* buffer, int count); +int a2dp_stop(a2dpData data); +void a2dp_cleanup(a2dpData data); + +#ifdef __cplusplus +} +#endif diff --git a/audio/main.c b/audio/main.c new file mode 100644 index 000000000..9defe60ae --- /dev/null +++ b/audio/main.c @@ -0,0 +1,203 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "glib-helper.h" +#include "btio.h" +#include "plugin.h" +#include "logging.h" +#include "device.h" +#include "unix.h" +#include "headset.h" +#include "manager.h" +#include "gateway.h" + +static GIOChannel *sco_server = NULL; + +static GKeyFile *load_config_file(const char *file) +{ + GError *err = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + + g_key_file_set_list_separator(keyfile, ','); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + error("Parsing %s failed: %s", file, err->message); + g_error_free(err); + g_key_file_free(keyfile); + return NULL; + } + + return keyfile; +} + +static void sco_server_cb(GIOChannel *chan, GError *err, gpointer data) +{ + int sk; + struct audio_device *device; + char addr[18]; + bdaddr_t src, dst; + + if (err) { + error("sco_server_cb: %s", err->message); + return; + } + + bt_io_get(chan, BT_IO_SCO, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, addr, + BT_IO_OPT_INVALID); + if (err) { + error("bt_io_get: %s", err->message); + goto drop; + } + + device = manager_find_device(NULL, &src, &dst, AUDIO_HEADSET_INTERFACE, + FALSE); + if (!device) + device = manager_find_device(NULL, &src, &dst, + AUDIO_GATEWAY_INTERFACE, + FALSE); + + if (!device) + goto drop; + + if (device->headset) { + if (headset_get_state(device) < HEADSET_STATE_CONNECTED) { + debug("Refusing SCO from non-connected headset"); + goto drop; + } + + if (!get_hfp_active(device)) { + error("Refusing non-HFP SCO connect attempt from %s", + addr); + goto drop; + } + + if (headset_connect_sco(device, chan) < 0) + goto drop; + + headset_set_state(device, HEADSET_STATE_PLAYING); + } else if (device->gateway) { + if (!gateway_is_connected(device)) { + debug("Refusing SCO from non-connected AG"); + goto drop; + } + + if (gateway_connect_sco(device, chan) < 0) + goto drop; + } else + goto drop; + + sk = g_io_channel_unix_get_fd(chan); + fcntl(sk, F_SETFL, 0); + + debug("Accepted SCO connection from %s", addr); + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static DBusConnection *connection; + +static int audio_init(void) +{ + GKeyFile *config; + gboolean enable_sco; + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (connection == NULL) + return -EIO; + + config = load_config_file(CONFIGDIR "/audio.conf"); + + if (unix_init() < 0) { + error("Unable to setup unix socket"); + goto failed; + } + + if (audio_manager_init(connection, config, &enable_sco) < 0) + goto failed; + + if (!enable_sco) + return 0; + + sco_server = bt_io_listen(BT_IO_SCO, sco_server_cb, NULL, NULL, + NULL, NULL, + BT_IO_OPT_INVALID); + if (!sco_server) { + error("Unable to start SCO server socket"); + goto failed; + } + + return 0; + +failed: + audio_manager_exit(); + unix_exit(); + + if (connection) { + dbus_connection_unref(connection); + connection = NULL; + } + + return -EIO; +} + +static void audio_exit(void) +{ + if (sco_server) { + g_io_channel_shutdown(sco_server, TRUE, NULL); + g_io_channel_unref(sco_server); + sco_server = NULL; + } + + audio_manager_exit(); + + unix_exit(); + + dbus_connection_unref(connection); +} + +BLUETOOTH_PLUGIN_DEFINE(audio, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, audio_init, audio_exit) diff --git a/audio/manager.c b/audio/manager.c new file mode 100644 index 000000000..ca4b1ab61 --- /dev/null +++ b/audio/manager.c @@ -0,0 +1,1276 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "glib-helper.h" +#include "btio.h" +#include "../src/manager.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#include "logging.h" +#include "textfile.h" +#include "ipc.h" +#include "device.h" +#include "error.h" +#include "avdtp.h" +#include "a2dp.h" +#include "headset.h" +#include "gateway.h" +#include "sink.h" +#include "source.h" +#include "control.h" +#include "manager.h" +#include "sdpd.h" +#include "telephony.h" + +typedef enum { + HEADSET = 1 << 0, + GATEWAY = 1 << 1, + SINK = 1 << 2, + SOURCE = 1 << 3, + CONTROL = 1 << 4, + TARGET = 1 << 5, + INVALID = 1 << 6 +} audio_service_type; + +typedef enum { + GENERIC_AUDIO = 0, + ADVANCED_AUDIO, + AV_REMOTE, + GET_RECORDS +} audio_sdp_state_t; + +struct audio_adapter { + struct btd_adapter *btd_adapter; + uint32_t hsp_ag_record_id; + uint32_t hfp_ag_record_id; + uint32_t hfp_hs_record_id; + GIOChannel *hsp_ag_server; + GIOChannel *hfp_ag_server; + GIOChannel *hfp_hs_server; + gint ref; +}; + +static gboolean auto_connect = TRUE; +static int max_connected_headsets = 1; +static DBusConnection *connection = NULL; +static GKeyFile *config = NULL; +static GSList *adapters = NULL; +static GSList *devices = NULL; + +static struct enabled_interfaces enabled = { + .hfp = TRUE, + .headset = TRUE, + .gateway = FALSE, + .sink = TRUE, + .source = FALSE, + .control = TRUE, +}; + +static struct audio_adapter *find_adapter(GSList *list, + struct btd_adapter *btd_adapter) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct audio_adapter *adapter = l->data; + + if (adapter->btd_adapter == btd_adapter) + return adapter; + } + + return NULL; +} + +gboolean server_is_enabled(bdaddr_t *src, uint16_t svc) +{ + switch (svc) { + case HEADSET_SVCLASS_ID: + return enabled.headset; + case HEADSET_AGW_SVCLASS_ID: + return FALSE; + case HANDSFREE_SVCLASS_ID: + return enabled.headset && enabled.hfp; + case HANDSFREE_AGW_SVCLASS_ID: + return enabled.gateway; + case AUDIO_SINK_SVCLASS_ID: + return enabled.sink; + case AUDIO_SOURCE_SVCLASS_ID: + return enabled.source; + case AV_REMOTE_TARGET_SVCLASS_ID: + case AV_REMOTE_SVCLASS_ID: + return enabled.control; + default: + return FALSE; + } +} + +static void handle_uuid(const char *uuidstr, struct audio_device *device) +{ + uuid_t uuid; + uint16_t uuid16; + + if (bt_string2uuid(&uuid, uuidstr) < 0) { + error("%s not detected as an UUID-128", uuidstr); + return; + } + + if (!sdp_uuid128_to_uuid(&uuid) && uuid.type != SDP_UUID16) { + error("Could not convert %s to a UUID-16", uuidstr); + return; + } + + uuid16 = uuid.value.uuid16; + + if (!server_is_enabled(&device->src, uuid16)) { + debug("audio handle_uuid: server not enabled for %s (0x%04x)", + uuidstr, uuid16); + return; + } + + switch (uuid16) { + case HEADSET_SVCLASS_ID: + debug("Found Headset record"); + if (device->headset) + headset_update(device, uuid16, uuidstr); + else + device->headset = headset_init(device, uuid16, + uuidstr); + break; + case HEADSET_AGW_SVCLASS_ID: + debug("Found Headset AG record"); + break; + case HANDSFREE_SVCLASS_ID: + debug("Found Handsfree record"); + if (device->headset) + headset_update(device, uuid16, uuidstr); + else + device->headset = headset_init(device, uuid16, + uuidstr); + break; + case HANDSFREE_AGW_SVCLASS_ID: + debug("Found Handsfree AG record"); + if (device->gateway == NULL) + device->gateway = gateway_init(device); + break; + case AUDIO_SINK_SVCLASS_ID: + debug("Found Audio Sink"); + if (device->sink == NULL) + device->sink = sink_init(device); + break; + case AUDIO_SOURCE_SVCLASS_ID: + debug("Found Audio Source"); + if (device->source == NULL) + device->source = source_init(device); + break; + case AV_REMOTE_SVCLASS_ID: + case AV_REMOTE_TARGET_SVCLASS_ID: + debug("Found AV %s", uuid16 == AV_REMOTE_SVCLASS_ID ? + "Remote" : "Target"); + if (device->control) + control_update(device, uuid16); + else + device->control = control_init(device, uuid16); + if (device->sink && sink_is_active(device)) + avrcp_connect(device); + break; + default: + debug("Unrecognized UUID: 0x%04X", uuid16); + break; + } +} + +static sdp_record_t *hsp_ag_record(uint8_t ch) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_record_t *record; + sdp_list_t *aproto, *proto[2]; + sdp_data_t *channel; + + 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); + + sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID); + profile.version = 0x0102; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Headset Audio Gateway", 0, 0); + + sdp_data_free(channel); + 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 sdp_record_t *hfp_hs_record(uint8_t ch) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_record_t *record; + sdp_list_t *aproto, *proto[2]; + sdp_data_t *channel; + + 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); + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID); + profile.version = 0x0100; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Hands-Free", 0, 0); + + sdp_data_free(channel); + 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 sdp_record_t *hfp_ag_record(uint8_t ch, uint32_t feat) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *channel, *features; + uint8_t netid = 0x01; + uint16_t sdpfeat; + sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid); + + 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); + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID); + profile.version = 0x0105; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + sdpfeat = (uint16_t) feat & 0xF; + features = sdp_data_alloc(SDP_UINT16, &sdpfeat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Hands-Free Audio Gateway", 0, 0); + + sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network); + + sdp_data_free(channel); + 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 void headset_auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *device = user_data; + GError *err = NULL; + GIOChannel *io; + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + return; + } + + io = headset_get_rfcomm(device); + + if (!bt_io_accept(io, headset_connect_cb, device, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + return; + } +} + +static void ag_confirm(GIOChannel *chan, gpointer data) +{ + const char *server_uuid, *remote_uuid; + struct audio_device *device; + gboolean hfp_active; + bdaddr_t src, dst; + int perr; + GError *err = NULL; + uint8_t ch; + + bt_io_get(chan, BT_IO_RFCOMM, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_CHANNEL, &ch, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + if (ch == DEFAULT_HS_AG_CHANNEL) { + hfp_active = FALSE; + server_uuid = HSP_AG_UUID; + remote_uuid = HSP_HS_UUID; + } else { + hfp_active = TRUE; + server_uuid = HFP_AG_UUID; + remote_uuid = HFP_HS_UUID; + } + + device = manager_get_device(&src, &dst, TRUE); + if (!device) + goto drop; + + if (!manager_allow_headset_connection(device)) { + debug("Refusing headset: too many existing connections"); + goto drop; + } + + if (!device->headset) { + btd_device_add_uuid(device->btd_dev, remote_uuid); + if (!device->headset) + goto drop; + } + + if (headset_get_state(device) > HEADSET_STATE_DISCONNECTED) { + debug("Refusing new connection since one already exists"); + goto drop; + } + + set_hfp_active(device, hfp_active); + + if (headset_connect_rfcomm(device, chan) < 0) { + error("headset_connect_rfcomm failed"); + goto drop; + } + + headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS); + + perr = audio_device_request_authorization(device, server_uuid, + headset_auth_cb, device); + if (perr < 0) { + debug("Authorization denied: %s", strerror(-perr)); + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + return; + } + + device->auto_connect = auto_connect; + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static void gateway_auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *device = user_data; + + if (derr && dbus_error_is_set(derr)) + error("Access denied: %s", derr->message); + else { + char ag_address[18]; + + ba2str(&device->dst, ag_address); + debug("Accepted AG connection from %s for %s", + ag_address, device->path); + + gateway_start_service(device); + } +} + +static void hf_io_cb(GIOChannel *chan, gpointer data) +{ + bdaddr_t src, dst; + GError *err = NULL; + uint8_t ch; + const char *server_uuid, *remote_uuid; + uint16_t svclass; + struct audio_device *device; + int perr; + + bt_io_get(chan, BT_IO_RFCOMM, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_CHANNEL, &ch, + BT_IO_OPT_INVALID); + + if (err) { + error("%s", err->message); + g_error_free(err); + return; + } + + server_uuid = HFP_HS_UUID; + remote_uuid = HFP_AG_UUID; + svclass = HANDSFREE_AGW_SVCLASS_ID; + + device = manager_get_device(&src, &dst, TRUE); + if (!device) + goto drop; + + if (!device->gateway) { + btd_device_add_uuid(device->btd_dev, remote_uuid); + if (!device->gateway) + goto drop; + } + + if (gateway_is_connected(device)) { + debug("Refusing new connection since one already exists"); + goto drop; + } + + if (gateway_connect_rfcomm(device, chan) < 0) { + error("Allocating new GIOChannel failed!"); + goto drop; + } + + perr = audio_device_request_authorization(device, server_uuid, + gateway_auth_cb, device); + if (perr < 0) { + debug("Authorization denied!"); + goto drop; + } + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); + g_io_channel_unref(chan); + return; +} + +static int headset_server_init(struct audio_adapter *adapter) +{ + uint8_t chan = DEFAULT_HS_AG_CHANNEL; + sdp_record_t *record; + gboolean master = TRUE; + GError *err = NULL; + uint32_t features; + GIOChannel *io; + bdaddr_t src; + + if (config) { + gboolean tmp; + + tmp = g_key_file_get_boolean(config, "General", "Master", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else + master = tmp; + } + + adapter_get_address(adapter->btd_adapter, &src); + + io = bt_io_listen(BT_IO_RFCOMM, NULL, ag_confirm, adapter, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) + goto failed; + + adapter->hsp_ag_server = io; + + record = hsp_ag_record(chan); + if (!record) { + error("Unable to allocate new service record"); + goto failed; + } + + if (add_record_to_server(&src, record) < 0) { + error("Unable to register HS AG service record"); + sdp_record_free(record); + goto failed; + } + adapter->hsp_ag_record_id = record->handle; + + features = headset_config_init(config); + + if (!enabled.hfp) + return 0; + + chan = DEFAULT_HF_AG_CHANNEL; + + io = bt_io_listen(BT_IO_RFCOMM, NULL, ag_confirm, adapter, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) + goto failed; + + adapter->hfp_ag_server = io; + + record = hfp_ag_record(chan, features); + if (!record) { + error("Unable to allocate new service record"); + goto failed; + } + + if (add_record_to_server(&src, record) < 0) { + error("Unable to register HF AG service record"); + sdp_record_free(record); + goto failed; + } + adapter->hfp_ag_record_id = record->handle; + + return 0; + +failed: + error("%s", err->message); + g_error_free(err); + if (adapter->hsp_ag_server) { + g_io_channel_shutdown(adapter->hsp_ag_server, TRUE, NULL); + g_io_channel_unref(adapter->hsp_ag_server); + adapter->hsp_ag_server = NULL; + } + + if (adapter->hfp_ag_server) { + g_io_channel_shutdown(adapter->hfp_ag_server, TRUE, NULL); + g_io_channel_unref(adapter->hfp_ag_server); + adapter->hfp_ag_server = NULL; + } + + return -1; +} + +static int gateway_server_init(struct audio_adapter *adapter) +{ + uint8_t chan = DEFAULT_HFP_HS_CHANNEL; + sdp_record_t *record; + gboolean master = TRUE; + GError *err = NULL; + GIOChannel *io; + bdaddr_t src; + + if (config) { + gboolean tmp; + + tmp = g_key_file_get_boolean(config, "General", "Master", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else + master = tmp; + } + + adapter_get_address(adapter->btd_adapter, &src); + + io = bt_io_listen(BT_IO_RFCOMM, NULL, hf_io_cb, adapter, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return -1; + } + + adapter->hfp_hs_server = io; + record = hfp_hs_record(chan); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(&src, record) < 0) { + error("Unable to register HFP HS service record"); + sdp_record_free(record); + g_io_channel_unref(adapter->hfp_hs_server); + adapter->hfp_hs_server = NULL; + return -1; + } + + adapter->hfp_hs_record_id = record->handle; + + return 0; +} + +static int audio_probe(struct btd_device *device, GSList *uuids) +{ + struct btd_adapter *adapter = device_get_adapter(device); + bdaddr_t src, dst; + struct audio_device *audio_dev; + + adapter_get_address(adapter, &src); + device_get_address(device, &dst); + + audio_dev = manager_get_device(&src, &dst, TRUE); + if (!audio_dev) { + debug("audio_probe: unable to get a device object"); + return -1; + } + + g_slist_foreach(uuids, (GFunc) handle_uuid, audio_dev); + + return 0; +} + +static void audio_remove(struct btd_device *device) +{ + struct audio_device *dev; + const char *path; + + path = device_get_path(device); + + dev = manager_find_device(path, NULL, NULL, NULL, FALSE); + if (!dev) + return; + + devices = g_slist_remove(devices, dev); + + audio_device_unregister(dev); +} + +static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp) +{ + adp->ref++; + + debug("audio_adapter_ref(%p): ref=%d", adp, adp->ref); + + return adp; +} + +static void audio_adapter_unref(struct audio_adapter *adp) +{ + adp->ref--; + + debug("audio_adapter_unref(%p): ref=%d", adp, adp->ref); + + if (adp->ref > 0) + return; + + adapters = g_slist_remove(adapters, adp); + btd_adapter_unref(adp->btd_adapter); + g_free(adp); +} + +static struct audio_adapter *audio_adapter_create(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + + adp = g_new0(struct audio_adapter, 1); + adp->btd_adapter = btd_adapter_ref(adapter); + + return audio_adapter_ref(adp); +} + +static struct audio_adapter *audio_adapter_get(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + + adp = find_adapter(adapters, adapter); + if (!adp) { + adp = audio_adapter_create(adapter); + if (!adp) + return NULL; + adapters = g_slist_append(adapters, adp); + } else + audio_adapter_ref(adp); + + return adp; +} + +static int headset_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + int ret; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + ret = headset_server_init(adp); + if (ret < 0) { + audio_adapter_unref(adp); + return ret; + } + + return 0; +} + +static void headset_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + if (adp->hsp_ag_record_id) { + remove_record_from_server(adp->hsp_ag_record_id); + adp->hsp_ag_record_id = 0; + } + + if (adp->hsp_ag_server) { + g_io_channel_shutdown(adp->hsp_ag_server, TRUE, NULL); + g_io_channel_unref(adp->hsp_ag_server); + adp->hsp_ag_server = NULL; + } + + if (adp->hfp_ag_record_id) { + remove_record_from_server(adp->hfp_ag_record_id); + adp->hfp_ag_record_id = 0; + } + + if (adp->hfp_ag_server) { + g_io_channel_shutdown(adp->hfp_ag_server, TRUE, NULL); + g_io_channel_unref(adp->hfp_ag_server); + adp->hfp_ag_server = NULL; + } + + audio_adapter_unref(adp); +} + +static int gateway_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + int ret; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + ret = gateway_server_init(adp); + if (ret < 0) { + audio_adapter_ref(adp); + return ret; + } + + return 0; +} + +static void gateway_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + if (adp->hfp_hs_record_id) { + remove_record_from_server(adp->hfp_hs_record_id); + adp->hfp_hs_record_id = 0; + } + + if (adp->hfp_hs_server) { + g_io_channel_unref(adp->hfp_hs_server); + adp->hfp_hs_server = NULL; + } + + audio_adapter_ref(adp); +} + +static int a2dp_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + int ret; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + adapter_get_address(adapter, &src); + + ret = a2dp_register(connection, &src, config); + if (ret < 0) { + audio_adapter_unref(adp); + return ret; + } + + return 0; +} + +static void a2dp_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + adapter_get_address(adapter, &src); + a2dp_unregister(&src); + audio_adapter_unref(adp); +} + +static int avrcp_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + adapter_get_address(adapter, &src); + + return avrcp_register(connection, &src, config); +} + +static void avrcp_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + adapter_get_address(adapter, &src); + avrcp_unregister(&src); + audio_adapter_unref(adp); +} + +static struct btd_device_driver audio_driver = { + .name = "audio", + .uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID, + ADVANCED_AUDIO_UUID, A2DP_SOURCE_UUID, A2DP_SINK_UUID, + AVRCP_TARGET_UUID, AVRCP_REMOTE_UUID), + .probe = audio_probe, + .remove = audio_remove, +}; + +static struct btd_adapter_driver headset_server_driver = { + .name = "audio-headset", + .probe = headset_server_probe, + .remove = headset_server_remove, +}; + +static struct btd_adapter_driver gateway_server_driver = { + .name = "audio-gateway", + .probe = gateway_server_probe, + .remove = gateway_server_remove, +}; + +static struct btd_adapter_driver a2dp_server_driver = { + .name = "audio-a2dp", + .probe = a2dp_server_probe, + .remove = a2dp_server_remove, +}; + +static struct btd_adapter_driver avrcp_server_driver = { + .name = "audio-control", + .probe = avrcp_server_probe, + .remove = avrcp_server_remove, +}; + +int audio_manager_init(DBusConnection *conn, GKeyFile *conf, + gboolean *enable_sco) +{ + char **list; + int i; + gboolean b; + GError *err = NULL; + + connection = dbus_connection_ref(conn); + + if (!conf) + goto proceed; + + config = conf; + + list = g_key_file_get_string_list(config, "General", "Enable", + NULL, NULL); + for (i = 0; list && list[i] != NULL; i++) { + if (g_str_equal(list[i], "Headset")) + enabled.headset = TRUE; + else if (g_str_equal(list[i], "Gateway")) + enabled.gateway = TRUE; + else if (g_str_equal(list[i], "Sink")) + enabled.sink = TRUE; + else if (g_str_equal(list[i], "Source")) + enabled.source = TRUE; + else if (g_str_equal(list[i], "Control")) + enabled.control = TRUE; + } + g_strfreev(list); + + list = g_key_file_get_string_list(config, "General", "Disable", + NULL, NULL); + for (i = 0; list && list[i] != NULL; i++) { + if (g_str_equal(list[i], "Headset")) + enabled.headset = FALSE; + else if (g_str_equal(list[i], "Gateway")) + enabled.gateway = FALSE; + else if (g_str_equal(list[i], "Sink")) + enabled.sink = FALSE; + else if (g_str_equal(list[i], "Source")) + enabled.source = FALSE; + else if (g_str_equal(list[i], "Control")) + enabled.control = FALSE; + } + g_strfreev(list); + + b = g_key_file_get_boolean(config, "General", "AutoConnect", &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else + auto_connect = b; + + b = g_key_file_get_boolean(config, "Headset", "HFP", + &err); + if (err) + g_clear_error(&err); + else + enabled.hfp = b; + + err = NULL; + i = g_key_file_get_integer(config, "Headset", "MaxConnected", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else + max_connected_headsets = i; + +proceed: + if (enabled.headset) { + telephony_init(); + btd_register_adapter_driver(&headset_server_driver); + } + + if (enabled.gateway) + btd_register_adapter_driver(&gateway_server_driver); + + if (enabled.source || enabled.sink) + btd_register_adapter_driver(&a2dp_server_driver); + + if (enabled.control) + btd_register_adapter_driver(&avrcp_server_driver); + + btd_register_device_driver(&audio_driver); + + *enable_sco = (enabled.gateway || enabled.headset); + + return 0; +} + +void audio_manager_exit(void) +{ + /* Bail out early if we haven't been initialized */ + if (connection == NULL) + return; + + dbus_connection_unref(connection); + connection = NULL; + + if (config) { + g_key_file_free(config); + config = NULL; + } + + if (enabled.headset) { + btd_unregister_adapter_driver(&headset_server_driver); + telephony_exit(); + } + + if (enabled.gateway) + btd_unregister_adapter_driver(&gateway_server_driver); + + if (enabled.source || enabled.sink) + btd_unregister_adapter_driver(&a2dp_server_driver); + + if (enabled.control) + btd_unregister_adapter_driver(&avrcp_server_driver); + + btd_unregister_device_driver(&audio_driver); +} + +struct audio_device *manager_find_device(const char *path, + const bdaddr_t *src, + const bdaddr_t *dst, + const char *interface, + gboolean connected) +{ + GSList *l; + + for (l = devices; l != NULL; l = l->next) { + struct audio_device *dev = l->data; + + if ((path && (strcmp(path, "")) && strcmp(dev->path, path))) + continue; + + if ((src && bacmp(src, BDADDR_ANY)) && bacmp(&dev->src, src)) + continue; + + if ((dst && bacmp(dst, BDADDR_ANY)) && bacmp(&dev->dst, dst)) + continue; + + if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface) + && !dev->headset) + continue; + + if (interface && !strcmp(AUDIO_GATEWAY_INTERFACE, interface) + && !dev->gateway) + continue; + + if (interface && !strcmp(AUDIO_SINK_INTERFACE, interface) + && !dev->sink) + continue; + + if (interface && !strcmp(AUDIO_SOURCE_INTERFACE, interface) + && !dev->source) + continue; + + if (interface && !strcmp(AUDIO_CONTROL_INTERFACE, interface) + && !dev->control) + continue; + + if (connected && !audio_device_is_active(dev, interface)) + continue; + + return dev; + } + + return NULL; +} + +struct audio_device *manager_get_device(const bdaddr_t *src, + const bdaddr_t *dst, + gboolean create) +{ + struct audio_device *dev; + struct btd_adapter *adapter; + struct btd_device *device; + char addr[18]; + const char *path; + + dev = manager_find_device(NULL, src, dst, NULL, FALSE); + if (dev) + return dev; + + if (!create) + return NULL; + + ba2str(src, addr); + + adapter = manager_find_adapter(src); + if (!adapter) { + error("Unable to get a btd_adapter object for %s", + addr); + return NULL; + } + + ba2str(dst, addr); + + device = adapter_get_device(connection, adapter, addr); + if (!device) { + error("Unable to get btd_device object for %s", addr); + return NULL; + } + + path = device_get_path(device); + + dev = audio_device_register(connection, device, path, src, dst); + if (!dev) + return NULL; + + devices = g_slist_append(devices, dev); + + return dev; +} + +gboolean manager_allow_headset_connection(struct audio_device *device) +{ + GSList *l; + int connected = 0; + + for (l = devices; l != NULL; l = l->next) { + struct audio_device *dev = l->data; + struct headset *hs = dev->headset; + + if (dev == device) + continue; + + if (bacmp(&dev->src, &device->src)) + continue; + + if (!hs) + continue; + + if (headset_get_state(dev) > HEADSET_STATE_DISCONNECTED) + connected++; + + if (connected >= max_connected_headsets) + return FALSE; + } + + return TRUE; +} diff --git a/audio/manager.h b/audio/manager.h new file mode 100644 index 000000000..cb9d63c17 --- /dev/null +++ b/audio/manager.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +struct enabled_interfaces { + gboolean hfp; + gboolean headset; + gboolean gateway; + gboolean sink; + gboolean source; + gboolean control; +}; + +int audio_manager_init(DBusConnection *conn, GKeyFile *config, + gboolean *enable_sco); +void audio_manager_exit(void); + +gboolean server_is_enabled(bdaddr_t *src, uint16_t svc); + +struct audio_device *manager_find_device(const char *path, + const bdaddr_t *src, + const bdaddr_t *dst, + const char *interface, + gboolean connected); + +struct audio_device *manager_get_device(const bdaddr_t *src, + const bdaddr_t *dst, + gboolean create); + +gboolean manager_allow_headset_connection(struct audio_device *device); diff --git a/audio/module-bluetooth-sink.c b/audio/module-bluetooth-sink.c new file mode 100644 index 000000000..2944d1881 --- /dev/null +++ b/audio/module-bluetooth-sink.c @@ -0,0 +1,39 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#if 0 +#include + +PA_MODULE_AUTHOR("Marcel Holtmann ") +PA_MODULE_DESCRIPTION("Bluetooth sink") +PA_MODULE_VERSION(VERSION) + +int pa__init(pa_core *core, pa_module *module) +{ + return 0; +} +#endif diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c new file mode 100644 index 000000000..13cf3ee20 --- /dev/null +++ b/audio/pcm_bluetooth.c @@ -0,0 +1,1778 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "ipc.h" +#include "sbc.h" +#include "rtp.h" + +//#define ENABLE_DEBUG + +#define UINT_SECS_MAX (UINT_MAX / 1000000 - 1) + +#define MIN_PERIOD_TIME 1 + +#define BUFFER_SIZE 2048 + +#ifdef ENABLE_DEBUG +#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg) +#else +#define DBG(fmt, arg...) +#endif + +#ifndef SOL_SCO +#define SOL_SCO 17 +#endif + +#ifndef SCO_TXBUFS +#define SCO_TXBUFS 0x03 +#endif + +#ifndef SCO_RXBUFS +#define SCO_RXBUFS 0x04 +#endif + +#ifndef MIN +# define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef MAX +# define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +/* adapted from glibc sys/time.h timersub() macro */ +#define priv_timespecsub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ + if ((result)->tv_nsec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_nsec += 1000000000; \ + } \ + } while (0) + +struct bluetooth_a2dp { + sbc_capabilities_t sbc_capabilities; + sbc_t sbc; /* Codec data */ + int sbc_initialized; /* Keep track if the encoder is initialized */ + unsigned int codesize; /* SBC codesize */ + int samples; /* Number of encoded samples */ + uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */ + unsigned int count; /* Codec transfer buffer counter */ + + int nsamples; /* Cumulative number of codec samples */ + uint16_t seq_num; /* Cumulative packet sequence */ + int frame_count; /* Current frames in buffer*/ +}; + +struct bluetooth_alsa_config { + char device[18]; /* Address of the remote Device */ + int has_device; + uint8_t transport; /* Requested transport */ + int has_transport; + uint16_t rate; + int has_rate; + uint8_t channel_mode; /* A2DP only */ + int has_channel_mode; + uint8_t allocation_method; /* A2DP only */ + int has_allocation_method; + uint8_t subbands; /* A2DP only */ + int has_subbands; + uint8_t block_length; /* A2DP only */ + int has_block_length; + uint8_t bitpool; /* A2DP only */ + int has_bitpool; + int autoconnect; +}; + +struct bluetooth_data { + snd_pcm_ioplug_t io; + struct bluetooth_alsa_config alsa_config; /* ALSA resource file parameters */ + volatile snd_pcm_sframes_t hw_ptr; + int transport; /* chosen transport SCO or AD2P */ + unsigned int link_mtu; /* MTU for selected transport channel */ + volatile struct pollfd stream; /* Audio stream filedescriptor */ + struct pollfd server; /* Audio daemon filedescriptor */ + uint8_t buffer[BUFFER_SIZE]; /* Encoded transfer buffer */ + unsigned int count; /* Transfer buffer counter */ + struct bluetooth_a2dp a2dp; /* A2DP data */ + + pthread_t hw_thread; /* Makes virtual hw pointer move */ + int pipefd[2]; /* Inter thread communication */ + int stopped; + sig_atomic_t reset; /* Request XRUN handling */ +}; + +static int audioservice_send(int sk, const bt_audio_msg_header_t *msg); +static int audioservice_expect(int sk, bt_audio_msg_header_t *outmsg, + int expected_type); + +static int bluetooth_start(snd_pcm_ioplug_t *io) +{ + DBG("bluetooth_start %p", io); + + return 0; +} + +static int bluetooth_stop(snd_pcm_ioplug_t *io) +{ + DBG("bluetooth_stop %p", io); + + return 0; +} + +static void *playback_hw_thread(void *param) +{ + struct bluetooth_data *data = param; + unsigned int prev_periods; + double period_time; + struct timespec start; + struct pollfd fds[2]; + int poll_timeout; + + data->server.events = POLLIN; + /* note: only errors for data->stream.events */ + + fds[0] = data->server; + fds[1] = data->stream; + + prev_periods = 0; + period_time = 1000000.0 * data->io.period_size / data->io.rate; + if (period_time > (int) (MIN_PERIOD_TIME * 1000)) + poll_timeout = (int) (period_time / 1000.0f); + else + poll_timeout = MIN_PERIOD_TIME; + + clock_gettime(CLOCK_MONOTONIC, &start); + + while (1) { + unsigned int dtime, periods; + struct timespec cur, delta; + int ret; + + if (data->stopped) + goto iter_sleep; + + if (data->reset) { + DBG("Handle XRUN in hw-thread."); + data->reset = 0; + clock_gettime(CLOCK_MONOTONIC, &start); + prev_periods = 0; + } + + clock_gettime(CLOCK_MONOTONIC, &cur); + + priv_timespecsub(&cur, &start, &delta); + + dtime = delta.tv_sec * 1000000 + delta.tv_nsec / 1000; + periods = 1.0 * dtime / period_time; + + if (periods > prev_periods) { + char c = 'w'; + int frags = periods - prev_periods, n; + + data->hw_ptr += frags * data->io.period_size; + data->hw_ptr %= data->io.buffer_size; + + for (n = 0; n < frags; n++) { + /* Notify user that hardware pointer + * has moved * */ + if (write(data->pipefd[1], &c, 1) < 0) + pthread_testcancel(); + } + + /* Reset point of reference to avoid too big values + * that wont fit an unsigned int */ + if ((unsigned int) delta.tv_sec < UINT_SECS_MAX) + prev_periods = periods; + else { + prev_periods = 0; + clock_gettime(CLOCK_MONOTONIC, &start); + } + } + +iter_sleep: + /* sleep up to one period interval */ + ret = poll(fds, 2, poll_timeout); + + if (ret < 0) { + SNDERR("poll error: %s (%d)", strerror(errno), errno); + if (errno != EINTR) + break; + } else if (ret > 0) { + ret = (fds[0].revents) ? 0 : 1; + SNDERR("poll fd %d revents %d", ret, fds[ret].revents); + if (fds[ret].revents & (POLLERR | POLLHUP | POLLNVAL)) + break; + } + + /* Offer opportunity to be canceled by main thread */ + pthread_testcancel(); + } + + data->hw_thread = 0; + pthread_exit(NULL); +} + +static int bluetooth_playback_start(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + int err; + + DBG("%p", io); + + data->stopped = 0; + + if (data->hw_thread) + return 0; + + err = pthread_create(&data->hw_thread, 0, playback_hw_thread, data); + + return -err; +} + +static int bluetooth_playback_stop(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + + DBG("%p", io); + + data->stopped = 1; + + return 0; +} + +static snd_pcm_sframes_t bluetooth_pointer(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + + return data->hw_ptr; +} + +static void bluetooth_exit(struct bluetooth_data *data) +{ + struct bluetooth_a2dp *a2dp = &data->a2dp; + + if (data->server.fd >= 0) + bt_audio_service_close(data->server.fd); + + if (data->stream.fd >= 0) + close(data->stream.fd); + + if (data->hw_thread) { + pthread_cancel(data->hw_thread); + pthread_join(data->hw_thread, 0); + } + + if (a2dp->sbc_initialized) + sbc_finish(&a2dp->sbc); + + if (data->pipefd[0] > 0) + close(data->pipefd[0]); + + if (data->pipefd[1] > 0) + close(data->pipefd[1]); + + free(data); +} + +static int bluetooth_close(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + + DBG("%p", io); + + bluetooth_exit(data); + + return 0; +} + +static int bluetooth_prepare(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + char c = 'w'; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_req *req = (void *) buf; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + uint32_t period_count = io->buffer_size / io->period_size; + int opt_name, err; + struct timeval t = { 0, period_count }; + + DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", + io->period_size, io->buffer_size); + + data->reset = 0; + + /* As we're gonna receive messages on the server socket, we have to stop the + hw thread that is polling on it, if any */ + if (data->hw_thread) { + pthread_cancel(data->hw_thread); + pthread_join(data->hw_thread, 0); + data->hw_thread = 0; + } + + if (io->stream == SND_PCM_STREAM_PLAYBACK) + /* If not null for playback, xmms doesn't display time + * correctly */ + data->hw_ptr = 0; + else + /* ALSA library is really picky on the fact hw_ptr is not null. + * If it is, capture won't start */ + data->hw_ptr = io->period_size; + + /* send start */ + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_START_STREAM; + req->h.length = sizeof(*req); + + err = audioservice_send(data->server.fd, &req->h); + if (err < 0) + return err; + + rsp->h.length = sizeof(*rsp); + err = audioservice_expect(data->server.fd, &rsp->h, + BT_START_STREAM); + if (err < 0) + return err; + + ind->h.length = sizeof(*ind); + err = audioservice_expect(data->server.fd, &ind->h, + BT_NEW_STREAM); + if (err < 0) + return err; + + if (data->stream.fd >= 0) + close(data->stream.fd); + + data->stream.fd = bt_audio_service_get_data_fd(data->server.fd); + if (data->stream.fd < 0) { + return -errno; + } + + if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? + SO_SNDTIMEO : SO_RCVTIMEO; + + if (setsockopt(data->stream.fd, SOL_SOCKET, opt_name, &t, + sizeof(t)) < 0) + return -errno; + } else { + opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? + SCO_TXBUFS : SCO_RXBUFS; + + if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count, + sizeof(period_count)) == 0) + return 0; + + opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? + SO_SNDBUF : SO_RCVBUF; + + if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count, + sizeof(period_count)) == 0) + return 0; + + /* FIXME : handle error codes */ + } + + /* wake up any client polling at us */ + err = write(data->pipefd[1], &c, 1); + if (err < 0) + return err; + + return 0; +} + +static int bluetooth_hsp_hw_params(snd_pcm_ioplug_t *io, + snd_pcm_hw_params_t *params) +{ + struct bluetooth_data *data = io->private_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_req *open_req = (void *) buf; + struct bt_open_rsp *open_rsp = (void *) buf; + struct bt_set_configuration_req *req = (void *) buf; + struct bt_set_configuration_rsp *rsp = (void *) buf; + int err; + + DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", + io->period_size, io->buffer_size); + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + open_req->h.type = BT_REQUEST; + open_req->h.name = BT_OPEN; + open_req->h.length = sizeof(*open_req); + + strncpy(open_req->destination, data->alsa_config.device, 18); + open_req->seid = BT_A2DP_SEID_RANGE + 1; + open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ? + BT_WRITE_LOCK : BT_READ_LOCK); + + err = audioservice_send(data->server.fd, &open_req->h); + if (err < 0) + return err; + + open_rsp->h.length = sizeof(*open_rsp); + err = audioservice_expect(data->server.fd, &open_rsp->h, + BT_OPEN); + if (err < 0) + return err; + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_SET_CONFIGURATION; + req->h.length = sizeof(*req); + + req->codec.transport = BT_CAPABILITIES_TRANSPORT_SCO; + req->codec.seid = BT_A2DP_SEID_RANGE + 1; + req->codec.length = sizeof(pcm_capabilities_t); + + req->h.length += req->codec.length - sizeof(req->codec); + err = audioservice_send(data->server.fd, &req->h); + if (err < 0) + return err; + + rsp->h.length = sizeof(*rsp); + err = audioservice_expect(data->server.fd, &rsp->h, + BT_SET_CONFIGURATION); + if (err < 0) + return err; + + data->transport = BT_CAPABILITIES_TRANSPORT_SCO; + data->link_mtu = rsp->link_mtu; + + return 0; +} + +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case BT_SBC_SAMPLING_FREQ_16000: + case BT_SBC_SAMPLING_FREQ_32000: + return 53; + case BT_SBC_SAMPLING_FREQ_44100: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + DBG("Invalid channel mode %u", mode); + return 53; + } + case BT_SBC_SAMPLING_FREQ_48000: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + DBG("Invalid channel mode %u", mode); + return 51; + } + default: + DBG("Invalid sampling freq %u", freq); + return 53; + } +} + +static int bluetooth_a2dp_init(struct bluetooth_data *data, + snd_pcm_hw_params_t *params) +{ + struct bluetooth_alsa_config *cfg = &data->alsa_config; + sbc_capabilities_t *cap = &data->a2dp.sbc_capabilities; + unsigned int max_bitpool, min_bitpool, rate, channels; + int dir; + + snd_pcm_hw_params_get_rate(params, &rate, &dir); + snd_pcm_hw_params_get_channels(params, &channels); + + switch (rate) { + case 48000: + cap->frequency = BT_SBC_SAMPLING_FREQ_48000; + break; + case 44100: + cap->frequency = BT_SBC_SAMPLING_FREQ_44100; + break; + case 32000: + cap->frequency = BT_SBC_SAMPLING_FREQ_32000; + break; + case 16000: + cap->frequency = BT_SBC_SAMPLING_FREQ_16000; + break; + default: + DBG("Rate %d not supported", rate); + return -1; + } + + if (cfg->has_channel_mode) + cap->channel_mode = cfg->channel_mode; + else if (channels == 2) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + } else { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } + + if (!cap->channel_mode) { + DBG("No supported channel modes"); + return -1; + } + + if (cfg->has_block_length) + cap->block_length = cfg->block_length; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) + cap->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) + cap->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) + cap->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) + cap->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + DBG("No supported block lengths"); + return -1; + } + + if (cfg->has_subbands) + cap->subbands = cfg->subbands; + if (cap->subbands & BT_A2DP_SUBBANDS_8) + cap->subbands = BT_A2DP_SUBBANDS_8; + else if (cap->subbands & BT_A2DP_SUBBANDS_4) + cap->subbands = BT_A2DP_SUBBANDS_4; + else { + DBG("No supported subbands"); + return -1; + } + + if (cfg->has_allocation_method) + cap->allocation_method = cfg->allocation_method; + if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) + cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) + cap->allocation_method = BT_A2DP_ALLOCATION_SNR; + + if (cfg->has_bitpool) + min_bitpool = max_bitpool = cfg->bitpool; + else { + min_bitpool = MAX(MIN_BITPOOL, cap->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, + cap->channel_mode), + cap->max_bitpool); + } + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return 0; +} + +static void bluetooth_a2dp_setup(struct bluetooth_a2dp *a2dp) +{ + sbc_capabilities_t active_capabilities = a2dp->sbc_capabilities; + + if (a2dp->sbc_initialized) + sbc_reinit(&a2dp->sbc, 0); + else + sbc_init(&a2dp->sbc, 0); + a2dp->sbc_initialized = 1; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000) + a2dp->sbc.frequency = SBC_FREQ_16000; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000) + a2dp->sbc.frequency = SBC_FREQ_32000; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100) + a2dp->sbc.frequency = SBC_FREQ_44100; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000) + a2dp->sbc.frequency = SBC_FREQ_48000; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + a2dp->sbc.mode = SBC_MODE_MONO; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + a2dp->sbc.mode = SBC_MODE_STEREO; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; + + a2dp->sbc.allocation = active_capabilities.allocation_method + == BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR + : SBC_AM_LOUDNESS; + + switch (active_capabilities.subbands) { + case BT_A2DP_SUBBANDS_4: + a2dp->sbc.subbands = SBC_SB_4; + break; + case BT_A2DP_SUBBANDS_8: + a2dp->sbc.subbands = SBC_SB_8; + break; + } + + switch (active_capabilities.block_length) { + case BT_A2DP_BLOCK_LENGTH_4: + a2dp->sbc.blocks = SBC_BLK_4; + break; + case BT_A2DP_BLOCK_LENGTH_8: + a2dp->sbc.blocks = SBC_BLK_8; + break; + case BT_A2DP_BLOCK_LENGTH_12: + a2dp->sbc.blocks = SBC_BLK_12; + break; + case BT_A2DP_BLOCK_LENGTH_16: + a2dp->sbc.blocks = SBC_BLK_16; + break; + } + + a2dp->sbc.bitpool = active_capabilities.max_bitpool; + a2dp->codesize = sbc_get_codesize(&a2dp->sbc); + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); +} + +static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io, + snd_pcm_hw_params_t *params) +{ + struct bluetooth_data *data = io->private_data; + struct bluetooth_a2dp *a2dp = &data->a2dp; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_req *open_req = (void *) buf; + struct bt_open_rsp *open_rsp = (void *) buf; + struct bt_set_configuration_req *req = (void *) buf; + struct bt_set_configuration_rsp *rsp = (void *) buf; + int err; + + DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", + io->period_size, io->buffer_size); + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + open_req->h.type = BT_REQUEST; + open_req->h.name = BT_OPEN; + open_req->h.length = sizeof(*open_req); + + strncpy(open_req->destination, data->alsa_config.device, 18); + open_req->seid = a2dp->sbc_capabilities.capability.seid; + open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ? + BT_WRITE_LOCK : BT_READ_LOCK); + + err = audioservice_send(data->server.fd, &open_req->h); + if (err < 0) + return err; + + open_rsp->h.length = sizeof(*open_rsp); + err = audioservice_expect(data->server.fd, &open_rsp->h, + BT_OPEN); + if (err < 0) + return err; + + err = bluetooth_a2dp_init(data, params); + if (err < 0) + return err; + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_SET_CONFIGURATION; + req->h.length = sizeof(*req); + + memcpy(&req->codec, &a2dp->sbc_capabilities, + sizeof(a2dp->sbc_capabilities)); + + req->codec.transport = BT_CAPABILITIES_TRANSPORT_A2DP; + req->codec.length = sizeof(a2dp->sbc_capabilities); + req->h.length += req->codec.length - sizeof(req->codec); + + err = audioservice_send(data->server.fd, &req->h); + if (err < 0) + return err; + + rsp->h.length = sizeof(*rsp); + err = audioservice_expect(data->server.fd, &rsp->h, + BT_SET_CONFIGURATION); + if (err < 0) + return err; + + data->transport = BT_CAPABILITIES_TRANSPORT_A2DP; + data->link_mtu = rsp->link_mtu; + + /* Setup SBC encoder now we agree on parameters */ + bluetooth_a2dp_setup(a2dp); + + DBG("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", + a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, + a2dp->sbc.bitpool); + + return 0; +} + +static int bluetooth_poll_descriptors(snd_pcm_ioplug_t *io, + struct pollfd *pfd, unsigned int space) +{ + struct bluetooth_data *data = io->private_data; + + assert(io); + + if (space < 1) + return 0; + + pfd[0].fd = data->stream.fd; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + + return 1; +} + +static int bluetooth_poll_revents(snd_pcm_ioplug_t *io ATTRIBUTE_UNUSED, + struct pollfd *pfds, unsigned int nfds, + unsigned short *revents) +{ + assert(pfds && nfds == 1 && revents); + + *revents = pfds[0].revents; + + return 0; +} + +static int bluetooth_playback_poll_descriptors_count(snd_pcm_ioplug_t *io) +{ + return 2; +} + +static int bluetooth_playback_poll_descriptors(snd_pcm_ioplug_t *io, + struct pollfd *pfd, unsigned int space) +{ + struct bluetooth_data *data = io->private_data; + + DBG(""); + + assert(data->pipefd[0] >= 0); + + if (space < 2) + return 0; + + pfd[0].fd = data->pipefd[0]; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + pfd[1].fd = data->stream.fd; + pfd[1].events = POLLERR | POLLHUP | POLLNVAL; + pfd[1].revents = 0; + + return 2; +} + +static int bluetooth_playback_poll_revents(snd_pcm_ioplug_t *io, + struct pollfd *pfds, unsigned int nfds, + unsigned short *revents) +{ + static char buf[1]; + int ret; + + DBG(""); + + assert(pfds); + assert(nfds == 2); + assert(revents); + assert(pfds[0].fd >= 0); + assert(pfds[1].fd >= 0); + + if (io->state != SND_PCM_STATE_PREPARED) + ret = read(pfds[0].fd, buf, 1); + + if (pfds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) + io->state = SND_PCM_STATE_DISCONNECTED; + + *revents = (pfds[0].revents & POLLIN) ? POLLOUT : 0; + + return 0; +} + + +static snd_pcm_sframes_t bluetooth_hsp_read(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + struct bluetooth_data *data = io->private_data; + snd_pcm_uframes_t frames_to_write, ret; + unsigned char *buff; + unsigned int frame_size = 0; + int nrecv; + + DBG("areas->step=%u areas->first=%u offset=%lu size=%lu io->nonblock=%u", + areas->step, areas->first, offset, size, io->nonblock); + + frame_size = areas->step / 8; + + if (data->count > 0) + goto proceed; + + nrecv = recv(data->stream.fd, data->buffer, data->link_mtu, + io->nonblock ? MSG_DONTWAIT : 0); + + if (nrecv < 0) { + ret = (errno == EPIPE) ? -EIO : -errno; + goto done; + } + + if ((unsigned int) nrecv != data->link_mtu) { + ret = -EIO; + SNDERR(strerror(-ret)); + goto done; + } + + /* Increment hardware transmition pointer */ + data->hw_ptr = (data->hw_ptr + data->link_mtu / frame_size) % + io->buffer_size; + +proceed: + buff = (unsigned char *) areas->addr + + (areas->first + areas->step * offset) / 8; + + if ((data->count + size * frame_size) <= data->link_mtu) + frames_to_write = size; + else + frames_to_write = (data->link_mtu - data->count) / frame_size; + + memcpy(buff, data->buffer + data->count, frame_size * frames_to_write); + data->count += (frame_size * frames_to_write); + data->count %= data->link_mtu; + + /* Return written frames count */ + ret = frames_to_write; + +done: + DBG("returning %lu", ret); + return ret; +} + +static snd_pcm_sframes_t bluetooth_hsp_write(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + struct bluetooth_data *data = io->private_data; + snd_pcm_sframes_t ret = 0; + snd_pcm_uframes_t frames_to_read; + uint8_t *buff; + int rsend, frame_size; + + DBG("areas->step=%u areas->first=%u offset=%lu, size=%lu io->nonblock=%u", + areas->step, areas->first, offset, size, io->nonblock); + + if (io->hw_ptr > io->appl_ptr) { + ret = bluetooth_playback_stop(io); + if (ret == 0) + ret = -EPIPE; + goto done; + } + + frame_size = areas->step / 8; + if ((data->count + size * frame_size) <= data->link_mtu) + frames_to_read = size; + else + frames_to_read = (data->link_mtu - data->count) / frame_size; + + DBG("count=%d frames_to_read=%lu", data->count, frames_to_read); + + /* Ready for more data */ + buff = (uint8_t *) areas->addr + + (areas->first + areas->step * offset) / 8; + memcpy(data->buffer + data->count, buff, frame_size * frames_to_read); + + /* Remember we have some frames in the pipe now */ + data->count += frames_to_read * frame_size; + if (data->count != data->link_mtu) { + ret = frames_to_read; + goto done; + } + + rsend = send(data->stream.fd, data->buffer, data->link_mtu, + io->nonblock ? MSG_DONTWAIT : 0); + if (rsend > 0) { + /* Reset count pointer */ + data->count = 0; + + ret = frames_to_read; + } else if (rsend < 0) + ret = (errno == EPIPE) ? -EIO : -errno; + else + ret = -EIO; + +done: + DBG("returning %ld", ret); + return ret; +} + +static snd_pcm_sframes_t bluetooth_a2dp_read(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, snd_pcm_uframes_t size) +{ + snd_pcm_uframes_t ret = 0; + return ret; +} + +static int avdtp_write(struct bluetooth_data *data) +{ + int ret = 0; + struct rtp_header *header; + struct rtp_payload *payload; + struct bluetooth_a2dp *a2dp = &data->a2dp; + + header = (void *) a2dp->buffer; + payload = (void *) (a2dp->buffer + sizeof(*header)); + + memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload)); + + payload->frame_count = a2dp->frame_count; + header->v = 2; + header->pt = 1; + header->sequence_number = htons(a2dp->seq_num); + header->timestamp = htonl(a2dp->nsamples); + header->ssrc = htonl(1); + + ret = send(data->stream.fd, a2dp->buffer, a2dp->count, MSG_DONTWAIT); + if (ret < 0) { + DBG("send returned %d errno %s.", ret, strerror(errno)); + ret = -errno; + } + + /* Reset buffer of data to send */ + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + a2dp->frame_count = 0; + a2dp->samples = 0; + a2dp->seq_num++; + + return ret; +} + +static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, snd_pcm_uframes_t size) +{ + struct bluetooth_data *data = io->private_data; + struct bluetooth_a2dp *a2dp = &data->a2dp; + snd_pcm_sframes_t ret = 0; + unsigned int bytes_left; + int frame_size, encoded; + size_t written; + uint8_t *buff; + + DBG("areas->step=%u areas->first=%u offset=%lu size=%lu", + areas->step, areas->first, offset, size); + DBG("hw_ptr=%lu appl_ptr=%lu diff=%lu", io->hw_ptr, io->appl_ptr, + io->appl_ptr - io->hw_ptr); + + /* Calutate starting pointers */ + frame_size = areas->step / 8; + bytes_left = size * frame_size; + buff = (uint8_t *) areas->addr + + (areas->first + areas->step * (offset)) / 8; + + /* Check for underrun */ + if (io->hw_ptr > io->appl_ptr) { + ret = bluetooth_playback_stop(io); + if (ret == 0) + ret = -EPIPE; + data->reset = 1; + return ret; + } + + /* Check if we should autostart */ + if (io->state == SND_PCM_STATE_PREPARED) { + snd_pcm_sw_params_t *swparams; + snd_pcm_uframes_t threshold; + + snd_pcm_sw_params_malloc(&swparams); + if (!snd_pcm_sw_params_current(io->pcm, swparams) && + !snd_pcm_sw_params_get_start_threshold(swparams, + &threshold)) { + if (io->appl_ptr >= threshold) { + ret = snd_pcm_start(io->pcm); + if (ret != 0) + return ret; + } + } + + snd_pcm_sw_params_free(swparams); + } + + /* Check if we have any left over data from the last write */ + if (data->count > 0 && (bytes_left - data->count) >= a2dp->codesize) { + int additional_bytes_needed = a2dp->codesize - data->count; + + memcpy(data->buffer + data->count, buff, + additional_bytes_needed); + + /* Enough data to encode (sbc wants 1k blocks) */ + encoded = sbc_encode(&a2dp->sbc, data->buffer, a2dp->codesize, + a2dp->buffer + a2dp->count, + sizeof(a2dp->buffer) - a2dp->count, + &written); + if (encoded <= 0) { + DBG("Encoding error %d", encoded); + goto done; + } + + /* Increment a2dp buffers */ + a2dp->count += written; + a2dp->frame_count++; + a2dp->samples += encoded / frame_size; + a2dp->nsamples += encoded / frame_size; + + /* No space left for another frame then send */ + if (a2dp->count + written >= data->link_mtu) { + avdtp_write(data); + DBG("sending packet %d, count %d, link_mtu %u", + a2dp->seq_num, a2dp->count, + data->link_mtu); + } + + /* Increment up buff pointer to take into account + * the data processed */ + buff += additional_bytes_needed; + bytes_left -= additional_bytes_needed; + + /* Since data has been process mark it as zero */ + data->count = 0; + } + + + /* Process this buffer in full chunks */ + while (bytes_left >= a2dp->codesize) { + /* Enough data to encode (sbc wants 1k blocks) */ + encoded = sbc_encode(&a2dp->sbc, buff, a2dp->codesize, + a2dp->buffer + a2dp->count, + sizeof(a2dp->buffer) - a2dp->count, + &written); + if (encoded <= 0) { + DBG("Encoding error %d", encoded); + goto done; + } + + /* Increment up buff pointer to take into account + * the data processed */ + buff += a2dp->codesize; + bytes_left -= a2dp->codesize; + + /* Increment a2dp buffers */ + a2dp->count += written; + a2dp->frame_count++; + a2dp->samples += encoded / frame_size; + a2dp->nsamples += encoded / frame_size; + + /* No space left for another frame then send */ + if (a2dp->count + written >= data->link_mtu) { + avdtp_write(data); + DBG("sending packet %d, count %d, link_mtu %u", + a2dp->seq_num, a2dp->count, + data->link_mtu); + } + } + + /* Copy the extra to our temp buffer for the next write */ + if (bytes_left > 0) { + memcpy(data->buffer + data->count, buff, bytes_left); + data->count += bytes_left; + bytes_left = 0; + } + +done: + DBG("returning %ld", size - bytes_left / frame_size); + + return size - bytes_left / frame_size; +} + +static int bluetooth_playback_delay(snd_pcm_ioplug_t *io, + snd_pcm_sframes_t *delayp) +{ + DBG(""); + + /* This updates io->hw_ptr value using pointer() function */ + snd_pcm_hwsync(io->pcm); + + *delayp = io->appl_ptr - io->hw_ptr; + if ((io->state == SND_PCM_STATE_RUNNING) && (*delayp < 0)) { + io->callback->stop(io); + io->state = SND_PCM_STATE_XRUN; + *delayp = 0; + } + + /* This should never fail, ALSA API is really not + prepared to handle a non zero return value */ + return 0; +} + +static snd_pcm_ioplug_callback_t bluetooth_hsp_playback = { + .start = bluetooth_playback_start, + .stop = bluetooth_playback_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_hsp_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_hsp_write, + .poll_descriptors_count = bluetooth_playback_poll_descriptors_count, + .poll_descriptors = bluetooth_playback_poll_descriptors, + .poll_revents = bluetooth_playback_poll_revents, + .delay = bluetooth_playback_delay, +}; + +static snd_pcm_ioplug_callback_t bluetooth_hsp_capture = { + .start = bluetooth_start, + .stop = bluetooth_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_hsp_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_hsp_read, + .poll_descriptors = bluetooth_poll_descriptors, + .poll_revents = bluetooth_poll_revents, +}; + +static snd_pcm_ioplug_callback_t bluetooth_a2dp_playback = { + .start = bluetooth_playback_start, + .stop = bluetooth_playback_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_a2dp_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_a2dp_write, + .poll_descriptors_count = bluetooth_playback_poll_descriptors_count, + .poll_descriptors = bluetooth_playback_poll_descriptors, + .poll_revents = bluetooth_playback_poll_revents, + .delay = bluetooth_playback_delay, +}; + +static snd_pcm_ioplug_callback_t bluetooth_a2dp_capture = { + .start = bluetooth_start, + .stop = bluetooth_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_a2dp_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_a2dp_read, + .poll_descriptors = bluetooth_poll_descriptors, + .poll_revents = bluetooth_poll_revents, +}; + +#define ARRAY_NELEMS(a) (sizeof((a)) / sizeof((a)[0])) + +static int bluetooth_hsp_hw_constraint(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + snd_pcm_access_t access_list[] = { + SND_PCM_ACCESS_RW_INTERLEAVED, + /* Mmap access is really useless fo this driver, but we + * support it because some pieces of software out there + * insist on using it */ + SND_PCM_ACCESS_MMAP_INTERLEAVED + }; + unsigned int format_list[] = { + SND_PCM_FORMAT_S16 + }; + int err; + + /* access type */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, + ARRAY_NELEMS(access_list), access_list); + if (err < 0) + return err; + + /* supported formats */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, + ARRAY_NELEMS(format_list), format_list); + if (err < 0) + return err; + + /* supported channels */ + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, + 1, 1); + if (err < 0) + return err; + + /* supported rate */ + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, + 8000, 8000); + if (err < 0) + return err; + + /* supported block size */ + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, + data->link_mtu, data->link_mtu); + if (err < 0) + return err; + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, + 2, 200); + if (err < 0) + return err; + + return 0; +} + +static int bluetooth_a2dp_hw_constraint(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + struct bluetooth_a2dp *a2dp = &data->a2dp; + struct bluetooth_alsa_config *cfg = &data->alsa_config; + snd_pcm_access_t access_list[] = { + SND_PCM_ACCESS_RW_INTERLEAVED, + /* Mmap access is really useless fo this driver, but we + * support it because some pieces of software out there + * insist on using it */ + SND_PCM_ACCESS_MMAP_INTERLEAVED + }; + unsigned int format_list[] = { + SND_PCM_FORMAT_S16 + }; + unsigned int rate_list[4]; + unsigned int rate_count; + int err, min_channels, max_channels; + unsigned int period_list[] = { + 2048, + 4096, /* e.g. 23.2msec/period (stereo 16bit at 44.1kHz) */ + 8192 + }; + + /* access type */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, + ARRAY_NELEMS(access_list), access_list); + if (err < 0) + return err; + + /* supported formats */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, + ARRAY_NELEMS(format_list), format_list); + if (err < 0) + return err; + + /* supported channels */ + if (cfg->has_channel_mode) + a2dp->sbc_capabilities.channel_mode = cfg->channel_mode; + + if (a2dp->sbc_capabilities.channel_mode & + BT_A2DP_CHANNEL_MODE_MONO) + min_channels = 1; + else + min_channels = 2; + + if (a2dp->sbc_capabilities.channel_mode & + (~BT_A2DP_CHANNEL_MODE_MONO)) + max_channels = 2; + else + max_channels = 1; + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, + min_channels, max_channels); + if (err < 0) + return err; + + /* supported buffer sizes + * (can be used as 3*8192, 6*4096, 12*2048, ...) */ + err = snd_pcm_ioplug_set_param_minmax(io, + SND_PCM_IOPLUG_HW_BUFFER_BYTES, + 8192*3, 8192*3); + if (err < 0) + return err; + + /* supported block sizes: */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, + ARRAY_NELEMS(period_list), period_list); + if (err < 0) + return err; + + /* supported rates */ + rate_count = 0; + if (cfg->has_rate) { + rate_list[rate_count] = cfg->rate; + rate_count++; + } else { + if (a2dp->sbc_capabilities.frequency & + BT_SBC_SAMPLING_FREQ_16000) { + rate_list[rate_count] = 16000; + rate_count++; + } + + if (a2dp->sbc_capabilities.frequency & + BT_SBC_SAMPLING_FREQ_32000) { + rate_list[rate_count] = 32000; + rate_count++; + } + + if (a2dp->sbc_capabilities.frequency & + BT_SBC_SAMPLING_FREQ_44100) { + rate_list[rate_count] = 44100; + rate_count++; + } + + if (a2dp->sbc_capabilities.frequency & + BT_SBC_SAMPLING_FREQ_48000) { + rate_list[rate_count] = 48000; + rate_count++; + } + } + + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE, + rate_count, rate_list); + if (err < 0) + return err; + + return 0; +} + +static int bluetooth_parse_config(snd_config_t *conf, + struct bluetooth_alsa_config *bt_config) +{ + snd_config_iterator_t i, next; + + memset(bt_config, 0, sizeof(struct bluetooth_alsa_config)); + + /* Set defaults */ + bt_config->autoconnect = 1; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id, *value; + + if (snd_config_get_id(n, &id) < 0) + continue; + + if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0) + continue; + + if (strcmp(id, "autoconnect") == 0) { + int b; + + b = snd_config_get_bool(n); + if (b < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->autoconnect = b; + continue; + } + + if (strcmp(id, "device") == 0 || strcmp(id, "bdaddr") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->has_device = 1; + strncpy(bt_config->device, value, 18); + continue; + } + + if (strcmp(id, "profile") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + if (strcmp(value, "auto") == 0) { + bt_config->transport = BT_CAPABILITIES_TRANSPORT_ANY; + bt_config->has_transport = 1; + } else if (strcmp(value, "voice") == 0 || + strcmp(value, "hfp") == 0) { + bt_config->transport = BT_CAPABILITIES_TRANSPORT_SCO; + bt_config->has_transport = 1; + } else if (strcmp(value, "hifi") == 0 || + strcmp(value, "a2dp") == 0) { + bt_config->transport = BT_CAPABILITIES_TRANSPORT_A2DP; + bt_config->has_transport = 1; + } + continue; + } + + if (strcmp(id, "rate") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->rate = atoi(value); + bt_config->has_rate = 1; + continue; + } + + if (strcmp(id, "mode") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + if (strcmp(value, "mono") == 0) { + bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + bt_config->has_channel_mode = 1; + } else if (strcmp(value, "dual") == 0) { + bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + bt_config->has_channel_mode = 1; + } else if (strcmp(value, "stereo") == 0) { + bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + bt_config->has_channel_mode = 1; + } else if (strcmp(value, "joint") == 0) { + bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + bt_config->has_channel_mode = 1; + } + continue; + } + + if (strcmp(id, "allocation") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + if (strcmp(value, "loudness") == 0) { + bt_config->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + bt_config->has_allocation_method = 1; + } else if (strcmp(value, "snr") == 0) { + bt_config->allocation_method = BT_A2DP_ALLOCATION_SNR; + bt_config->has_allocation_method = 1; + } + continue; + } + + if (strcmp(id, "subbands") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->subbands = atoi(value); + bt_config->has_subbands = 1; + continue; + } + + if (strcmp(id, "blocks") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->block_length = atoi(value); + bt_config->has_block_length = 1; + continue; + } + + if (strcmp(id, "bitpool") == 0) { + if (snd_config_get_string(n, &value) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + bt_config->bitpool = atoi(value); + bt_config->has_bitpool = 1; + continue; + } + + SNDERR("Unknown field %s", id); + return -EINVAL; + } + + return 0; +} + +static int audioservice_send(int sk, const bt_audio_msg_header_t *msg) +{ + int err; + uint16_t length; + + length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE; + + DBG("sending %s:%s", bt_audio_strtype(msg->type), + bt_audio_strname(msg->name)); + if (send(sk, msg, length, 0) > 0) + err = 0; + else { + err = -errno; + SNDERR("Error sending data to audio service: %s(%d)", + strerror(errno), errno); + } + + return err; +} + +static int audioservice_recv(int sk, bt_audio_msg_header_t *inmsg) +{ + int err; + ssize_t ret; + const char *type, *name; + uint16_t length; + + length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE; + + DBG("trying to receive msg from audio service..."); + + ret = recv(sk, inmsg, length, 0); + if (ret < 0) { + err = -errno; + SNDERR("Error receiving IPC data from bluetoothd: %s (%d)", + strerror(errno), errno); + } else if ((size_t) ret < sizeof(bt_audio_msg_header_t)) { + SNDERR("Too short (%d bytes) IPC packet from bluetoothd", ret); + err = -EINVAL; + } else { + type = bt_audio_strtype(inmsg->type); + name = bt_audio_strname(inmsg->name); + if (type && name) { + DBG("Received %s - %s", type, name); + err = 0; + } else { + err = -EINVAL; + SNDERR("Bogus message type %d - name %d" + " received from audio service", + inmsg->type, inmsg->name); + } + + } + + return err; +} + +static int audioservice_expect(int sk, bt_audio_msg_header_t *rsp, + int expected_name) +{ + bt_audio_error_t *error; + int err = audioservice_recv(sk, rsp); + + if (err != 0) + return err; + + if (rsp->name != expected_name) { + err = -EINVAL; + SNDERR("Bogus message %s received while %s was expected", + bt_audio_strname(rsp->name), + bt_audio_strname(expected_name)); + } + + if (rsp->type == BT_ERROR) { + error = (void *) rsp; + SNDERR("%s failed : %s(%d)", + bt_audio_strname(rsp->name), + strerror(error->posix_errno), + error->posix_errno); + return -error->posix_errno; + } + + return err; +} + +static int bluetooth_parse_capabilities(struct bluetooth_data *data, + struct bt_get_capabilities_rsp *rsp) +{ + int bytes_left = rsp->h.length - sizeof(*rsp); + codec_capabilities_t *codec = (void *) rsp->data; + + data->transport = codec->transport; + + if (codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) + return 0; + + while (bytes_left > 0) { + if ((codec->type == BT_A2DP_SBC_SINK) && + !(codec->lock & BT_WRITE_LOCK)) + break; + + bytes_left -= codec->length; + codec = (void *) codec + codec->length; + } + + if (bytes_left <= 0 || + codec->length != sizeof(data->a2dp.sbc_capabilities)) + return -EINVAL; + + memcpy(&data->a2dp.sbc_capabilities, codec, codec->length); + + return 0; +} + +static int bluetooth_init(struct bluetooth_data *data, + snd_pcm_stream_t stream, snd_config_t *conf) +{ + int sk, err; + struct bluetooth_alsa_config *alsa_conf = &data->alsa_config; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_req *req = (void *) buf; + struct bt_get_capabilities_rsp *rsp = (void *) buf; + + memset(data, 0, sizeof(struct bluetooth_data)); + + err = bluetooth_parse_config(conf, alsa_conf); + if (err < 0) + return err; + + data->server.fd = -1; + data->stream.fd = -1; + + sk = bt_audio_service_open(); + if (sk <= 0) { + err = -errno; + goto failed; + } + + data->server.fd = sk; + data->server.events = POLLIN; + + data->pipefd[0] = -1; + data->pipefd[1] = -1; + + if (pipe(data->pipefd) < 0) { + err = -errno; + goto failed; + } + if (fcntl(data->pipefd[0], F_SETFL, O_NONBLOCK) < 0) { + err = -errno; + goto failed; + } + if (fcntl(data->pipefd[1], F_SETFL, O_NONBLOCK) < 0) { + err = -errno; + goto failed; + } + + memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); + req->h.type = BT_REQUEST; + req->h.name = BT_GET_CAPABILITIES; + req->h.length = sizeof(*req); + + if (alsa_conf->autoconnect) + req->flags |= BT_FLAG_AUTOCONNECT; + strncpy(req->destination, alsa_conf->device, 18); + if (alsa_conf->has_transport) + req->transport = alsa_conf->transport; + else + req->transport = BT_CAPABILITIES_TRANSPORT_ANY; + + err = audioservice_send(data->server.fd, &req->h); + if (err < 0) + goto failed; + + rsp->h.length = 0; + err = audioservice_expect(data->server.fd, &rsp->h, + BT_GET_CAPABILITIES); + if (err < 0) + goto failed; + + bluetooth_parse_capabilities(data, rsp); + + return 0; + +failed: + if (sk >= 0) + bt_audio_service_close(sk); + return err; +} + +SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth); + +SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) +{ + struct bluetooth_data *data; + int err; + + DBG("Bluetooth PCM plugin (%s)", + stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); + + data = malloc(sizeof(struct bluetooth_data)); + if (!data) { + err = -ENOMEM; + goto error; + } + + err = bluetooth_init(data, stream, conf); + if (err < 0) + goto error; + + data->io.version = SND_PCM_IOPLUG_VERSION; + data->io.name = "Bluetooth Audio Device"; + data->io.mmap_rw = 0; /* No direct mmap communication */ + data->io.private_data = data; + + if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? + &bluetooth_a2dp_playback : + &bluetooth_a2dp_capture; + else + data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? + &bluetooth_hsp_playback : + &bluetooth_hsp_capture; + + err = snd_pcm_ioplug_create(&data->io, name, stream, mode); + if (err < 0) + goto error; + + if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + err = bluetooth_a2dp_hw_constraint(&data->io); + else + err = bluetooth_hsp_hw_constraint(&data->io); + + if (err < 0) { + snd_pcm_ioplug_delete(&data->io); + goto error; + } + + *pcmp = data->io.pcm; + + return 0; + +error: + if (data) + bluetooth_exit(data); + + return err; +} + +SND_PCM_PLUGIN_SYMBOL(bluetooth); diff --git a/audio/rtp.h b/audio/rtp.h new file mode 100644 index 000000000..145736284 --- /dev/null +++ b/audio/rtp.h @@ -0,0 +1,76 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct rtp_header { + unsigned cc:4; + unsigned x:1; + unsigned p:1; + unsigned v:2; + + unsigned pt:7; + unsigned m:1; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + unsigned frame_count:4; + unsigned rfa0:1; + unsigned is_last_fragment:1; + unsigned is_first_fragment:1; + unsigned is_fragmented:1; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct rtp_header { + unsigned v:2; + unsigned p:1; + unsigned x:1; + unsigned cc:4; + + unsigned m:1; + unsigned pt:7; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + unsigned is_fragmented:1; + unsigned is_first_fragment:1; + unsigned is_last_fragment:1; + unsigned rfa0:1; + unsigned frame_count:4; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif diff --git a/audio/sink.c b/audio/sink.c new file mode 100644 index 000000000..90bceabcd --- /dev/null +++ b/audio/sink.c @@ -0,0 +1,940 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2009-2010 Motorola 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include + +#include "logging.h" + +#include "device.h" +#include "avdtp.h" +#include "a2dp.h" +#include "error.h" +#include "sink.h" +#include "dbus-common.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#define STREAM_SETUP_RETRY_TIMER 2 + +struct pending_request { + DBusConnection *conn; + DBusMessage *msg; + unsigned int id; +}; + +struct sink { + struct audio_device *dev; + struct avdtp *session; + struct avdtp_stream *stream; + unsigned int cb_id; + guint dc_id; + guint retry_id; + avdtp_session_state_t session_state; + avdtp_state_t stream_state; + sink_state_t state; + struct pending_request *connect; + struct pending_request *disconnect; + DBusConnection *conn; +}; + +struct sink_state_callback { + sink_state_cb cb; + void *user_data; + unsigned int id; +}; + +static GSList *sink_callbacks = NULL; + +static unsigned int avdtp_callback_id = 0; + +static const char *state2str(sink_state_t state) +{ + switch (state) { + case SINK_STATE_DISCONNECTED: + return "disconnected"; + case SINK_STATE_CONNECTING: + return "connecting"; + case SINK_STATE_CONNECTED: + return "connected"; + case SINK_STATE_PLAYING: + return "playing"; + default: + error("Invalid sink state %d", state); + return NULL; + } +} + +static void sink_set_state(struct audio_device *dev, sink_state_t new_state) +{ + struct sink *sink = dev->sink; + const char *state_str; + sink_state_t old_state = sink->state; + GSList *l; + + sink->state = new_state; + + state_str = state2str(new_state); + if (state_str) + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + + for (l = sink_callbacks; l != NULL; l = l->next) { + struct sink_state_callback *cb = l->data; + cb->cb(dev, old_state, new_state, cb->user_data); + } +} + +static void avdtp_state_callback(struct audio_device *dev, + struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + struct sink *sink = dev->sink; + + if (sink == NULL) + return; + + switch (new_state) { + case AVDTP_SESSION_STATE_DISCONNECTED: + if (sink->state != SINK_STATE_CONNECTING) { + gboolean value = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, "Disconnected", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + if (sink->dc_id) { + device_remove_disconnect_watch(dev->btd_dev, + sink->dc_id); + sink->dc_id = 0; + } + } + sink_set_state(dev, SINK_STATE_DISCONNECTED); + break; + case AVDTP_SESSION_STATE_CONNECTING: + sink_set_state(dev, SINK_STATE_CONNECTING); + break; + case AVDTP_SESSION_STATE_CONNECTED: + break; + } + + sink->session_state = new_state; +} + +static void pending_request_free(struct audio_device *dev, + struct pending_request *pending) +{ + if (pending->conn) + dbus_connection_unref(pending->conn); + if (pending->msg) + dbus_message_unref(pending->msg); + if (pending->id) + a2dp_cancel(dev, pending->id); + + g_free(pending); +} + +static void disconnect_cb(struct btd_device *btd_dev, gboolean removal, + void *user_data) +{ + struct audio_device *device = user_data; + struct sink *sink = device->sink; + + debug("Sink: disconnect %s", device->path); + + avdtp_close(sink->session, sink->stream); +} + +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 audio_device *dev = user_data; + struct sink *sink = dev->sink; + gboolean value; + + if (err) + return; + + switch (new_state) { + case AVDTP_STATE_IDLE: + if (sink->disconnect) { + DBusMessage *reply; + struct pending_request *p; + + p = sink->disconnect; + sink->disconnect = NULL; + + reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(p->conn, reply); + pending_request_free(dev, p); + } + + if (sink->dc_id) { + device_remove_disconnect_watch(dev->btd_dev, + sink->dc_id); + sink->dc_id = 0; + } + + if (sink->session) { + avdtp_unref(sink->session); + sink->session = NULL; + } + sink->stream = NULL; + sink->cb_id = 0; + break; + case AVDTP_STATE_OPEN: + if (old_state == AVDTP_STATE_CONFIGURED && + sink->state == SINK_STATE_CONNECTING) { + value = TRUE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Connected", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Connected", + DBUS_TYPE_BOOLEAN, &value); + sink->dc_id = device_add_disconnect_watch(dev->btd_dev, + disconnect_cb, + dev, NULL); + } else if (old_state == AVDTP_STATE_STREAMING) { + value = FALSE; + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Stopped", + DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Playing", + DBUS_TYPE_BOOLEAN, &value); + } + sink_set_state(dev, SINK_STATE_CONNECTED); + break; + case AVDTP_STATE_STREAMING: + value = TRUE; + g_dbus_emit_signal(dev->conn, dev->path, AUDIO_SINK_INTERFACE, + "Playing", DBUS_TYPE_INVALID); + emit_property_changed(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, "Playing", + DBUS_TYPE_BOOLEAN, &value); + sink_set_state(dev, SINK_STATE_PLAYING); + break; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + break; + } + + sink->stream_state = new_state; +} + +static DBusHandlerResult error_failed(DBusConnection *conn, + DBusMessage *msg, const char * desc) +{ + return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc); +} + +static gboolean stream_setup_retry(gpointer user_data) +{ + struct sink *sink = user_data; + struct pending_request *pending = sink->connect; + + sink->retry_id = 0; + + if (sink->stream_state >= AVDTP_STATE_OPEN) { + debug("Stream successfully created, after XCASE connect:connect"); + if (pending->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + g_dbus_send_message(pending->conn, reply); + } + } else { + debug("Stream setup failed, after XCASE connect:connect"); + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + } + + sink->connect = NULL; + pending_request_free(sink->dev, pending); + + return FALSE; +} + +static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct sink *sink = user_data; + struct pending_request *pending; + + pending = sink->connect; + + pending->id = 0; + + if (stream) { + debug("Stream successfully created"); + + if (pending->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + g_dbus_send_message(pending->conn, reply); + } + + sink->connect = NULL; + pending_request_free(sink->dev, pending); + + return; + } + + avdtp_unref(sink->session); + sink->session = NULL; + if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + debug("connect:connect XCASE detected"); + sink->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, + sink); + } else { + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + sink->connect = NULL; + pending_request_free(sink->dev, pending); + debug("Stream setup failed : %s", avdtp_strerror(err)); + } +} + +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case SBC_SAMPLING_FREQ_16000: + case SBC_SAMPLING_FREQ_32000: + return 53; + case SBC_SAMPLING_FREQ_44100: + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + error("Invalid channel mode %u", mode); + return 53; + } + case SBC_SAMPLING_FREQ_48000: + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + error("Invalid channel mode %u", mode); + return 51; + } + default: + error("Invalid sampling freq %u", freq); + return 53; + } +} + +static gboolean select_sbc_params(struct sbc_codec_cap *cap, + struct sbc_codec_cap *supported) +{ + unsigned int max_bitpool, min_bitpool; + + memset(cap, 0, sizeof(struct sbc_codec_cap)); + + cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->cap.media_codec_type = A2DP_CODEC_SBC; + + if (supported->frequency & SBC_SAMPLING_FREQ_44100) + cap->frequency = SBC_SAMPLING_FREQ_44100; + else if (supported->frequency & SBC_SAMPLING_FREQ_48000) + cap->frequency = SBC_SAMPLING_FREQ_48000; + else if (supported->frequency & SBC_SAMPLING_FREQ_32000) + cap->frequency = SBC_SAMPLING_FREQ_32000; + else if (supported->frequency & SBC_SAMPLING_FREQ_16000) + cap->frequency = SBC_SAMPLING_FREQ_16000; + else { + error("No supported frequencies"); + return FALSE; + } + + if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO) + cap->channel_mode = SBC_CHANNEL_MODE_STEREO; + else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; + else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO) + cap->channel_mode = SBC_CHANNEL_MODE_MONO; + else { + error("No supported channel modes"); + return FALSE; + } + + if (supported->block_length & SBC_BLOCK_LENGTH_16) + cap->block_length = SBC_BLOCK_LENGTH_16; + else if (supported->block_length & SBC_BLOCK_LENGTH_12) + cap->block_length = SBC_BLOCK_LENGTH_12; + else if (supported->block_length & SBC_BLOCK_LENGTH_8) + cap->block_length = SBC_BLOCK_LENGTH_8; + else if (supported->block_length & SBC_BLOCK_LENGTH_4) + cap->block_length = SBC_BLOCK_LENGTH_4; + else { + error("No supported block lengths"); + return FALSE; + } + + if (supported->subbands & SBC_SUBBANDS_8) + cap->subbands = SBC_SUBBANDS_8; + else if (supported->subbands & SBC_SUBBANDS_4) + cap->subbands = SBC_SUBBANDS_4; + else { + error("No supported subbands"); + return FALSE; + } + + if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS) + cap->allocation_method = SBC_ALLOCATION_LOUDNESS; + else if (supported->allocation_method & SBC_ALLOCATION_SNR) + cap->allocation_method = SBC_ALLOCATION_SNR; + + min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), + supported->max_bitpool); + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return TRUE; +} + +static gboolean select_capabilities(struct avdtp *session, + struct avdtp_remote_sep *rsep, + GSList **caps) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + + media_codec = avdtp_get_codec(rsep); + if (!media_codec) + return FALSE; + + select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data); + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + + return TRUE; +} + +static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err, + void *user_data) +{ + struct sink *sink = user_data; + struct pending_request *pending; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + struct a2dp_sep *sep; + GSList *caps = NULL; + int id; + + pending = sink->connect; + + if (err) { + avdtp_unref(sink->session); + sink->session = NULL; + if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + debug("connect:connect XCASE detected"); + sink->retry_id = + g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, + sink); + } else + goto failed; + return; + } + + debug("Discovery complete"); + + if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO, + A2DP_CODEC_SBC, &lsep, &rsep) < 0) { + error("No matching ACP and INT SEPs found"); + goto failed; + } + + if (!select_capabilities(session, rsep, &caps)) { + error("Unable to select remote SEP capabilities"); + goto failed; + } + + sep = a2dp_get(session, rsep); + if (!sep) { + error("Unable to get a local source SEP"); + goto failed; + } + + id = a2dp_config(sink->session, sep, stream_setup_complete, caps, sink); + if (id == 0) + goto failed; + + pending->id = id; + return; + +failed: + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + pending_request_free(sink->dev, pending); + sink->connect = NULL; + avdtp_unref(sink->session); + sink->session = NULL; +} + +gboolean sink_setup_stream(struct sink *sink, struct avdtp *session) +{ + if (sink->connect || sink->disconnect) + return FALSE; + + if (session && !sink->session) + sink->session = avdtp_ref(session); + + if (!sink->session) + return FALSE; + + avdtp_set_auto_disconnect(sink->session, FALSE); + + if (avdtp_discover(sink->session, discovery_complete, sink) < 0) + return FALSE; + + sink->connect = g_new0(struct pending_request, 1); + + return TRUE; +} + +static DBusMessage *sink_connect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *dev = data; + struct sink *sink = dev->sink; + struct pending_request *pending; + + if (!sink->session) + sink->session = avdtp_get(&dev->src, &dev->dst); + + if (!sink->session) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Unable to get a session"); + + if (sink->connect || sink->disconnect) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(EBUSY)); + + if (sink->stream_state >= AVDTP_STATE_OPEN) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".AlreadyConnected", + "Device Already Connected"); + + if (!sink_setup_stream(sink, NULL)) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Failed to create a stream"); + + dev->auto_connect = FALSE; + + pending = sink->connect; + + pending->conn = dbus_connection_ref(conn); + pending->msg = dbus_message_ref(msg); + + debug("stream creation in progress"); + + return NULL; +} + +static DBusMessage *sink_disconnect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct sink *sink = device->sink; + struct pending_request *pending; + int err; + + if (!sink->session) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + if (sink->connect || sink->disconnect) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(EBUSY)); + + if (sink->stream_state < AVDTP_STATE_OPEN) { + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + avdtp_unref(sink->session); + sink->session = NULL; + return reply; + } + + err = avdtp_close(sink->session, sink->stream); + if (err < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(-err)); + + pending = g_new0(struct pending_request, 1); + pending->conn = dbus_connection_ref(conn); + pending->msg = dbus_message_ref(msg); + sink->disconnect = pending; + + return NULL; +} + +static DBusMessage *sink_suspend(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct sink *sink = device->sink; + struct pending_request *pending; + int err; + + if (!sink->session) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + if (sink->connect || sink->disconnect) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(EBUSY)); + + if (sink->state < AVDTP_STATE_OPEN) { + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + avdtp_unref(sink->session); + sink->session = NULL; + return reply; + } + + err = avdtp_suspend(sink->session, sink->stream); + if (err < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(-err)); + + return NULL; +} + +static DBusMessage *sink_resume(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct sink *sink = device->sink; + struct pending_request *pending; + int err; + + if (!sink->session) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + if (sink->connect || sink->disconnect) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(EBUSY)); + + if (sink->state < AVDTP_STATE_OPEN) { + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + avdtp_unref(sink->session); + sink->session = NULL; + return reply; + } + + err = avdtp_start(sink->session, sink->stream); + if (err < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(-err)); + + return NULL; +} + +static DBusMessage *sink_is_connected(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct sink *sink = device->sink; + DBusMessage *reply; + dbus_bool_t connected; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + connected = (sink->stream_state >= AVDTP_STATE_CONFIGURED); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *sink_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct sink *sink = device->sink; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *state; + gboolean value; + + 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); + + /* Playing */ + value = (sink->stream_state == AVDTP_STATE_STREAMING); + dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value); + + /* Connected */ + value = (sink->stream_state >= AVDTP_STATE_CONFIGURED); + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); + + /* State */ + state = state2str(sink->state); + if (state) + dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable sink_methods[] = { + { "Connect", "", "", sink_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", sink_disconnect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Suspend", "", "", sink_suspend, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Resume", "", "", sink_resume, + G_DBUS_METHOD_FLAG_ASYNC }, + { "IsConnected", "", "b", sink_is_connected, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetProperties", "", "a{sv}",sink_get_properties }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable sink_signals[] = { + { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Playing", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "Stopped", "", G_DBUS_SIGNAL_FLAG_DEPRECATED }, + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +static void sink_free(struct audio_device *dev) +{ + struct sink *sink = dev->sink; + + if (sink->cb_id) + avdtp_stream_remove_cb(sink->session, sink->stream, + sink->cb_id); + + if (sink->dc_id) + device_remove_disconnect_watch(dev->btd_dev, sink->dc_id); + + if (sink->session) + avdtp_unref(sink->session); + + if (sink->connect) + pending_request_free(dev, sink->connect); + + if (sink->disconnect) + pending_request_free(dev, sink->disconnect); + + if (sink->retry_id) + g_source_remove(sink->retry_id); + + g_free(sink); + dev->sink = NULL; +} + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + + debug("Unregistered interface %s on path %s", + AUDIO_SINK_INTERFACE, dev->path); + + sink_free(dev); +} + +void sink_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_SINK_INTERFACE); +} + +struct sink *sink_init(struct audio_device *dev) +{ + struct sink *sink; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + sink_methods, sink_signals, NULL, + dev, path_unregister)) + return NULL; + + debug("Registered interface %s on path %s", + AUDIO_SINK_INTERFACE, dev->path); + + if (avdtp_callback_id == 0) + avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback, + NULL); + + sink = g_new0(struct sink, 1); + + sink->dev = dev; + + return sink; +} + +gboolean sink_is_active(struct audio_device *dev) +{ + struct sink *sink = dev->sink; + + if (sink->session) + return TRUE; + + return FALSE; +} + +avdtp_state_t sink_get_state(struct audio_device *dev) +{ + struct sink *sink = dev->sink; + + return sink->stream_state; +} + +gboolean sink_new_stream(struct audio_device *dev, struct avdtp *session, + struct avdtp_stream *stream) +{ + struct sink *sink = dev->sink; + + 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, dev); + + return TRUE; +} + +gboolean sink_shutdown(struct sink *sink) +{ + if (!sink->stream) + return FALSE; + + if (avdtp_close(sink->session, sink->stream) < 0) + return FALSE; + + return TRUE; +} + +unsigned int sink_add_state_cb(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->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; +} diff --git a/audio/sink.h b/audio/sink.h new file mode 100644 index 000000000..3f822bc97 --- /dev/null +++ b/audio/sink.h @@ -0,0 +1,49 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#define AUDIO_SINK_INTERFACE "org.bluez.AudioSink" + +typedef enum { + SINK_STATE_DISCONNECTED, + SINK_STATE_CONNECTING, + SINK_STATE_CONNECTED, + SINK_STATE_PLAYING, +} sink_state_t; + +typedef void (*sink_state_cb) (struct audio_device *dev, + sink_state_t old_state, + sink_state_t new_state, + void *user_data); + +unsigned int sink_add_state_cb(sink_state_cb cb, void *user_data); +gboolean sink_remove_state_cb(unsigned int id); + +struct sink *sink_init(struct audio_device *dev); +void sink_unregister(struct audio_device *dev); +gboolean sink_is_active(struct audio_device *dev); +avdtp_state_t sink_get_state(struct audio_device *dev); +gboolean sink_new_stream(struct audio_device *dev, struct avdtp *session, + struct avdtp_stream *stream); +gboolean sink_setup_stream(struct sink *sink, struct avdtp *session); +gboolean sink_shutdown(struct sink *sink); diff --git a/audio/source.c b/audio/source.c new file mode 100644 index 000000000..1cec1f64d --- /dev/null +++ b/audio/source.c @@ -0,0 +1,800 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2009 Joao Paulo Rechi Vita + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include + +#include "logging.h" + +#include "device.h" +#include "avdtp.h" +#include "a2dp.h" +#include "error.h" +#include "source.h" +#include "dbus-common.h" +#include "../src/adapter.h" +#include "../src/device.h" + +#define STREAM_SETUP_RETRY_TIMER 2 + +struct pending_request { + DBusConnection *conn; + DBusMessage *msg; + unsigned int id; +}; + +struct source { + struct audio_device *dev; + struct avdtp *session; + struct avdtp_stream *stream; + unsigned int cb_id; + guint dc_id; + guint retry_id; + avdtp_session_state_t session_state; + avdtp_state_t stream_state; + source_state_t state; + struct pending_request *connect; + struct pending_request *disconnect; + DBusConnection *conn; +}; + +struct source_state_callback { + source_state_cb cb; + void *user_data; + unsigned int id; +}; + +static GSList *source_callbacks = NULL; + +static unsigned int avdtp_callback_id = 0; + +static const char *state2str(source_state_t state) +{ + switch (state) { + case SOURCE_STATE_DISCONNECTED: + return "disconnected"; + case SOURCE_STATE_CONNECTING: + return "connecting"; + case SOURCE_STATE_CONNECTED: + return "connected"; + case SOURCE_STATE_PLAYING: + return "playing"; + default: + error("Invalid source state %d", state); + return NULL; + } +} + +static void source_set_state(struct audio_device *dev, source_state_t new_state) +{ + struct source *source = dev->source; + const char *state_str; + source_state_t old_state = source->state; + GSList *l; + + source->state = new_state; + + state_str = state2str(new_state); + if (state_str) + emit_property_changed(dev->conn, dev->path, + AUDIO_SOURCE_INTERFACE, "State", + DBUS_TYPE_STRING, &state_str); + + for (l = source_callbacks; l != NULL; l = l->next) { + struct source_state_callback *cb = l->data; + cb->cb(dev, old_state, new_state, cb->user_data); + } +} + +static void avdtp_state_callback(struct audio_device *dev, + struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + struct source *source = dev->source; + + if (source == NULL) + return; + + switch (new_state) { + case AVDTP_SESSION_STATE_DISCONNECTED: + if (source->state != SOURCE_STATE_CONNECTING && + source->dc_id) { + device_remove_disconnect_watch(dev->btd_dev, + source->dc_id); + source->dc_id = 0; + } + source_set_state(dev, SOURCE_STATE_DISCONNECTED); + break; + case AVDTP_SESSION_STATE_CONNECTING: + source_set_state(dev, SOURCE_STATE_CONNECTING); + break; + case AVDTP_SESSION_STATE_CONNECTED: + break; + } + + source->session_state = new_state; +} + +static void pending_request_free(struct audio_device *dev, + struct pending_request *pending) +{ + if (pending->conn) + dbus_connection_unref(pending->conn); + if (pending->msg) + dbus_message_unref(pending->msg); + if (pending->id) + a2dp_cancel(dev, pending->id); + + g_free(pending); +} + +static void disconnect_cb(struct btd_device *btd_dev, gboolean removal, + void *user_data) +{ + struct audio_device *device = user_data; + struct source *source = device->source; + + debug("Source: disconnect %s", device->path); + + avdtp_close(source->session, source->stream); +} + +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 audio_device *dev = user_data; + struct source *source = dev->source; + + if (err) + return; + + switch (new_state) { + case AVDTP_STATE_IDLE: + if (source->disconnect) { + DBusMessage *reply; + struct pending_request *p; + + p = source->disconnect; + source->disconnect = NULL; + + reply = dbus_message_new_method_return(p->msg); + g_dbus_send_message(p->conn, reply); + pending_request_free(dev, p); + } + + if (source->dc_id) { + device_remove_disconnect_watch(dev->btd_dev, + source->dc_id); + source->dc_id = 0; + } + + if (source->session) { + avdtp_unref(source->session); + source->session = NULL; + } + source->stream = NULL; + source->cb_id = 0; + break; + case AVDTP_STATE_OPEN: + if (old_state == AVDTP_STATE_CONFIGURED && + source->state == SOURCE_STATE_CONNECTING) { + source->dc_id = device_add_disconnect_watch(dev->btd_dev, + disconnect_cb, + dev, NULL); + } + source_set_state(dev, SOURCE_STATE_CONNECTED); + break; + case AVDTP_STATE_STREAMING: + source_set_state(dev, SOURCE_STATE_PLAYING); + break; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + break; + } + + source->stream_state = new_state; +} + +static DBusHandlerResult error_failed(DBusConnection *conn, + DBusMessage *msg, const char * desc) +{ + return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc); +} + +static gboolean stream_setup_retry(gpointer user_data) +{ + struct source *source = user_data; + struct pending_request *pending = source->connect; + + source->retry_id = 0; + + if (source->stream_state >= AVDTP_STATE_OPEN) { + debug("Stream successfully created, after XCASE connect:connect"); + if (pending->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + g_dbus_send_message(pending->conn, reply); + } + } else { + debug("Stream setup failed, after XCASE connect:connect"); + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + } + + source->connect = NULL; + pending_request_free(source->dev, pending); + + return FALSE; +} + +static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct source *source = user_data; + struct pending_request *pending; + + pending = source->connect; + + pending->id = 0; + + if (stream) { + debug("Stream successfully created"); + + if (pending->msg) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + g_dbus_send_message(pending->conn, reply); + } + + source->connect = NULL; + pending_request_free(source->dev, pending); + + return; + } + + avdtp_unref(source->session); + source->session = NULL; + if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + debug("connect:connect XCASE detected"); + source->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, + source); + } else { + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + source->connect = NULL; + pending_request_free(source->dev, pending); + debug("Stream setup failed : %s", avdtp_strerror(err)); + } +} + +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case SBC_SAMPLING_FREQ_16000: + case SBC_SAMPLING_FREQ_32000: + return 53; + case SBC_SAMPLING_FREQ_44100: + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + error("Invalid channel mode %u", mode); + return 53; + } + case SBC_SAMPLING_FREQ_48000: + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + error("Invalid channel mode %u", mode); + return 51; + } + default: + error("Invalid sampling freq %u", freq); + return 53; + } +} + +static gboolean select_sbc_params(struct sbc_codec_cap *cap, + struct sbc_codec_cap *supported) +{ + unsigned int max_bitpool, min_bitpool; + + memset(cap, 0, sizeof(struct sbc_codec_cap)); + + cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->cap.media_codec_type = A2DP_CODEC_SBC; + + if (supported->frequency & SBC_SAMPLING_FREQ_44100) + cap->frequency = SBC_SAMPLING_FREQ_44100; + else if (supported->frequency & SBC_SAMPLING_FREQ_48000) + cap->frequency = SBC_SAMPLING_FREQ_48000; + else if (supported->frequency & SBC_SAMPLING_FREQ_32000) + cap->frequency = SBC_SAMPLING_FREQ_32000; + else if (supported->frequency & SBC_SAMPLING_FREQ_16000) + cap->frequency = SBC_SAMPLING_FREQ_16000; + else { + error("No supported frequencies"); + return FALSE; + } + + if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO) + cap->channel_mode = SBC_CHANNEL_MODE_STEREO; + else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; + else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO) + cap->channel_mode = SBC_CHANNEL_MODE_MONO; + else { + error("No supported channel modes"); + return FALSE; + } + + if (supported->block_length & SBC_BLOCK_LENGTH_16) + cap->block_length = SBC_BLOCK_LENGTH_16; + else if (supported->block_length & SBC_BLOCK_LENGTH_12) + cap->block_length = SBC_BLOCK_LENGTH_12; + else if (supported->block_length & SBC_BLOCK_LENGTH_8) + cap->block_length = SBC_BLOCK_LENGTH_8; + else if (supported->block_length & SBC_BLOCK_LENGTH_4) + cap->block_length = SBC_BLOCK_LENGTH_4; + else { + error("No supported block lengths"); + return FALSE; + } + + if (supported->subbands & SBC_SUBBANDS_8) + cap->subbands = SBC_SUBBANDS_8; + else if (supported->subbands & SBC_SUBBANDS_4) + cap->subbands = SBC_SUBBANDS_4; + else { + error("No supported subbands"); + return FALSE; + } + + if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS) + cap->allocation_method = SBC_ALLOCATION_LOUDNESS; + else if (supported->allocation_method & SBC_ALLOCATION_SNR) + cap->allocation_method = SBC_ALLOCATION_SNR; + + min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), + supported->max_bitpool); + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return TRUE; +} + +static gboolean select_capabilities(struct avdtp *session, + struct avdtp_remote_sep *rsep, + GSList **caps) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + + media_codec = avdtp_get_codec(rsep); + if (!media_codec) + return FALSE; + + select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data); + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + + return TRUE; +} + +static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err, + void *user_data) +{ + struct source *source = user_data; + struct pending_request *pending; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + struct a2dp_sep *sep; + GSList *caps = NULL; + int id; + + pending = source->connect; + + if (err) { + avdtp_unref(source->session); + source->session = NULL; + if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + debug("connect:connect XCASE detected"); + source->retry_id = + g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, + source); + } else + goto failed; + return; + } + + debug("Discovery complete"); + + if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SOURCE, AVDTP_MEDIA_TYPE_AUDIO, + A2DP_CODEC_SBC, &lsep, &rsep) < 0) { + error("No matching ACP and INT SEPs found"); + goto failed; + } + + if (!select_capabilities(session, rsep, &caps)) { + error("Unable to select remote SEP capabilities"); + goto failed; + } + + sep = a2dp_get(session, rsep); + if (!sep) { + error("Unable to get a local sink SEP"); + goto failed; + } + + id = a2dp_config(source->session, sep, stream_setup_complete, caps, + source); + if (id == 0) + goto failed; + + pending->id = id; + return; + +failed: + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + pending_request_free(source->dev, pending); + source->connect = NULL; + avdtp_unref(source->session); + source->session = NULL; +} + +gboolean source_setup_stream(struct source *source, struct avdtp *session) +{ + if (source->connect || source->disconnect) + return FALSE; + + if (session && !source->session) + source->session = avdtp_ref(session); + + if (!source->session) + return FALSE; + + avdtp_set_auto_disconnect(source->session, FALSE); + + if (avdtp_discover(source->session, discovery_complete, source) < 0) + return FALSE; + + source->connect = g_new0(struct pending_request, 1); + + return TRUE; +} + +static DBusMessage *source_connect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *dev = data; + struct source *source = dev->source; + struct pending_request *pending; + + if (!source->session) + source->session = avdtp_get(&dev->src, &dev->dst); + + if (!source->session) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Unable to get a session"); + + if (source->connect || source->disconnect) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(EBUSY)); + + if (source->stream_state >= AVDTP_STATE_OPEN) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".AlreadyConnected", + "Device Already Connected"); + + if (!source_setup_stream(source, NULL)) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Failed to create a stream"); + + dev->auto_connect = FALSE; + + pending = source->connect; + + pending->conn = dbus_connection_ref(conn); + pending->msg = dbus_message_ref(msg); + + debug("stream creation in progress"); + + return NULL; +} + +static DBusMessage *source_disconnect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct source *source = device->source; + struct pending_request *pending; + int err; + + if (!source->session) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + if (source->connect || source->disconnect) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(EBUSY)); + + if (source->stream_state < AVDTP_STATE_OPEN) { + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + avdtp_unref(source->session); + source->session = NULL; + return reply; + } + + err = avdtp_close(source->session, source->stream); + if (err < 0) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", strerror(-err)); + + pending = g_new0(struct pending_request, 1); + pending->conn = dbus_connection_ref(conn); + pending->msg = dbus_message_ref(msg); + source->disconnect = pending; + + return NULL; +} + +static DBusMessage *source_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct source *source = device->source; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *state; + + 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); + + /* State */ + state = state2str(source->state); + if (state) + dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable source_methods[] = { + { "Connect", "", "", source_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", source_disconnect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "GetProperties", "", "a{sv}",source_get_properties }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable source_signals[] = { + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +static void source_free(struct audio_device *dev) +{ + struct source *source = dev->source; + + if (source->cb_id) + avdtp_stream_remove_cb(source->session, source->stream, + source->cb_id); + + if (source->dc_id) + device_remove_disconnect_watch(dev->btd_dev, source->dc_id); + + if (source->session) + avdtp_unref(source->session); + + if (source->connect) + pending_request_free(dev, source->connect); + + if (source->disconnect) + pending_request_free(dev, source->disconnect); + + if (source->retry_id) + g_source_remove(source->retry_id); + + g_free(source); + dev->source = NULL; +} + +static void path_unregister(void *data) +{ + struct audio_device *dev = data; + + debug("Unregistered interface %s on path %s", + AUDIO_SOURCE_INTERFACE, dev->path); + + source_free(dev); +} + +void source_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + AUDIO_SOURCE_INTERFACE); +} + +struct source *source_init(struct audio_device *dev) +{ + struct source *source; + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_SOURCE_INTERFACE, + source_methods, source_signals, NULL, + dev, path_unregister)) + return NULL; + + debug("Registered interface %s on path %s", + AUDIO_SOURCE_INTERFACE, dev->path); + + if (avdtp_callback_id == 0) + avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback, + NULL); + + source = g_new0(struct source, 1); + + source->dev = dev; + + return source; +} + +gboolean source_is_active(struct audio_device *dev) +{ + struct source *source = dev->source; + + if (source->session) + return TRUE; + + return FALSE; +} + +avdtp_state_t source_get_state(struct audio_device *dev) +{ + struct source *source = dev->source; + + return source->stream_state; +} + +gboolean source_new_stream(struct audio_device *dev, struct avdtp *session, + struct avdtp_stream *stream) +{ + struct source *source = dev->source; + + 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, dev); + + return TRUE; +} + +gboolean source_shutdown(struct source *source) +{ + if (!source->stream) + return FALSE; + + if (avdtp_close(source->session, source->stream) < 0) + return FALSE; + + return TRUE; +} + +unsigned int source_add_state_cb(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->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; +} diff --git a/audio/source.h b/audio/source.h new file mode 100644 index 000000000..8250814d2 --- /dev/null +++ b/audio/source.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2009 Joao Paulo Rechi Vita + * + * + * 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 + * + */ + +#define AUDIO_SOURCE_INTERFACE "org.bluez.AudioSource" + +typedef enum { + SOURCE_STATE_DISCONNECTED, + SOURCE_STATE_CONNECTING, + SOURCE_STATE_CONNECTED, + SOURCE_STATE_PLAYING, +} source_state_t; + +typedef void (*source_state_cb) (struct audio_device *dev, + source_state_t old_state, + source_state_t new_state, + void *user_data); + +unsigned int source_add_state_cb(source_state_cb cb, void *user_data); +gboolean source_remove_state_cb(unsigned int id); + +struct source *source_init(struct audio_device *dev); +void source_unregister(struct audio_device *dev); +gboolean source_is_active(struct audio_device *dev); +avdtp_state_t source_get_state(struct audio_device *dev); +gboolean source_new_stream(struct audio_device *dev, struct avdtp *session, + struct avdtp_stream *stream); +gboolean source_setup_stream(struct source *source, struct avdtp *session); +gboolean source_shutdown(struct source *source); diff --git a/audio/telephony-dummy.c b/audio/telephony-dummy.c new file mode 100644 index 000000000..deb604c23 --- /dev/null +++ b/audio/telephony-dummy.c @@ -0,0 +1,417 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "telephony.h" + +static const char *chld_str = "0,1,1x,2,2x,3,4"; +static char *subscriber_number = NULL; +static char *active_call_number = NULL; +static int active_call_status = 0; +static int active_call_dir = 0; + +static gboolean events_enabled = FALSE; + +/* Response and hold state + * -1 = none + * 0 = incoming call is put on hold in the AG + * 1 = held incoming call is accepted in the AG + * 2 = held incoming call is rejected in the AG + */ +static int response_and_hold = -1; + +static struct indicator dummy_indicators[] = +{ + { "battchg", "0-5", 5, TRUE }, + { "signal", "0-5", 5, TRUE }, + { "service", "0,1", 1, TRUE }, + { "call", "0,1", 0, TRUE }, + { "callsetup", "0-3", 0, TRUE }, + { "callheld", "0-2", 0, FALSE }, + { "roam", "0,1", 0, TRUE }, + { NULL } +}; + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments", + "Invalid arguments in method call"); +} + +void telephony_device_connected(void *telephony_device) +{ + debug("telephony-dummy: device %p connected", telephony_device); +} + +void telephony_device_disconnected(void *telephony_device) +{ + debug("telephony-dummy: device %p disconnected", telephony_device); + events_enabled = FALSE; +} + +void telephony_event_reporting_req(void *telephony_device, int ind) +{ + events_enabled = ind == 1 ? TRUE : FALSE; + + telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_response_and_hold_req(void *telephony_device, int rh) +{ + response_and_hold = rh; + + telephony_response_and_hold_ind(response_and_hold); + + telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_last_dialed_number_req(void *telephony_device) +{ + telephony_last_dialed_number_rsp(telephony_device, CME_ERROR_NONE); + + /* Notify outgoing call set-up successfully initiated */ + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + + active_call_status = CALL_STATUS_ALERTING; + active_call_dir = CALL_DIR_OUTGOING; +} + +void telephony_terminate_call_req(void *telephony_device) +{ + g_free(active_call_number); + active_call_number = NULL; + + telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE); + + if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + else + telephony_update_indicator(dummy_indicators, "call", + EV_CALL_INACTIVE); +} + +void telephony_answer_call_req(void *telephony_device) +{ + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); + + telephony_update_indicator(dummy_indicators, "call", EV_CALL_ACTIVE); + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + + active_call_status = CALL_STATUS_ACTIVE; +} + +void telephony_dial_number_req(void *telephony_device, const char *number) +{ + g_free(active_call_number); + active_call_number = g_strdup(number); + + debug("telephony-dummy: dial request to %s", active_call_number); + + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); + + /* Notify outgoing call set-up successfully initiated */ + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + + active_call_status = CALL_STATUS_ALERTING; + active_call_dir = CALL_DIR_OUTGOING; +} + +void telephony_transmit_dtmf_req(void *telephony_device, char tone) +{ + debug("telephony-dummy: transmit dtmf: %c", tone); + telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_subscriber_number_req(void *telephony_device) +{ + debug("telephony-dummy: subscriber number request"); + if (subscriber_number) + telephony_subscriber_number_ind(subscriber_number, + NUMBER_TYPE_TELEPHONY, + SUBSCRIBER_SERVICE_VOICE); + telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_list_current_calls_req(void *telephony_device) +{ + debug("telephony-dummy: list current calls request"); + if (active_call_number) + telephony_list_current_call_ind(1, active_call_dir, + active_call_status, + CALL_MODE_VOICE, + CALL_MULTIPARTY_NO, + active_call_number, + NUMBER_TYPE_TELEPHONY); + telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_operator_selection_req(void *telephony_device) +{ + telephony_operator_selection_ind(OPERATOR_MODE_AUTO, "DummyOperator"); + telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_call_hold_req(void *telephony_device, const char *cmd) +{ + debug("telephony-dymmy: got call hold request %s", cmd); + telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable) +{ + debug("telephony-dummy: got %s NR and EC request", + enable ? "enable" : "disable"); + + telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_key_press_req(void *telephony_device, const char *keys) +{ + debug("telephony-dummy: got key press request for %s", keys); + telephony_key_press_rsp(telephony_device, CME_ERROR_NONE); +} + +/* D-Bus method handlers */ +static DBusMessage *outgoing_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *number; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + debug("telephony-dummy: outgoing call to %s", number); + + g_free(active_call_number); + active_call_number = g_strdup(number); + + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + + active_call_status = CALL_STATUS_ALERTING; + active_call_dir = CALL_DIR_OUTGOING; + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *incoming_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *number; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + debug("telephony-dummy: incoming call to %s", number); + + g_free(active_call_number); + active_call_number = g_strdup(number); + + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_INCOMING); + + active_call_status = CALL_STATUS_INCOMING; + active_call_dir = CALL_DIR_INCOMING; + + telephony_incoming_call_ind(number, NUMBER_TYPE_TELEPHONY); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *cancel_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + debug("telephony-dummy: cancel call"); + + g_free(active_call_number); + active_call_number = NULL; + + if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) { + telephony_update_indicator(dummy_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + telephony_calling_stopped_ind(); + } + + if (telephony_get_indicator(dummy_indicators, "call") > 0) + telephony_update_indicator(dummy_indicators, "call", + EV_CALL_INACTIVE); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *signal_strength(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_uint32_t strength; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &strength, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (strength > 5) + return invalid_args(msg); + + telephony_update_indicator(dummy_indicators, "signal", strength); + + debug("telephony-dummy: signal strength set to %u", strength); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *battery_level(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_uint32_t level; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &level, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (level > 5) + return invalid_args(msg); + + telephony_update_indicator(dummy_indicators, "battchg", level); + + debug("telephony-dummy: battery level set to %u", level); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *roaming_status(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_bool_t roaming; + int val; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &roaming, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + val = roaming ? EV_ROAM_ACTIVE : EV_ROAM_INACTIVE; + + telephony_update_indicator(dummy_indicators, "roam", val); + + debug("telephony-dummy: roaming status set to %d", val); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *registration_status(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_bool_t registration; + int val; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, ®istration, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + val = registration ? EV_SERVICE_PRESENT : EV_SERVICE_NONE; + + telephony_update_indicator(dummy_indicators, "service", val); + + debug("telephony-dummy: registration status set to %d", val); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_subscriber_number(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + const char *number; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + g_free(subscriber_number); + subscriber_number = g_strdup(number); + + debug("telephony-dummy: subscriber number set to %s", number); + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable dummy_methods[] = { + { "OutgoingCall", "s", "", outgoing_call }, + { "IncomingCall", "s", "", incoming_call }, + { "CancelCall", "", "", cancel_call }, + { "SignalStrength", "u", "", signal_strength }, + { "BatteryLevel", "u", "", battery_level }, + { "RoamingStatus", "b", "", roaming_status }, + { "RegistrationStatus", "b", "", registration_status }, + { "SetSubscriberNumber","s", "", set_subscriber_number }, + { } +}; + +static DBusConnection *connection = NULL; + +int telephony_init(void) +{ + uint32_t features = AG_FEATURE_REJECT_A_CALL | + AG_FEATURE_ENHANCED_CALL_STATUS | + AG_FEATURE_EXTENDED_ERROR_RESULT_CODES; + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + + g_dbus_register_interface(connection, "/org/bluez/test", + "org.bluez.TelephonyTest", + dummy_methods, NULL, + NULL, NULL, NULL); + + telephony_ready_ind(features, dummy_indicators, response_and_hold, + chld_str); + + return 0; +} + +void telephony_exit(void) +{ + dbus_connection_unref(connection); + connection = NULL; +} diff --git a/audio/telephony-maemo.c b/audio/telephony-maemo.c new file mode 100644 index 000000000..f302cdf87 --- /dev/null +++ b/audio/telephony-maemo.c @@ -0,0 +1,2061 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2008-2009 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "telephony.h" + +/* libcsnet D-Bus definitions */ +#define NETWORK_BUS_NAME "com.nokia.phone.net" +#define NETWORK_INTERFACE "Phone.Net" +#define NETWORK_PATH "/com/nokia/phone/net" + +/* Mask bits for supported services */ +#define NETWORK_MASK_GPRS_SUPPORT 0x01 +#define NETWORK_MASK_CS_SERVICES 0x02 +#define NETWORK_MASK_EGPRS_SUPPORT 0x04 +#define NETWORK_MASK_HSDPA_AVAIL 0x08 +#define NETWORK_MASK_HSUPA_AVAIL 0x10 + +/* network get cell info: cell type */ +#define NETWORK_UNKNOWN_CELL 0 +#define NETWORK_GSM_CELL 1 +#define NETWORK_WCDMA_CELL 2 + +enum net_registration_status { + NETWORK_REG_STATUS_HOME = 0x00, + NETWORK_REG_STATUS_ROAM, + NETWORK_REG_STATUS_ROAM_BLINK, + NETWORK_REG_STATUS_NOSERV, + NETWORK_REG_STATUS_NOSERV_SEARCHING, + NETWORK_REG_STATUS_NOSERV_NOTSEARCHING, + NETWORK_REG_STATUS_NOSERV_NOSIM, + NETWORK_REG_STATUS_POWER_OFF = 0x08, + NETWORK_REG_STATUS_NSPS, + NETWORK_REG_STATUS_NSPS_NO_COVERAGE, + NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW +}; + +enum network_types { + NETWORK_GSM_HOME_PLMN = 0, + NETWORK_GSM_PREFERRED_PLMN, + NETWORK_GSM_FORBIDDEN_PLMN, + NETWORK_GSM_OTHER_PLMN, + NETWORK_GSM_NO_PLMN_AVAIL +}; + +enum network_alpha_tag_name_type { + NETWORK_HARDCODED_LATIN_OPER_NAME = 0, + NETWORK_HARDCODED_USC2_OPER_NAME, + NETWORK_NITZ_SHORT_OPER_NAME, + NETWORK_NITZ_FULL_OPER_NAME, +}; + +#define TELEPHONY_MAEMO_PATH "/com/nokia/MaemoTelephony" +#define TELEPHONY_MAEMO_INTERFACE "com.nokia.MaemoTelephony" + +#define CALLERID_BASE "/var/lib/bluetooth/maemo-callerid-" +#define ALLOWED_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-allowed" +#define RESTRICTED_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-restricted" +#define NONE_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-none" + +static uint32_t callerid = 0; + +/* CSD CALL plugin D-Bus definitions */ +#define CSD_CALL_BUS_NAME "com.nokia.csd.Call" +#define CSD_CALL_INTERFACE "com.nokia.csd.Call" +#define CSD_CALL_INSTANCE "com.nokia.csd.Call.Instance" +#define CSD_CALL_CONFERENCE "com.nokia.csd.Call.Conference" +#define CSD_CALL_PATH "/com/nokia/csd/call" + +/* Call status values as exported by the CSD CALL plugin */ +#define CSD_CALL_STATUS_IDLE 0 +#define CSD_CALL_STATUS_CREATE 1 +#define CSD_CALL_STATUS_COMING 2 +#define CSD_CALL_STATUS_PROCEEDING 3 +#define CSD_CALL_STATUS_MO_ALERTING 4 +#define CSD_CALL_STATUS_MT_ALERTING 5 +#define CSD_CALL_STATUS_WAITING 6 +#define CSD_CALL_STATUS_ANSWERED 7 +#define CSD_CALL_STATUS_ACTIVE 8 +#define CSD_CALL_STATUS_MO_RELEASE 9 +#define CSD_CALL_STATUS_MT_RELEASE 10 +#define CSD_CALL_STATUS_HOLD_INITIATED 11 +#define CSD_CALL_STATUS_HOLD 12 +#define CSD_CALL_STATUS_RETRIEVE_INITIATED 13 +#define CSD_CALL_STATUS_RECONNECT_PENDING 14 +#define CSD_CALL_STATUS_TERMINATED 15 +#define CSD_CALL_STATUS_SWAP_INITIATED 16 + +#define CALL_FLAG_NONE 0 +#define CALL_FLAG_PRESENTATION_ALLOWED 0x01 +#define CALL_FLAG_PRESENTATION_RESTRICTED 0x02 + +/* SIM Phonebook D-Bus definitions */ +#define SIM_PHONEBOOK_BUS_NAME "com.nokia.phone.SIM" +#define SIM_PHONEBOOK_INTERFACE "Phone.Sim.Phonebook" +#define SIM_PHONEBOOK_PATH "/com/nokia/phone/SIM/phonebook" + +#define PHONEBOOK_INDEX_FIRST_ENTRY 0xFFFF +#define PHONEBOOK_INDEX_NEXT_FREE_LOCATION 0xFFFE + +enum sim_phonebook_type { + SIM_PHONEBOOK_TYPE_ADN = 0x0, + SIM_PHONEBOOK_TYPE_SDN, + SIM_PHONEBOOK_TYPE_FDN, + SIM_PHONEBOOK_TYPE_VMBX, + SIM_PHONEBOOK_TYPE_MBDN, + SIM_PHONEBOOK_TYPE_EN, + SIM_PHONEBOOK_TYPE_MSISDN +}; + +enum sim_phonebook_location_type { + SIM_PHONEBOOK_LOCATION_EXACT = 0x0, + SIM_PHONEBOOK_LOCATION_NEXT +}; + +struct csd_call { + char *object_path; + int status; + gboolean originating; + gboolean emergency; + gboolean on_hold; + gboolean conference; + char *number; + gboolean setup; +}; + +static struct { + uint8_t status; + uint16_t lac; + uint32_t cell_id; + uint32_t operator_code; + uint32_t country_code; + uint8_t network_type; + uint8_t supported_services; + uint16_t signals_bar; + char *operator_name; +} net = { + .status = NETWORK_REG_STATUS_NOSERV, + .lac = 0, + .cell_id = 0, + .operator_code = 0, + .country_code = 0, + .network_type = NETWORK_GSM_NO_PLMN_AVAIL, + .supported_services = 0, + .signals_bar = 0, + .operator_name = NULL, +}; + +static guint csd_watch = 0; + +static DBusConnection *connection = NULL; + +static GSList *calls = NULL; + +/* Reference count for determining the call indicator status */ +static GSList *active_calls = NULL; + +static char *msisdn = NULL; /* Subscriber number */ +static char *vmbx = NULL; /* Voice mailbox number */ + +/* HAL battery namespace key values */ +static int battchg_cur = -1; /* "battery.charge_level.current" */ +static int battchg_last = -1; /* "battery.charge_level.last_full" */ +static int battchg_design = -1; /* "battery.charge_level.design" */ + +static gboolean events_enabled = FALSE; + +/* Supported set of call hold operations */ +static const char *chld_str = "0,1,1x,2,2x,3,4"; + +/* Response and hold state + * -1 = none + * 0 = incoming call is put on hold in the AG + * 1 = held incoming call is accepted in the AG + * 2 = held incoming call is rejected in the AG + */ +static int response_and_hold = -1; + +static char *last_dialed_number = NULL; + +/* Timer for tracking call creation requests */ +static guint create_request_timer = 0; + +static struct indicator maemo_indicators[] = +{ + { "battchg", "0-5", 5, TRUE }, + { "signal", "0-5", 5, TRUE }, + { "service", "0,1", 1, TRUE }, + { "call", "0,1", 0, TRUE }, + { "callsetup", "0-3", 0, TRUE }, + { "callheld", "0-2", 0, FALSE }, + { "roam", "0,1", 0, TRUE }, + { NULL } +}; + +static char *call_status_str[] = { + "IDLE", + "CREATE", + "COMING", + "PROCEEDING", + "MO_ALERTING", + "MT_ALERTING", + "WAITING", + "ANSWERED", + "ACTIVE", + "MO_RELEASE", + "MT_RELEASE", + "HOLD_INITIATED", + "HOLD", + "RETRIEVE_INITIATED", + "RECONNECT_PENDING", + "TERMINATED", + "SWAP_INITIATED", + "???" +}; + +static struct csd_call *find_call(const char *path) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (g_str_equal(call->object_path, path)) + return call; + } + + return NULL; +} + +static struct csd_call *find_non_held_call(void) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == CSD_CALL_STATUS_IDLE) + continue; + + if (call->status != CSD_CALL_STATUS_HOLD) + return call; + } + + return NULL; +} + +static struct csd_call *find_non_idle_call(void) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status != CSD_CALL_STATUS_IDLE) + return call; + } + + return NULL; +} + +static struct csd_call *find_call_with_status(int status) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == status) + return call; + } + + return NULL; +} + +static int release_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Release"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int answer_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Answer"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int split_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Split"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int unhold_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Unhold"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int hold_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Hold"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int swap_calls(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Swap"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int create_conference(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Conference"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int call_transfer(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Transfer"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int number_type(const char *number) +{ + if (number == NULL) + return NUMBER_TYPE_TELEPHONY; + + if (number[0] == '+' || strncmp(number, "00", 2) == 0) + return NUMBER_TYPE_INTERNATIONAL; + + return NUMBER_TYPE_TELEPHONY; +} + +void telephony_device_connected(void *telephony_device) +{ + struct csd_call *coming; + + debug("telephony-maemo: device %p connected", telephony_device); + + coming = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + if (coming) { + if (find_call_with_status(CSD_CALL_STATUS_ACTIVE)) + telephony_call_waiting_ind(coming->number, + number_type(coming->number)); + else + telephony_incoming_call_ind(coming->number, + number_type(coming->number)); + } +} + +void telephony_device_disconnected(void *telephony_device) +{ + debug("telephony-maemo: device %p disconnected", telephony_device); + events_enabled = FALSE; +} + +void telephony_event_reporting_req(void *telephony_device, int ind) +{ + events_enabled = ind == 1 ? TRUE : FALSE; + + telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_response_and_hold_req(void *telephony_device, int rh) +{ + response_and_hold = rh; + + telephony_response_and_hold_ind(response_and_hold); + + telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_last_dialed_number_req(void *telephony_device) +{ + debug("telephony-maemo: last dialed number request"); + + if (last_dialed_number) + telephony_dial_number_req(telephony_device, + last_dialed_number); + else + telephony_last_dialed_number_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); +} + +void telephony_terminate_call_req(void *telephony_device) +{ + struct csd_call *call; + + call = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + if (!call) + call = find_non_idle_call(); + + if (!call) { + error("No active call"); + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + if (release_call(call) < 0) + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_answer_call_req(void *telephony_device) +{ + struct csd_call *call; + + call = find_call_with_status(CSD_CALL_STATUS_COMING); + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_PROCEEDING); + + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_WAITING); + + if (!call) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + if (answer_call(call) < 0) + telephony_answer_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); +} + +static int send_method_call(const char *dest, const char *path, + const char *interface, const char *method, + DBusPendingCallNotifyFunction cb, + void *user_data, int type, ...) +{ + DBusMessage *msg; + DBusPendingCall *call; + va_list args; + + msg = dbus_message_new_method_call(dest, path, interface, method); + if (!msg) { + error("Unable to allocate new D-Bus %s message", method); + return -ENOMEM; + } + + va_start(args, type); + + if (!dbus_message_append_args_valist(msg, type, args)) { + dbus_message_unref(msg); + va_end(args); + return -EIO; + } + + va_end(args); + + if (!cb) { + g_dbus_send_message(connection, msg); + return 0; + } + + if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) { + error("Sending %s failed", method); + dbus_message_unref(msg); + return -EIO; + } + + dbus_pending_call_set_notify(call, cb, user_data, NULL); + dbus_pending_call_unref(call); + dbus_message_unref(msg); + + return 0; +} + +static const char *memory_dial_lookup(int location) +{ + if (location == 1) + return vmbx; + else + return NULL; +} + +void telephony_dial_number_req(void *telephony_device, const char *number) +{ + uint32_t flags = callerid; + int ret; + + debug("telephony-maemo: dial request to %s", number); + + if (strncmp(number, "*31#", 4) == 0) { + number += 4; + flags = CALL_FLAG_PRESENTATION_ALLOWED; + } else if (strncmp(number, "#31#", 4) == 0) { + number += 4; + flags = CALL_FLAG_PRESENTATION_RESTRICTED; + } else if (number[0] == '>') { + const char *location = &number[1]; + + number = memory_dial_lookup(strtol(&number[1], NULL, 0)); + if (!number) { + error("No number at memory location %s", location); + telephony_dial_number_rsp(telephony_device, + CME_ERROR_INVALID_INDEX); + return; + } + } + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "CreateWith", + NULL, NULL, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID); + if (ret < 0) { + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_transmit_dtmf_req(void *telephony_device, char tone) +{ + int ret; + char buf[2] = { tone, '\0' }, *buf_ptr = buf; + + debug("telephony-maemo: transmit dtmf: %s", buf); + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "SendDTMF", + NULL, NULL, + DBUS_TYPE_STRING, &buf_ptr, + DBUS_TYPE_INVALID); + if (ret < 0) { + telephony_transmit_dtmf_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_subscriber_number_req(void *telephony_device) +{ + debug("telephony-maemo: subscriber number request"); + if (msisdn) + telephony_subscriber_number_ind(msisdn, + number_type(msisdn), + SUBSCRIBER_SERVICE_VOICE); + telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE); +} + +static int csd_status_to_hfp(struct csd_call *call) +{ + switch (call->status) { + case CSD_CALL_STATUS_IDLE: + case CSD_CALL_STATUS_MO_RELEASE: + case CSD_CALL_STATUS_MT_RELEASE: + case CSD_CALL_STATUS_TERMINATED: + return -1; + case CSD_CALL_STATUS_CREATE: + return CALL_STATUS_DIALING; + case CSD_CALL_STATUS_WAITING: + return CALL_STATUS_WAITING; + case CSD_CALL_STATUS_PROCEEDING: + /* PROCEEDING can happen in outgoing/incoming */ + if (call->originating) + return CALL_STATUS_DIALING; + else + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_COMING: + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_MO_ALERTING: + return CALL_STATUS_ALERTING; + case CSD_CALL_STATUS_MT_ALERTING: + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_ANSWERED: + case CSD_CALL_STATUS_ACTIVE: + case CSD_CALL_STATUS_RECONNECT_PENDING: + case CSD_CALL_STATUS_SWAP_INITIATED: + case CSD_CALL_STATUS_HOLD_INITIATED: + return CALL_STATUS_ACTIVE; + case CSD_CALL_STATUS_RETRIEVE_INITIATED: + case CSD_CALL_STATUS_HOLD: + return CALL_STATUS_HELD; + default: + return -1; + } +} + +void telephony_list_current_calls_req(void *telephony_device) +{ + GSList *l; + int i; + + debug("telephony-maemo: list current calls request"); + + for (l = calls, i = 1; l != NULL; l = l->next, i++) { + struct csd_call *call = l->data; + int status, direction, multiparty; + + status = csd_status_to_hfp(call); + if (status < 0) + continue; + + direction = call->originating ? + CALL_DIR_OUTGOING : CALL_DIR_INCOMING; + + multiparty = call->conference ? + CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO; + + telephony_list_current_call_ind(i, direction, status, + CALL_MODE_VOICE, multiparty, + call->number, + number_type(call->number)); + } + + telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_operator_selection_req(void *telephony_device) +{ + telephony_operator_selection_ind(OPERATOR_MODE_AUTO, + net.operator_name ? net.operator_name : ""); + telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE); +} + +static void foreach_call_with_status(int status, + int (*func)(struct csd_call *call)) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == status) + func(call); + } +} + +void telephony_call_hold_req(void *telephony_device, const char *cmd) +{ + const char *idx; + struct csd_call *call; + int err = 0; + + debug("telephony-maemo: got call hold request %s", cmd); + + if (strlen(cmd) > 1) + idx = &cmd[1]; + else + idx = NULL; + + if (idx) + call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1); + else + call = NULL; + + switch (cmd[0]) { + case '0': + foreach_call_with_status(CSD_CALL_STATUS_HOLD, release_call); + foreach_call_with_status(CSD_CALL_STATUS_WAITING, + release_call); + break; + case '1': + if (idx) { + if (call) + err = release_call(call); + break; + } + foreach_call_with_status(CSD_CALL_STATUS_ACTIVE, release_call); + call = find_call_with_status(CSD_CALL_STATUS_WAITING); + if (call) + err = answer_call(call); + break; + case '2': + if (idx) { + if (call) + err = split_call(call); + } else { + struct csd_call *held, *wait; + + call = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + held = find_call_with_status(CSD_CALL_STATUS_HOLD); + wait = find_call_with_status(CSD_CALL_STATUS_WAITING); + + if (wait) + err = answer_call(wait); + else if (call && held) + err = swap_calls(); + else { + if (call) + err = hold_call(call); + if (held) + err = unhold_call(held); + } + } + break; + case '3': + if (find_call_with_status(CSD_CALL_STATUS_HOLD) || + find_call_with_status(CSD_CALL_STATUS_WAITING)) + err = create_conference(); + break; + case '4': + err = call_transfer(); + break; + default: + debug("Unknown call hold request"); + break; + } + + if (err) + telephony_call_hold_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable) +{ + debug("telephony-maemo: got %s NR and EC request", + enable ? "enable" : "disable"); + telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_key_press_req(void *telephony_device, const char *keys) +{ + struct csd_call *active, *waiting; + int err; + + debug("telephony-maemo: got key press request for %s", keys); + + waiting = find_call_with_status(CSD_CALL_STATUS_COMING); + if (!waiting) + waiting = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + if (!waiting) + waiting = find_call_with_status(CSD_CALL_STATUS_PROCEEDING); + + active = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + + if (waiting) + err = answer_call(waiting); + else if (active) + err = release_call(active); + else + err = 0; + + if (err < 0) + telephony_key_press_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_key_press_rsp(telephony_device, CME_ERROR_NONE); +} + +static void handle_incoming_call(DBusMessage *msg) +{ + const char *number, *call_path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &call_path, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Call.Coming() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + debug("Incoming call to %s from number %s", call_path, number); + + g_free(call->number); + call->number = g_strdup(number); + + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_INCOMING); + + if (find_call_with_status(CSD_CALL_STATUS_ACTIVE)) + telephony_call_waiting_ind(call->number, + number_type(call->number)); + else + telephony_incoming_call_ind(call->number, + number_type(call->number)); +} + +static void handle_outgoing_call(DBusMessage *msg) +{ + const char *number, *call_path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &call_path, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Call.Created() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + debug("Outgoing call from %s to number %s", call_path, number); + + g_free(call->number); + call->number = g_strdup(number); + + g_free(last_dialed_number); + last_dialed_number = g_strdup(number); + + if (create_request_timer) { + g_source_remove(create_request_timer); + create_request_timer = 0; + } +} + +static gboolean create_timeout(gpointer user_data) +{ + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + create_request_timer = 0; + return FALSE; +} + +static void handle_create_requested(DBusMessage *msg) +{ + debug("Call.CreateRequested()"); + + if (create_request_timer) + g_source_remove(create_request_timer); + + create_request_timer = g_timeout_add_seconds(5, create_timeout, NULL); + + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); +} + +static void handle_call_status(DBusMessage *msg, const char *call_path) +{ + struct csd_call *call; + dbus_uint32_t status, cause_type, cause; + int callheld = telephony_get_indicator(maemo_indicators, "callheld"); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &status, + DBUS_TYPE_UINT32, &cause_type, + DBUS_TYPE_UINT32, &cause, + DBUS_TYPE_INVALID)) { + error("Unexpected paramters in Instance.CallStatus() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + if (status > 16) { + error("Invalid call status %u", status); + return; + } + + debug("Call %s changed from %s to %s", call_path, + call_status_str[call->status], call_status_str[status]); + + if (call->status == (int) status) { + debug("Ignoring CSD Call state change to existing state"); + return; + } + + call->status = (int) status; + + switch (status) { + case CSD_CALL_STATUS_IDLE: + if (call->setup) { + telephony_update_indicator(maemo_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (!call->originating) + telephony_calling_stopped_ind(); + } + + g_free(call->number); + call->number = NULL; + call->originating = FALSE; + call->emergency = FALSE; + call->on_hold = FALSE; + call->conference = FALSE; + call->setup = FALSE; + break; + case CSD_CALL_STATUS_CREATE: + call->originating = TRUE; + call->setup = TRUE; + break; + case CSD_CALL_STATUS_COMING: + call->originating = FALSE; + call->setup = TRUE; + break; + case CSD_CALL_STATUS_PROCEEDING: + break; + case CSD_CALL_STATUS_MO_ALERTING: + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + break; + case CSD_CALL_STATUS_MT_ALERTING: + break; + case CSD_CALL_STATUS_WAITING: + break; + case CSD_CALL_STATUS_ANSWERED: + break; + case CSD_CALL_STATUS_ACTIVE: + if (call->on_hold) { + call->on_hold = FALSE; + if (find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_NONE); + } else { + if (!g_slist_find(active_calls, call)) + active_calls = g_slist_prepend(active_calls, call); + if (g_slist_length(active_calls) == 1) + telephony_update_indicator(maemo_indicators, + "call", + EV_CALL_ACTIVE); + /* Upgrade callheld status if necessary */ + if (callheld == EV_CALLHELD_ON_HOLD) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + telephony_update_indicator(maemo_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (!call->originating) + telephony_calling_stopped_ind(); + call->setup = FALSE; + } + break; + case CSD_CALL_STATUS_MO_RELEASE: + case CSD_CALL_STATUS_MT_RELEASE: + active_calls = g_slist_remove(active_calls, call); + if (g_slist_length(active_calls) == 0) + telephony_update_indicator(maemo_indicators, "call", + EV_CALL_INACTIVE); + break; + case CSD_CALL_STATUS_HOLD_INITIATED: + break; + case CSD_CALL_STATUS_HOLD: + call->on_hold = TRUE; + if (find_non_held_call()) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + break; + case CSD_CALL_STATUS_RETRIEVE_INITIATED: + break; + case CSD_CALL_STATUS_RECONNECT_PENDING: + break; + case CSD_CALL_STATUS_TERMINATED: + if (call->on_hold && + !find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_NONE); + else if (callheld == EV_CALLHELD_MULTIPLE && + find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + break; + case CSD_CALL_STATUS_SWAP_INITIATED: + break; + default: + error("Unknown call status %u", status); + break; + } +} + +static void handle_conference(DBusMessage *msg, gboolean joined) +{ + const char *path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Conference.%s", + dbus_message_get_member(msg)); + return; + } + + call = find_call(path); + if (!call) { + error("Conference signal for unknown call %s", path); + return; + } + + debug("Call %s %s the conference", path, joined ? "joined" : "left"); + + call->conference = joined; +} + +static void get_operator_name_reply(DBusPendingCall *pending_call, + void *user_data) +{ + DBusMessage *reply; + DBusError err; + const char *name; + dbus_int32_t net_err; + + reply = dbus_pending_call_steal_reply(pending_call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("get_operator_name failed: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(reply, &err, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INT32, &net_err, + DBUS_TYPE_INVALID)) { + error("Unexpected get_operator_name reply parameters: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + if (net_err != 0) { + error("get_operator_name failed with code %d", net_err); + goto done; + } + + if (strlen(name) == 0) + goto done; + + g_free(net.operator_name); + net.operator_name = g_strdup(name); + + debug("telephony-maemo: operator name updated: %s", name); + +done: + dbus_message_unref(reply); +} + +static void resolve_operator_name(uint32_t operator, uint32_t country) +{ + uint8_t name_type = NETWORK_HARDCODED_LATIN_OPER_NAME; + + send_method_call(NETWORK_BUS_NAME, NETWORK_PATH, + NETWORK_INTERFACE, "get_operator_name", + get_operator_name_reply, NULL, + DBUS_TYPE_BYTE, &name_type, + DBUS_TYPE_UINT32, &operator, + DBUS_TYPE_UINT32, &country, + DBUS_TYPE_INVALID); +} + +static void update_registration_status(uint8_t status, uint16_t lac, + uint32_t cell_id, + uint32_t operator_code, + uint32_t country_code, + uint8_t network_type, + uint8_t supported_services) +{ + if (net.status != status) { + switch (status) { + case NETWORK_REG_STATUS_HOME: + telephony_update_indicator(maemo_indicators, "roam", + EV_ROAM_INACTIVE); + if (net.status >= NETWORK_REG_STATUS_NOSERV) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_PRESENT); + break; + case NETWORK_REG_STATUS_ROAM: + case NETWORK_REG_STATUS_ROAM_BLINK: + telephony_update_indicator(maemo_indicators, "roam", + EV_ROAM_ACTIVE); + if (net.status >= NETWORK_REG_STATUS_NOSERV) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_PRESENT); + break; + case NETWORK_REG_STATUS_NOSERV: + case NETWORK_REG_STATUS_NOSERV_SEARCHING: + case NETWORK_REG_STATUS_NOSERV_NOTSEARCHING: + case NETWORK_REG_STATUS_NOSERV_NOSIM: + case NETWORK_REG_STATUS_POWER_OFF: + case NETWORK_REG_STATUS_NSPS: + case NETWORK_REG_STATUS_NSPS_NO_COVERAGE: + case NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW: + if (net.status < NETWORK_REG_STATUS_NOSERV) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_NONE); + break; + } + + net.status = status; + } + + net.lac = lac; + net.cell_id = cell_id; + + if (net.operator_code != operator_code || + net.country_code != country_code) { + g_free(net.operator_name); + net.operator_name = NULL; + resolve_operator_name(operator_code, country_code); + net.operator_code = operator_code; + net.country_code = country_code; + } + + net.network_type = network_type; + net.supported_services = supported_services; +} + +static void handle_registration_status_change(DBusMessage *msg) +{ + uint8_t status; + dbus_uint16_t lac, network_type, supported_services; + dbus_uint32_t cell_id, operator_code, country_code; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_BYTE, &status, + DBUS_TYPE_UINT16, &lac, + DBUS_TYPE_UINT32, &cell_id, + DBUS_TYPE_UINT32, &operator_code, + DBUS_TYPE_UINT32, &country_code, + DBUS_TYPE_BYTE, &network_type, + DBUS_TYPE_BYTE, &supported_services, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in registration_status_change"); + return; + } + + update_registration_status(status, lac, cell_id, operator_code, + country_code, network_type, + supported_services); +} + +static void update_signal_strength(uint8_t signals_bar) +{ + int signal; + + if (signals_bar > 100) { + debug("signals_bar greater than expected: %u", signals_bar); + signals_bar = 100; + } + + if (net.signals_bar == signals_bar) + return; + + /* A simple conversion from 0-100 to 0-5 (used by HFP) */ + signal = (signals_bar + 20) / 21; + + telephony_update_indicator(maemo_indicators, "signal", signal); + + net.signals_bar = signals_bar; + + debug("Signal strength updated: %u/100, %d/5", signals_bar, signal); +} + +static void handle_signal_strength_change(DBusMessage *msg) +{ + uint8_t signals_bar, rssi_in_dbm; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_BYTE, &signals_bar, + DBUS_TYPE_BYTE, &rssi_in_dbm, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in signal_strength_change"); + return; + } + + update_signal_strength(signals_bar); +} + +static gboolean iter_get_basic_args(DBusMessageIter *iter, + int first_arg_type, ...) +{ + int type; + va_list ap; + + va_start(ap, first_arg_type); + + for (type = first_arg_type; type != DBUS_TYPE_INVALID; + type = va_arg(ap, int)) { + void *value = va_arg(ap, void *); + int real_type = dbus_message_iter_get_arg_type(iter); + + if (real_type != type) { + error("iter_get_basic_args: expected %c but got %c", + (char) type, (char) real_type); + break; + } + + dbus_message_iter_get_basic(iter, value); + dbus_message_iter_next(iter); + } + + va_end(ap); + + return type == DBUS_TYPE_INVALID ? TRUE : FALSE; +} + +static void hal_battery_level_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + dbus_int32_t level; + int *value = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_INT32, &level, + DBUS_TYPE_INVALID); + + *value = (int) level; + + if (value == &battchg_last) + debug("telephony-maemo: battery.charge_level.last_full is %d", + *value); + else if (value == &battchg_design) + debug("telephony-maemo: battery.charge_level.design is %d", + *value); + else + debug("telephony-maemo: battery.charge_level.current is %d", + *value); + + if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) { + int new, max; + + if (battchg_last > 0) + max = battchg_last; + else + max = battchg_design; + + new = battchg_cur * 5 / max; + + telephony_update_indicator(maemo_indicators, "battchg", new); + } +done: + dbus_message_unref(reply); +} + +static void hal_get_integer(const char *path, const char *key, void *user_data) +{ + send_method_call("org.freedesktop.Hal", path, + "org.freedesktop.Hal.Device", + "GetPropertyInteger", + hal_battery_level_reply, user_data, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_INVALID); +} + +static void handle_hal_property_modified(DBusMessage *msg) +{ + DBusMessageIter iter, array; + dbus_int32_t num_changes; + const char *path; + + path = dbus_message_get_path(msg); + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &num_changes); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) { + DBusMessageIter prop; + const char *name; + dbus_bool_t added, removed; + + dbus_message_iter_recurse(&array, &prop); + + if (!iter_get_basic_args(&prop, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &added, + DBUS_TYPE_BOOLEAN, &removed, + DBUS_TYPE_INVALID)) { + error("Invalid hal PropertyModified parameters"); + break; + } + + if (g_str_equal(name, "battery.charge_level.last_full")) + hal_get_integer(path, name, &battchg_last); + else if (g_str_equal(name, "battery.charge_level.current")) + hal_get_integer(path, name, &battchg_cur); + else if (g_str_equal(name, "battery.charge_level.design")) + hal_get_integer(path, name, &battchg_design); + + dbus_message_iter_next(&array); + } +} + +static DBusHandlerResult signal_filter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path = dbus_message_get_path(msg); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming")) + handle_incoming_call(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created")) + handle_outgoing_call(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, + "CreateRequested")) + handle_create_requested(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INSTANCE, "CallStatus")) + handle_call_status(msg, path); + else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Joined")) + handle_conference(msg, TRUE); + else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Left")) + handle_conference(msg, FALSE); + else if (dbus_message_is_signal(msg, NETWORK_INTERFACE, + "registration_status_change")) + handle_registration_status_change(msg); + else if (dbus_message_is_signal(msg, NETWORK_INTERFACE, + "signal_strength_change")) + handle_signal_strength_change(msg); + else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device", + "PropertyModified")) + handle_hal_property_modified(msg); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static void csd_call_free(struct csd_call *call) +{ + if (!call) + return; + + g_free(call->object_path); + g_free(call->number); + + g_free(call); +} + +static void parse_call_list(DBusMessageIter *iter) +{ + do { + DBusMessageIter call_iter; + struct csd_call *call; + const char *object_path, *number; + dbus_uint32_t status; + dbus_bool_t originating, terminating, emerg, on_hold, conf; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) { + error("Unexpected signature in GetCallInfoAll reply"); + break; + } + + dbus_message_iter_recurse(iter, &call_iter); + + if (!iter_get_basic_args(&call_iter, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_UINT32, &status, + DBUS_TYPE_BOOLEAN, &originating, + DBUS_TYPE_BOOLEAN, &terminating, + DBUS_TYPE_BOOLEAN, &emerg, + DBUS_TYPE_BOOLEAN, &on_hold, + DBUS_TYPE_BOOLEAN, &conf, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Parsing call D-Bus parameters failed"); + break; + } + + call = find_call(object_path); + if (!call) { + call = g_new0(struct csd_call, 1); + call->object_path = g_strdup(object_path); + call->status = (int) status; + calls = g_slist_append(calls, call); + debug("telephony-maemo: new csd call instance at %s", + object_path); + } + + if (call->status == CSD_CALL_STATUS_IDLE) + continue; + + /* CSD gives incorrect call_hold property sometimes */ + if ((call->status != CSD_CALL_STATUS_HOLD && on_hold) || + (call->status == CSD_CALL_STATUS_HOLD && + !on_hold)) { + error("Conflicting call status and on_hold property!"); + on_hold = call->status == CSD_CALL_STATUS_HOLD; + } + + call->originating = originating; + call->on_hold = on_hold; + call->conference = conf; + g_free(call->number); + call->number = g_strdup(number); + + } while (dbus_message_iter_next(iter)); +} + +static void signal_strength_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + uint8_t signals_bar, rssi_in_dbm; + dbus_int32_t net_err; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("Unable to get signal strength: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_BYTE, &signals_bar, + DBUS_TYPE_BYTE, &rssi_in_dbm, + DBUS_TYPE_INT32, &net_err, + DBUS_TYPE_INVALID)) { + error("Unable to parse signal_strength reply: %s, %s", + err.name, err.message); + dbus_error_free(&err); + return; + } + + if (net_err != 0) { + error("get_signal_strength failed with code %d", net_err); + return; + } + + update_signal_strength(signals_bar); + +done: + dbus_message_unref(reply); +} + +static int get_signal_strength(void) +{ + return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH, + NETWORK_INTERFACE, "get_signal_strength", + signal_strength_reply, NULL, + DBUS_TYPE_INVALID); +} + +static void registration_status_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + uint8_t status; + dbus_uint16_t lac, network_type, supported_services; + dbus_uint32_t cell_id, operator_code, country_code; + dbus_int32_t net_err; + uint32_t features = AG_FEATURE_EC_ANDOR_NR | + AG_FEATURE_INBAND_RINGTONE | + AG_FEATURE_REJECT_A_CALL | + AG_FEATURE_ENHANCED_CALL_STATUS | + AG_FEATURE_ENHANCED_CALL_CONTROL | + AG_FEATURE_EXTENDED_ERROR_RESULT_CODES | + AG_FEATURE_THREE_WAY_CALLING; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("Unable to get registration status: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_BYTE, &status, + DBUS_TYPE_UINT16, &lac, + DBUS_TYPE_UINT32, &cell_id, + DBUS_TYPE_UINT32, &operator_code, + DBUS_TYPE_UINT32, &country_code, + DBUS_TYPE_BYTE, &network_type, + DBUS_TYPE_BYTE, &supported_services, + DBUS_TYPE_INT32, &net_err, + DBUS_TYPE_INVALID)) { + error("Unable to parse registration_status_change reply:" + " %s, %s", err.name, err.message); + dbus_error_free(&err); + return; + } + + if (net_err != 0) { + error("get_registration_status failed with code %d", net_err); + return; + } + + update_registration_status(status, lac, cell_id, operator_code, + country_code, network_type, + supported_services); + + telephony_ready_ind(features, maemo_indicators, response_and_hold, + chld_str); + + get_signal_strength(); + +done: + dbus_message_unref(reply); +} + +static int get_registration_status(void) +{ + return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH, + NETWORK_INTERFACE, "get_registration_status", + registration_status_reply, NULL, + DBUS_TYPE_INVALID); +} + +static void call_info_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub;; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("csd replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in GetCallInfoAll return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + parse_call_list(&sub); + + get_registration_status(); + +done: + dbus_message_unref(reply); +} + +static void hal_find_device_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub; + const char *path; + char match_string[256]; + int type; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in GetCallInfoAll return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + type = dbus_message_iter_get_arg_type(&sub); + + if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) { + error("No hal device with battery capability found"); + goto done; + } + + dbus_message_iter_get_basic(&sub, &path); + + debug("telephony-maemo: found battery device at %s", path); + + snprintf(match_string, sizeof(match_string), + "type='signal'," + "path='%s'," + "interface='org.freedesktop.Hal.Device'," + "member='PropertyModified'", path); + dbus_bus_add_match(connection, match_string, NULL); + + hal_get_integer(path, "battery.charge_level.last_full", &battchg_last); + hal_get_integer(path, "battery.charge_level.current", &battchg_cur); + hal_get_integer(path, "battery.charge_level.design", &battchg_design); + +done: + dbus_message_unref(reply); +} + +static void phonebook_read_reply(DBusPendingCall *call, void *user_data) +{ + DBusError derr; + DBusMessage *reply; + const char *name, *number; + char **number_type = user_data; + dbus_int32_t current_location, err; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&derr); + if (dbus_set_error_from_message(&derr, reply)) { + error("SIM.Phonebook replied with an error: %s, %s", + derr.name, derr.message); + dbus_error_free(&derr); + goto done; + } + + dbus_error_init(&derr); + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INT32, ¤t_location, + DBUS_TYPE_INT32, &err, + DBUS_TYPE_INVALID); + + if (dbus_error_is_set(&derr)) { + error("Unable to parse SIM.Phonebook.read arguments: %s, %s", + derr.name, derr.message); + dbus_error_free(&derr); + goto done; + } + + if (err != 0) { + error("SIM.Phonebook.read failed with error %d", err); + if (number_type == &vmbx) + vmbx = g_strdup(getenv("VMBX_NUMBER")); + goto done; + } + + if (number_type == &msisdn) { + g_free(msisdn); + msisdn = g_strdup(number); + debug("Got MSISDN %s (%s)", number, name); + } else { + g_free(vmbx); + vmbx = g_strdup(number); + debug("Got voice mailbox number %s (%s)", number, name); + } + +done: + dbus_message_unref(reply); +} + +static gboolean csd_init(gpointer user_data) +{ + char match_string[128]; + const char *battery_cap = "battery"; + dbus_uint32_t location; + uint8_t pb_type, location_type; + int ret; + + if (!dbus_connection_add_filter(connection, signal_filter, + NULL, NULL)) { + error("Can't add signal filter"); + return FALSE; + } + + snprintf(match_string, sizeof(match_string), + "type=signal,interface=%s", CSD_CALL_INTERFACE); + dbus_bus_add_match(connection, match_string, NULL); + + snprintf(match_string, sizeof(match_string), + "type=signal,interface=%s", CSD_CALL_INSTANCE); + dbus_bus_add_match(connection, match_string, NULL); + + snprintf(match_string, sizeof(match_string), + "type=signal,interface=%s", CSD_CALL_CONFERENCE); + dbus_bus_add_match(connection, match_string, NULL); + + snprintf(match_string, sizeof(match_string), + "type=signal,interface=%s", NETWORK_INTERFACE); + dbus_bus_add_match(connection, match_string, NULL); + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "GetCallInfoAll", + call_info_reply, NULL, DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to sent GetCallInfoAll method call"); + return FALSE; + } + + ret = send_method_call("org.freedesktop.Hal", + "/org/freedesktop/Hal/Manager", + "org.freedesktop.Hal.Manager", + "FindDeviceByCapability", + hal_find_device_reply, NULL, + DBUS_TYPE_STRING, &battery_cap, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send HAL method call"); + return FALSE; + } + + pb_type = SIM_PHONEBOOK_TYPE_MSISDN; + location = PHONEBOOK_INDEX_FIRST_ENTRY; + location_type = SIM_PHONEBOOK_LOCATION_NEXT; + + ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH, + SIM_PHONEBOOK_INTERFACE, "read", + phonebook_read_reply, &msisdn, + DBUS_TYPE_BYTE, &pb_type, + DBUS_TYPE_INT32, &location, + DBUS_TYPE_BYTE, &location_type, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()"); + return FALSE; + } + + pb_type = SIM_PHONEBOOK_TYPE_VMBX; + location = PHONEBOOK_INDEX_FIRST_ENTRY; + location_type = SIM_PHONEBOOK_LOCATION_NEXT; + + ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH, + SIM_PHONEBOOK_INTERFACE, "read", + phonebook_read_reply, &vmbx, + DBUS_TYPE_BYTE, &pb_type, + DBUS_TYPE_INT32, &location, + DBUS_TYPE_BYTE, &location_type, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()"); + return FALSE; + } + + return FALSE; +} + +static void csd_ready(DBusConnection *conn, void *user_data) +{ + g_dbus_remove_watch(conn, csd_watch); + csd_watch = 0; + + g_timeout_add_seconds(2, csd_init, NULL); +} + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg,"org.bluez.Error.InvalidArguments", + "Invalid arguments in method call"); +} + +static uint32_t get_callflag(const char *callerid_setting) +{ + if (callerid_setting != NULL) { + if (g_str_equal(callerid_setting, "allowed")) + return CALL_FLAG_PRESENTATION_ALLOWED; + else if (g_str_equal(callerid_setting, "restricted")) + return CALL_FLAG_PRESENTATION_RESTRICTED; + else + return CALL_FLAG_NONE; + } else + return CALL_FLAG_NONE; +} + +static void generate_flag_file(const char *filename) +{ + int fd; + + if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS) || + g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS) || + g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS)) + return; + + fd = open(filename, O_WRONLY | O_CREAT, 0); + if (fd >= 0) + close(fd); +} + +static void save_callerid_to_file(const char *callerid_setting) +{ + char callerid_file[FILENAME_MAX]; + + snprintf(callerid_file, sizeof(callerid_file), "%s%s", + CALLERID_BASE, callerid_setting); + + if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS)) + rename(ALLOWED_FLAG_FILE, callerid_file); + else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS)) + rename(RESTRICTED_FLAG_FILE, callerid_file); + else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS)) + rename(NONE_FLAG_FILE, callerid_file); + else + generate_flag_file(callerid_file); +} + +static uint32_t callerid_from_file(void) +{ + if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS)) + return CALL_FLAG_PRESENTATION_ALLOWED; + else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS)) + return CALL_FLAG_PRESENTATION_RESTRICTED; + else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS)) + return CALL_FLAG_NONE; + else + return CALL_FLAG_NONE; +} + +static DBusMessage *set_callerid(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *callerid_setting; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, + &callerid_setting, + DBUS_TYPE_INVALID) == FALSE) + return invalid_args(msg); + + if (g_str_equal(callerid_setting, "allowed") || + g_str_equal(callerid_setting, "restricted") || + g_str_equal(callerid_setting, "none")) { + save_callerid_to_file(callerid_setting); + callerid = get_callflag(callerid_setting); + debug("telephony-maemo setting callerid flag: %s", + callerid_setting); + return dbus_message_new_method_return(msg); + } + + error("telephony-maemo: invalid argument %s for method call" + " SetCallerId", callerid_setting); + return invalid_args(msg); +} + +static GDBusMethodTable telephony_maemo_methods[] = { + {"SetCallerId", "s", "", set_callerid, + G_DBUS_METHOD_FLAG_ASYNC}, + { } +}; + +int telephony_init(void) +{ + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + + csd_watch = g_dbus_add_service_watch(connection, CSD_CALL_BUS_NAME, + csd_ready, NULL, NULL, NULL); + + if (dbus_bus_name_has_owner(connection, CSD_CALL_BUS_NAME, NULL)) + csd_ready(connection, NULL); + else + info("CSD not yet available. Waiting for it..."); + + generate_flag_file(NONE_FLAG_FILE); + callerid = callerid_from_file(); + + if (!g_dbus_register_interface(connection, TELEPHONY_MAEMO_PATH, + TELEPHONY_MAEMO_INTERFACE, telephony_maemo_methods, + NULL, NULL, NULL, NULL)) { + error("telephony-maemo interface %s init failed on path %s", + TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH); + } + + debug("telephony-maemo registering %s interface on path %s", + TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH); + + return 0; +} + +void telephony_exit(void) +{ + if (csd_watch) { + g_dbus_remove_watch(connection, csd_watch); + csd_watch = 0; + } + + g_slist_foreach(calls, (GFunc) csd_call_free, NULL); + g_slist_free(calls); + calls = NULL; + + dbus_connection_remove_filter(connection, signal_filter, NULL); + + dbus_connection_unref(connection); + connection = NULL; +} diff --git a/audio/telephony-ofono.c b/audio/telephony-ofono.c new file mode 100644 index 000000000..7c647b60d --- /dev/null +++ b/audio/telephony-ofono.c @@ -0,0 +1,1110 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009 Intel Corporation + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "telephony.h" + +enum net_registration_status { + NETWORK_REG_STATUS_HOME = 0x00, + NETWORK_REG_STATUS_ROAM, + NETWORK_REG_STATUS_NOSERV +}; + +struct voice_call { + char *obj_path; + int status; + gboolean originating; + char *number; +}; + +static DBusConnection *connection = NULL; +static char *modem_obj_path = NULL; +static char *last_dialed_number = NULL; +static GSList *calls = NULL; + +#define OFONO_BUS_NAME "org.ofono" +#define OFONO_PATH "/" +#define OFONO_MANAGER_INTERFACE "org.ofono.Manager" +#define OFONO_NETWORKREG_INTERFACE "org.ofono.NetworkRegistration" +#define OFONO_VCMANAGER_INTERFACE "org.ofono.VoiceCallManager" +#define OFONO_VC_INTERFACE "org.ofono.VoiceCall" + +/* HAL battery namespace key values */ +static int battchg_cur = -1; /* "battery.charge_level.current" */ +static int battchg_last = -1; /* "battery.charge_level.last_full" */ +static int battchg_design = -1; /* "battery.charge_level.design" */ + +static struct { + uint8_t status; + uint32_t signals_bar; + char *operator_name; +} net = { + .status = NETWORK_REG_STATUS_NOSERV, + .signals_bar = 0, + .operator_name = NULL, +}; + +static const char *chld_str = "0,1,1x,2,2x,3,4"; +static char *subscriber_number = NULL; + +static gboolean events_enabled = FALSE; + +/* Response and hold state + * -1 = none + * 0 = incoming call is put on hold in the AG + * 1 = held incoming call is accepted in the AG + * 2 = held incoming call is rejected in the AG + */ +static int response_and_hold = -1; + +static struct indicator ofono_indicators[] = +{ + { "battchg", "0-5", 5, TRUE }, + { "signal", "0-5", 5, TRUE }, + { "service", "0,1", 1, TRUE }, + { "call", "0,1", 0, TRUE }, + { "callsetup", "0-3", 0, TRUE }, + { "callheld", "0-2", 0, FALSE }, + { "roam", "0,1", 0, TRUE }, + { NULL } +}; + +static struct voice_call *find_vc(const char *path) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct voice_call *vc = l->data; + + if (g_str_equal(vc->obj_path, path)) + return vc; + } + + return NULL; +} + +static struct voice_call *find_vc_with_status(int status) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct voice_call *vc = l->data; + + if (vc->status == status) + return vc; + } + + return NULL; +} + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments", + "Invalid arguments in method call"); +} + +void telephony_device_connected(void *telephony_device) +{ + debug("telephony-ofono: device %p connected", telephony_device); +} + +void telephony_device_disconnected(void *telephony_device) +{ + debug("telephony-ofono: device %p disconnected", telephony_device); + events_enabled = FALSE; +} + +void telephony_event_reporting_req(void *telephony_device, int ind) +{ + events_enabled = ind == 1 ? TRUE : FALSE; + + telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_response_and_hold_req(void *telephony_device, int rh) +{ + response_and_hold = rh; + + telephony_response_and_hold_ind(response_and_hold); + + telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_last_dialed_number_req(void *telephony_device) +{ + debug("telephony-ofono: last dialed number request"); + + if (last_dialed_number) + telephony_dial_number_req(telephony_device, last_dialed_number); + else + telephony_last_dialed_number_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); +} + +static int send_method_call(const char *dest, const char *path, + const char *interface, const char *method, + DBusPendingCallNotifyFunction cb, + void *user_data, int type, ...) +{ + DBusMessage *msg; + DBusPendingCall *call; + va_list args; + + msg = dbus_message_new_method_call(dest, path, interface, method); + if (!msg) { + error("Unable to allocate new D-Bus %s message", method); + return -ENOMEM; + } + + va_start(args, type); + + if (!dbus_message_append_args_valist(msg, type, args)) { + dbus_message_unref(msg); + va_end(args); + return -EIO; + } + + va_end(args); + + if (!cb) { + g_dbus_send_message(connection, msg); + return 0; + } + + if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) { + error("Sending %s failed", method); + dbus_message_unref(msg); + return -EIO; + } + + dbus_pending_call_set_notify(call, cb, user_data, NULL); + dbus_pending_call_unref(call); + dbus_message_unref(msg); + + return 0; +} + +void telephony_terminate_call_req(void *telephony_device) +{ + struct voice_call *vc; + int ret; + + if ((vc = find_vc_with_status(CALL_STATUS_ACTIVE))) { + } else if ((vc = find_vc_with_status(CALL_STATUS_DIALING))) { + } else if ((vc = find_vc_with_status(CALL_STATUS_ALERTING))) { + } else if ((vc = find_vc_with_status(CALL_STATUS_INCOMING))) { + } + + if (!vc) { + error("in telephony_terminate_call_req, no active call"); + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + ret = send_method_call(OFONO_BUS_NAME, vc->obj_path, + OFONO_VC_INTERFACE, + "Hangup", NULL, + NULL, DBUS_TYPE_INVALID); + + if (ret < 0) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_answer_call_req(void *telephony_device) +{ + struct voice_call *vc = find_vc_with_status(CALL_STATUS_INCOMING); + int ret; + + if (!vc) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + ret = send_method_call(OFONO_BUS_NAME, vc->obj_path, + OFONO_VC_INTERFACE, + "Answer", NULL, + NULL, DBUS_TYPE_INVALID); + + if (ret < 0) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_dial_number_req(void *telephony_device, const char *number) +{ + char *clir = "default"; + int ret; + + debug("telephony-ofono: dial request to %s", number); + + if (!strncmp(number, "*31#", 4)) { + number += 4; + clir = g_strdup("enabled"); + } else if (!strncmp(number, "#31#", 4)) { + number += 4; + clir = g_strdup("disabled"); + } + + ret = send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "Dial", NULL, NULL, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_STRING, &clir, + DBUS_TYPE_INVALID); + + if (ret < 0) + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_transmit_dtmf_req(void *telephony_device, char tone) +{ + char *tone_string; + int ret; + + debug("telephony-ofono: transmit dtmf: %c", tone); + + tone_string = g_strdup_printf("%c", tone); + ret = send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_VCMANAGER_INTERFACE, + "SendTones", NULL, NULL, + DBUS_TYPE_STRING, &tone_string, + DBUS_TYPE_INVALID); + g_free(tone_string); + + if (ret < 0) + telephony_transmit_dtmf_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_subscriber_number_req(void *telephony_device) +{ + debug("telephony-ofono: subscriber number request"); + + if (subscriber_number) + telephony_subscriber_number_ind(subscriber_number, + NUMBER_TYPE_TELEPHONY, + SUBSCRIBER_SERVICE_VOICE); + telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_list_current_calls_req(void *telephony_device) +{ + GSList *l; + int i; + + debug("telephony-ofono: list current calls request"); + + for (l = calls, i = 1; l != NULL; l = l->next, i++) { + struct voice_call *vc = l->data; + int direction; + + direction = vc->originating ? + CALL_DIR_OUTGOING : CALL_DIR_INCOMING; + + telephony_list_current_call_ind(i, direction, vc->status, + CALL_MODE_VOICE, CALL_MULTIPARTY_NO, + vc->number, NUMBER_TYPE_TELEPHONY); + } + telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_operator_selection_req(void *telephony_device) +{ + debug("telephony-ofono: operator selection request"); + + telephony_operator_selection_ind(OPERATOR_MODE_AUTO, + net.operator_name ? net.operator_name : ""); + telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_call_hold_req(void *telephony_device, const char *cmd) +{ + debug("telephony-ofono: got call hold request %s", cmd); + telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable) +{ + debug("telephony-ofono: got %s NR and EC request", + enable ? "enable" : "disable"); + + telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_key_press_req(void *telephony_device, const char *keys) +{ + debug("telephony-ofono: got key press request for %s", keys); + telephony_key_press_rsp(telephony_device, CME_ERROR_NONE); +} + +static gboolean iter_get_basic_args(DBusMessageIter *iter, + int first_arg_type, ...) +{ + int type; + va_list ap; + + va_start(ap, first_arg_type); + + for (type = first_arg_type; type != DBUS_TYPE_INVALID; + type = va_arg(ap, int)) { + void *value = va_arg(ap, void *); + int real_type = dbus_message_iter_get_arg_type(iter); + + if (real_type != type) { + error("iter_get_basic_args: expected %c but got %c", + (char) type, (char) real_type); + break; + } + + dbus_message_iter_get_basic(iter, value); + dbus_message_iter_next(iter); + } + + va_end(ap); + + return type == DBUS_TYPE_INVALID ? TRUE : FALSE; +} + +static void handle_registration_property(const char *property, DBusMessageIter sub) +{ + char *status, *operator; + unsigned int signals_bar; + + if (g_str_equal(property, "Status")) { + dbus_message_iter_get_basic(&sub, &status); + debug("Status is %s", status); + if (g_str_equal(status, "registered")) { + net.status = NETWORK_REG_STATUS_HOME; + telephony_update_indicator(ofono_indicators, + "roam", EV_ROAM_INACTIVE); + telephony_update_indicator(ofono_indicators, + "service", EV_SERVICE_PRESENT); + } else if (g_str_equal(status, "roaming")) { + net.status = NETWORK_REG_STATUS_ROAM; + telephony_update_indicator(ofono_indicators, + "roam", EV_ROAM_ACTIVE); + telephony_update_indicator(ofono_indicators, + "service", EV_SERVICE_PRESENT); + } else { + net.status = NETWORK_REG_STATUS_NOSERV; + telephony_update_indicator(ofono_indicators, + "roam", EV_ROAM_INACTIVE); + telephony_update_indicator(ofono_indicators, + "service", EV_SERVICE_NONE); + } + } else if (g_str_equal(property, "Operator")) { + dbus_message_iter_get_basic(&sub, &operator); + debug("Operator is %s", operator); + g_free(net.operator_name); + net.operator_name = g_strdup(operator); + } else if (g_str_equal(property, "SignalStrength")) { + dbus_message_iter_get_basic(&sub, &signals_bar); + debug("SignalStrength is %d", signals_bar); + net.signals_bar = signals_bar; + telephony_update_indicator(ofono_indicators, "signal", + (signals_bar + 20) / 21); + } +} + +static void get_registration_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, iter_entry; + uint32_t features = AG_FEATURE_EC_ANDOR_NR | + AG_FEATURE_REJECT_A_CALL | + AG_FEATURE_ENHANCED_CALL_STATUS | + AG_FEATURE_EXTENDED_ERROR_RESULT_CODES; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("ofono replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + /* ARRAY -> ENTRY -> VARIANT*/ + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in GetProperties return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &iter_entry); + + if (dbus_message_iter_get_arg_type(&iter_entry) + != DBUS_TYPE_DICT_ENTRY) { + error("Unexpected signature in GetProperties return"); + goto done; + } + + while (dbus_message_iter_get_arg_type(&iter_entry) + != DBUS_TYPE_INVALID) { + DBusMessageIter iter_property, sub; + char *property; + + dbus_message_iter_recurse(&iter_entry, &iter_property); + if (dbus_message_iter_get_arg_type(&iter_property) + != DBUS_TYPE_STRING) { + error("Unexpected signature in GetProperties return"); + goto done; + } + + dbus_message_iter_get_basic(&iter_property, &property); + + dbus_message_iter_next(&iter_property); + dbus_message_iter_recurse(&iter_property, &sub); + + handle_registration_property(property, sub); + + dbus_message_iter_next(&iter_entry); + } + + telephony_ready_ind(features, ofono_indicators, + response_and_hold, chld_str); + +done: + dbus_message_unref(reply); +} + +static int get_registration_and_signal_status() +{ + return send_method_call(OFONO_BUS_NAME, modem_obj_path, + OFONO_NETWORKREG_INTERFACE, + "GetProperties", get_registration_reply, + NULL, DBUS_TYPE_INVALID); +} + +static void list_modem_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, iter_entry, iter_property, iter_arrary, sub; + char *property, *modem_obj_path_local; + int ret; + + debug("list_modem_reply is called\n"); + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("ofono replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in ListModems return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &iter_entry); + + if (dbus_message_iter_get_arg_type(&iter_entry) + != DBUS_TYPE_DICT_ENTRY) { + error("Unexpected signature in ListModems return 2, %c", + dbus_message_iter_get_arg_type(&iter_entry)); + goto done; + } + + dbus_message_iter_recurse(&iter_entry, &iter_property); + + dbus_message_iter_get_basic(&iter_property, &property); + + dbus_message_iter_next(&iter_property); + dbus_message_iter_recurse(&iter_property, &iter_arrary); + dbus_message_iter_recurse(&iter_arrary, &sub); + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + + dbus_message_iter_get_basic(&sub, &modem_obj_path_local); + modem_obj_path = g_strdup(modem_obj_path_local); + debug("modem_obj_path is %p, %s\n", modem_obj_path, + modem_obj_path); + dbus_message_iter_next(&sub); + } + + ret = get_registration_and_signal_status(); + if (ret < 0) + error("get_registration_and_signal_status() failed(%d)", ret); +done: + dbus_message_unref(reply); +} + +static void handle_networkregistration_property_changed(DBusMessage *msg, + const char *call_path) +{ + DBusMessageIter iter, sub; + const char *property; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + error("Unexpected signature in networkregistration" + " PropertyChanged signal"); + return; + } + dbus_message_iter_get_basic(&iter, &property); + debug("in handle_networkregistration_property_changed()," + " the property is %s", property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &sub); + + handle_registration_property(property, sub); +} + +static void vc_getproperties_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply; + DBusError err; + DBusMessageIter iter, iter_entry; + const char *path = user_data; + struct voice_call *vc; + + debug("in vc_getproperties_reply"); + + reply = dbus_pending_call_steal_reply(call); + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("ofono replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + vc = find_vc(path); + if (!vc) { + error("in vc_getproperties_reply, vc is NULL"); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in vc_getproperties_reply()"); + goto done; + } + + dbus_message_iter_recurse(&iter, &iter_entry); + + if (dbus_message_iter_get_arg_type(&iter_entry) + != DBUS_TYPE_DICT_ENTRY) { + error("Unexpected signature in vc_getproperties_reply()"); + goto done; + } + + while (dbus_message_iter_get_arg_type(&iter_entry) + != DBUS_TYPE_INVALID) { + DBusMessageIter iter_property, sub; + char *property, *cli, *state; + + dbus_message_iter_recurse(&iter_entry, &iter_property); + if (dbus_message_iter_get_arg_type(&iter_property) + != DBUS_TYPE_STRING) { + error("Unexpected signature in" + " vc_getproperties_reply()"); + goto done; + } + + dbus_message_iter_get_basic(&iter_property, &property); + + dbus_message_iter_next(&iter_property); + dbus_message_iter_recurse(&iter_property, &sub); + if (g_str_equal(property, "LineIdentification")) { + dbus_message_iter_get_basic(&sub, &cli); + debug("in vc_getproperties_reply(), cli is %s", cli); + vc->number = g_strdup(cli); + } else if (g_str_equal(property, "State")) { + dbus_message_iter_get_basic(&sub, &state); + debug("in vc_getproperties_reply()," + " state is %s", state); + if (g_str_equal(state, "incoming")) + vc->status = CALL_STATUS_INCOMING; + else if (g_str_equal(state, "dialing")) + vc->status = CALL_STATUS_DIALING; + else if (g_str_equal(state, "alerting")) + vc->status = CALL_STATUS_ALERTING; + else if (g_str_equal(state, "waiting")) + vc->status = CALL_STATUS_WAITING; + } + + dbus_message_iter_next(&iter_entry); + } + + switch (vc->status) { + case CALL_STATUS_INCOMING: + printf("in CALL_STATUS_INCOMING: case\n"); + vc->originating = FALSE; + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_INCOMING); + telephony_incoming_call_ind(vc->number, NUMBER_TYPE_TELEPHONY); + break; + case CALL_STATUS_DIALING: + printf("in CALL_STATUS_DIALING: case\n"); + vc->originating = TRUE; + g_free(last_dialed_number); + last_dialed_number = g_strdup(vc->number); + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); + break; + case CALL_STATUS_ALERTING: + printf("in CALL_STATUS_ALERTING: case\n"); + vc->originating = TRUE; + g_free(last_dialed_number); + last_dialed_number = g_strdup(vc->number); + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + break; + case CALL_STATUS_WAITING: + debug("in CALL_STATUS_WAITING: case"); + vc->originating = FALSE; + telephony_update_indicator(ofono_indicators, "callsetup", + EV_CALLSETUP_INCOMING); + telephony_call_waiting_ind(vc->number, NUMBER_TYPE_TELEPHONY); + break; + } +done: + dbus_message_unref(reply); +} + +static void handle_vcmanager_property_changed(DBusMessage *msg, + const char *obj_path) +{ + DBusMessageIter iter, sub, array; + const char *property, *vc_obj_path = NULL; + struct voice_call *vc = NULL, *vc_new = NULL; + + debug("in handle_vcmanager_property_changed"); + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + error("Unexpected signature in vcmanager" + " PropertyChanged signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &property); + debug("in handle_vcmanager_property_changed()," + " the property is %s", property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &sub); + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in vcmanager" + " PropertyChanged signal"); + return; + } + dbus_message_iter_recurse(&sub, &array); + while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) { + dbus_message_iter_get_basic(&array, &vc_obj_path); + vc = find_vc(vc_obj_path); + if (vc) { + debug("in handle_vcmanager_property_changed," + " found an existing vc"); + } else { + vc_new = g_new0(struct voice_call, 1); + vc_new->obj_path = g_strdup(vc_obj_path); + calls = g_slist_append(calls, vc_new); + } + dbus_message_iter_next(&array); + } + + if (!vc_new) + return; + + send_method_call(OFONO_BUS_NAME, vc_new->obj_path, + OFONO_VC_INTERFACE, + "GetProperties", vc_getproperties_reply, + vc_new->obj_path, DBUS_TYPE_INVALID); +} + +static void vc_free(struct voice_call *vc) +{ + if (!vc) + return; + + g_free(vc->obj_path); + g_free(vc->number); + g_free(vc); +} + +static void handle_vc_property_changed(DBusMessage *msg, const char *obj_path) +{ + DBusMessageIter iter, sub; + const char *property, *state; + struct voice_call *vc = NULL; + + debug("in handle_vc_property_changed, obj_path is %s", obj_path); + + vc = find_vc(obj_path); + + if (!vc) + return; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + error("Unexpected signature in vc PropertyChanged signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &property); + debug("in handle_vc_property_changed(), the property is %s", property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &sub); + if (g_str_equal(property, "State")) { + dbus_message_iter_get_basic(&sub, &state); + debug("in handle_vc_property_changed(), State is %s", state); + if (g_str_equal(state, "disconnected")) { + printf("in disconnected case\n"); + if (vc->status == CALL_STATUS_ACTIVE) + telephony_update_indicator(ofono_indicators, + "call", EV_CALL_INACTIVE); + else + telephony_update_indicator(ofono_indicators, + "callsetup", EV_CALLSETUP_INACTIVE); + if (vc->status == CALL_STATUS_INCOMING) + telephony_calling_stopped_ind(); + calls = g_slist_remove(calls, vc); + vc_free(vc); + } else if (g_str_equal(state, "active")) { + telephony_update_indicator(ofono_indicators, + "call", EV_CALL_ACTIVE); + telephony_update_indicator(ofono_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (vc->status == CALL_STATUS_INCOMING) { + telephony_calling_stopped_ind(); + } + vc->status = CALL_STATUS_ACTIVE; + debug("vc status is CALL_STATUS_ACTIVE"); + } else if (g_str_equal(state, "alerting")) { + telephony_update_indicator(ofono_indicators, + "callsetup", EV_CALLSETUP_ALERTING); + vc->status = CALL_STATUS_ALERTING; + debug("vc status is CALL_STATUS_ALERTING"); + } else if (g_str_equal(state, "incoming")) { + /* state change from waiting to incoming */ + telephony_update_indicator(ofono_indicators, + "callsetup", EV_CALLSETUP_INCOMING); + telephony_incoming_call_ind(vc->number, + NUMBER_TYPE_TELEPHONY); + vc->status = CALL_STATUS_INCOMING; + debug("vc status is CALL_STATUS_INCOMING"); + } + } +} + +static void hal_battery_level_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply; + DBusError err; + dbus_int32_t level; + int *value = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_INT32, &level, + DBUS_TYPE_INVALID); + + *value = (int) level; + + if (value == &battchg_last) + debug("telephony-ofono: battery.charge_level.last_full" + " is %d", *value); + else if (value == &battchg_design) + debug("telephony-ofono: battery.charge_level.design" + " is %d", *value); + else + debug("telephony-ofono: battery.charge_level.current" + " is %d", *value); + + if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) { + int new, max; + + if (battchg_last > 0) + max = battchg_last; + else + max = battchg_design; + + new = battchg_cur * 5 / max; + + telephony_update_indicator(ofono_indicators, "battchg", new); + } +done: + dbus_message_unref(reply); +} + +static void hal_get_integer(const char *path, const char *key, void *user_data) +{ + send_method_call("org.freedesktop.Hal", path, + "org.freedesktop.Hal.Device", + "GetPropertyInteger", + hal_battery_level_reply, user_data, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_INVALID); +} + +static void hal_find_device_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply; + DBusError err; + DBusMessageIter iter, sub; + int type; + const char *path; + char match_string[256]; + + debug("begin of hal_find_device_reply()"); + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in hal_find_device_reply()"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + type = dbus_message_iter_get_arg_type(&sub); + + if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) { + error("No hal device with battery capability found"); + goto done; + } + + dbus_message_iter_get_basic(&sub, &path); + + debug("telephony-ofono: found battery device at %s", path); + + snprintf(match_string, sizeof(match_string), + "type='signal'," + "path='%s'," + "interface='org.freedesktop.Hal.Device'," + "member='PropertyModified'", path); + dbus_bus_add_match(connection, match_string, NULL); + + hal_get_integer(path, "battery.charge_level.last_full", &battchg_last); + hal_get_integer(path, "battery.charge_level.current", &battchg_cur); + hal_get_integer(path, "battery.charge_level.design", &battchg_design); +done: + dbus_message_unref(reply); +} + +static void handle_hal_property_modified(DBusMessage *msg) +{ + const char *path; + DBusMessageIter iter, array; + dbus_int32_t num_changes; + + path = dbus_message_get_path(msg); + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &num_changes); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) { + DBusMessageIter prop; + const char *name; + dbus_bool_t added, removed; + + dbus_message_iter_recurse(&array, &prop); + + if (!iter_get_basic_args(&prop, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &added, + DBUS_TYPE_BOOLEAN, &removed, + DBUS_TYPE_INVALID)) { + error("Invalid hal PropertyModified parameters"); + break; + } + + if (g_str_equal(name, "battery.charge_level.last_full")) + hal_get_integer(path, name, &battchg_last); + else if (g_str_equal(name, "battery.charge_level.current")) + hal_get_integer(path, name, &battchg_cur); + else if (g_str_equal(name, "battery.charge_level.design")) + hal_get_integer(path, name, &battchg_design); + + dbus_message_iter_next(&array); + } +} + +static DBusHandlerResult signal_filter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path = dbus_message_get_path(msg); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_signal(msg, OFONO_NETWORKREG_INTERFACE, + "PropertyChanged")) + handle_networkregistration_property_changed(msg, path); + else if (dbus_message_is_signal(msg, OFONO_VCMANAGER_INTERFACE, + "PropertyChanged")) + handle_vcmanager_property_changed(msg, path); + else if (dbus_message_is_signal(msg, OFONO_VC_INTERFACE, + "PropertyChanged")) + handle_vc_property_changed(msg, path); + else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device", + "PropertyModified")) + handle_hal_property_modified(msg); + + debug("signal_filter is called, path is %s\n", path); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int telephony_init(void) +{ + const char *battery_cap = "battery"; + char match_string[128]; + int ret; + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + + if (!dbus_connection_add_filter(connection, signal_filter, + NULL, NULL)) { + error("telephony-ofono: Can't add signal filter"); + return -EIO; + } + + snprintf(match_string, sizeof(match_string), "type=signal,interface=%s", + OFONO_NETWORKREG_INTERFACE); + dbus_bus_add_match(connection, match_string, NULL); + + snprintf(match_string, sizeof(match_string), "type=signal,interface=%s", + OFONO_VCMANAGER_INTERFACE); + dbus_bus_add_match(connection, match_string, NULL); + + snprintf(match_string, sizeof(match_string), "type=signal,interface=%s", + OFONO_VC_INTERFACE); + dbus_bus_add_match(connection, match_string, NULL); + + ret = send_method_call(OFONO_BUS_NAME, OFONO_PATH, + OFONO_MANAGER_INTERFACE, "GetProperties", + list_modem_reply, NULL, DBUS_TYPE_INVALID); + if (ret < 0) + return ret; + + ret = send_method_call("org.freedesktop.Hal", + "/org/freedesktop/Hal/Manager", + "org.freedesktop.Hal.Manager", + "FindDeviceByCapability", + hal_find_device_reply, NULL, + DBUS_TYPE_STRING, &battery_cap, + DBUS_TYPE_INVALID); + if (ret < 0) + return ret; + + debug("telephony_init() successfully"); + + return ret; +} + +void telephony_exit(void) +{ + g_free(net.operator_name); + + g_free(modem_obj_path); + g_free(last_dialed_number); + + g_slist_foreach(calls, (GFunc) vc_free, NULL); + g_slist_free(calls); + calls = NULL; + + dbus_connection_remove_filter(connection, signal_filter, NULL); + + dbus_connection_unref(connection); + connection = NULL; +} diff --git a/audio/telephony.h b/audio/telephony.h new file mode 100644 index 000000000..9bc5ee2ce --- /dev/null +++ b/audio/telephony.h @@ -0,0 +1,234 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#include +#include +#include + +/* HFP feature bits */ +#define AG_FEATURE_THREE_WAY_CALLING 0x0001 +#define AG_FEATURE_EC_ANDOR_NR 0x0002 +#define AG_FEATURE_VOICE_RECOGNITION 0x0004 +#define AG_FEATURE_INBAND_RINGTONE 0x0008 +#define AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG 0x0010 +#define AG_FEATURE_REJECT_A_CALL 0x0020 +#define AG_FEATURE_ENHANCED_CALL_STATUS 0x0040 +#define AG_FEATURE_ENHANCED_CALL_CONTROL 0x0080 +#define AG_FEATURE_EXTENDED_ERROR_RESULT_CODES 0x0100 + +#define HF_FEATURE_EC_ANDOR_NR 0x0001 +#define HF_FEATURE_CALL_WAITING_AND_3WAY 0x0002 +#define HF_FEATURE_CLI_PRESENTATION 0x0004 +#define HF_FEATURE_VOICE_RECOGNITION 0x0008 +#define HF_FEATURE_REMOTE_VOLUME_CONTROL 0x0010 +#define HF_FEATURE_ENHANCED_CALL_STATUS 0x0020 +#define HF_FEATURE_ENHANCED_CALL_CONTROL 0x0040 + +/* Indicator event values */ +#define EV_SERVICE_NONE 0 +#define EV_SERVICE_PRESENT 1 + +#define EV_CALL_INACTIVE 0 +#define EV_CALL_ACTIVE 1 + +#define EV_CALLSETUP_INACTIVE 0 +#define EV_CALLSETUP_INCOMING 1 +#define EV_CALLSETUP_OUTGOING 2 +#define EV_CALLSETUP_ALERTING 3 + +#define EV_CALLHELD_NONE 0 +#define EV_CALLHELD_MULTIPLE 1 +#define EV_CALLHELD_ON_HOLD 2 + +#define EV_ROAM_INACTIVE 0 +#define EV_ROAM_ACTIVE 1 + +/* Call parameters */ +#define CALL_DIR_OUTGOING 0 +#define CALL_DIR_INCOMING 1 + +#define CALL_STATUS_ACTIVE 0 +#define CALL_STATUS_HELD 1 +#define CALL_STATUS_DIALING 2 +#define CALL_STATUS_ALERTING 3 +#define CALL_STATUS_INCOMING 4 +#define CALL_STATUS_WAITING 5 + +#define CALL_MODE_VOICE 0 +#define CALL_MODE_DATA 1 +#define CALL_MODE_FAX 2 + +#define CALL_MULTIPARTY_NO 0 +#define CALL_MULTIPARTY_YES 1 + +/* Subscriber number parameters */ +#define SUBSCRIBER_SERVICE_VOICE 4 +#define SUBSCRIBER_SERVICE_FAX 5 + +/* Operator selection mode values */ +#define OPERATOR_MODE_AUTO 0 +#define OPERATOR_MODE_MANUAL 1 +#define OPERATOR_MODE_DEREGISTER 2 +#define OPERATOR_MODE_MANUAL_AUTO 4 + +/* Some common number types */ +#define NUMBER_TYPE_UNKNOWN 128 +#define NUMBER_TYPE_TELEPHONY 129 +#define NUMBER_TYPE_INTERNATIONAL 145 +#define NUMBER_TYPE_NATIONAL 161 +#define NUMBER_TYPE_VOIP 255 + +/* Extended Audio Gateway Error Result Codes */ +typedef enum { + CME_ERROR_NONE = -1, + CME_ERROR_AG_FAILURE = 0, + CME_ERROR_NO_PHONE_CONNECTION = 1, + CME_ERROR_NOT_ALLOWED = 3, + CME_ERROR_NOT_SUPPORTED = 4, + CME_ERROR_PH_SIM_PIN_REQUIRED = 5, + CME_ERROR_SIM_NOT_INSERTED = 10, + CME_ERROR_SIM_PIN_REQUIRED = 11, + CME_ERROR_SIM_PUK_REQUIRED = 12, + CME_ERROR_SIM_FAILURE = 13, + CME_ERROR_SIM_BUSY = 14, + CME_ERROR_INCORRECT_PASSWORD = 16, + CME_ERROR_SIM_PIN2_REQUIRED = 17, + CME_ERROR_SIM_PUK2_REQUIRED = 18, + CME_ERROR_MEMORY_FULL = 20, + CME_ERROR_INVALID_INDEX = 21, + CME_ERROR_MEMORY_FAILURE = 23, + CME_ERROR_TEXT_STRING_TOO_LONG = 24, + CME_ERROR_INVALID_TEXT_STRING = 25, + CME_ERROR_DIAL_STRING_TOO_LONG = 26, + CME_ERROR_INVALID_DIAL_STRING = 27, + CME_ERROR_NO_NETWORK_SERVICE = 30, + CME_ERROR_NETWORK_TIMEOUT = 31, + CME_ERROR_NETWORK_NOT_ALLOWED = 32, +} cme_error_t; + +struct indicator { + const char *desc; + const char *range; + int val; + gboolean ignore_redundant; +}; + +/* Notify telephony-*.c of connected/disconnected devices. Implemented by + * telephony-*.c + */ +void telephony_device_connected(void *telephony_device); +void telephony_device_disconnected(void *telephony_device); + +/* HF requests (sent by the handsfree device). These are implemented by + * telephony-*.c + */ +void telephony_event_reporting_req(void *telephony_device, int ind); +void telephony_response_and_hold_req(void *telephony_device, int rh); +void telephony_last_dialed_number_req(void *telephony_device); +void telephony_terminate_call_req(void *telephony_device); +void telephony_answer_call_req(void *telephony_device); +void telephony_dial_number_req(void *telephony_device, const char *number); +void telephony_transmit_dtmf_req(void *telephony_device, char tone); +void telephony_subscriber_number_req(void *telephony_device); +void telephony_list_current_calls_req(void *telephony_device); +void telephony_operator_selection_req(void *telephony_device); +void telephony_call_hold_req(void *telephony_device, const char *cmd); +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable); +void telephony_key_press_req(void *telephony_device, const char *keys); + +/* AG responses to HF requests. These are implemented by headset.c */ +int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err); +int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err); +int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err); +int telephony_terminate_call_rsp(void *telephony_device, cme_error_t err); +int telephony_answer_call_rsp(void *telephony_device, cme_error_t err); +int telephony_dial_number_rsp(void *telephony_device, cme_error_t err); +int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err); +int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err); +int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err); +int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err); +int telephony_call_hold_rsp(void *telephony_device, cme_error_t err); +int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err); +int telephony_key_press_rsp(void *telephony_device, cme_error_t err); + +/* Event indications by AG. These are implemented by headset.c */ +int telephony_event_ind(int index); +int telephony_response_and_hold_ind(int rh); +int telephony_incoming_call_ind(const char *number, int type); +int telephony_calling_stopped_ind(void); +int telephony_ready_ind(uint32_t features, const struct indicator *indicators, + int rh, const char *chld); +int telephony_list_current_call_ind(int idx, int dir, int status, int mode, + int mprty, const char *number, + int type); +int telephony_subscriber_number_ind(const char *number, int type, + int service); +int telephony_call_waiting_ind(const char *number, int type); +int telephony_operator_selection_ind(int mode, const char *oper); + +/* Helper function for quick indicator updates */ +static inline int telephony_update_indicator(struct indicator *indicators, + const char *desc, + int new_val) +{ + int i; + struct indicator *ind = NULL; + + for (i = 0; indicators[i].desc != NULL; i++) { + if (g_str_equal(indicators[i].desc, desc)) { + ind = &indicators[i]; + break; + } + } + + if (!ind) + return -ENOENT; + + debug("Telephony indicator \"%s\" %d->%d", desc, ind->val, new_val); + + if (ind->ignore_redundant && ind->val == new_val) { + debug("Ignoring no-change indication"); + return 0; + } + + ind->val = new_val; + + return telephony_event_ind(i); +} + +static inline int telephony_get_indicator(const struct indicator *indicators, + const char *desc) +{ + int i; + + for (i = 0; indicators[i].desc != NULL; i++) { + if (g_str_equal(indicators[i].desc, desc)) + return indicators[i].val; + } + + return -ENOENT; +} + +int telephony_init(void); +void telephony_exit(void); diff --git a/audio/unix.c b/audio/unix.c new file mode 100644 index 000000000..b04cc59af --- /dev/null +++ b/audio/unix.c @@ -0,0 +1,1772 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "logging.h" +#include "ipc.h" +#include "device.h" +#include "manager.h" +#include "avdtp.h" +#include "a2dp.h" +#include "headset.h" +#include "sink.h" +#include "gateway.h" +#include "unix.h" +#include "glib-helper.h" + +#define check_nul(str) (str[sizeof(str) - 1] == '\0') + +typedef enum { + TYPE_NONE, + TYPE_HEADSET, + TYPE_GATEWAY, + TYPE_SINK, + TYPE_SOURCE +} service_type_t; + +typedef void (*notify_cb_t) (struct audio_device *dev, void *data); + +struct a2dp_data { + struct avdtp *session; + struct avdtp_stream *stream; + struct a2dp_sep *sep; +}; + +struct headset_data { + gboolean locked; +}; + +struct unix_client { + struct audio_device *dev; + GSList *caps; + service_type_t type; + char *interface; + uint8_t seid; + union { + struct a2dp_data a2dp; + struct headset_data hs; + } d; + int sock; + int lock; + int data_fd; /* To be deleted once two phase configuration is fully implemented */ + unsigned int req_id; + unsigned int cb_id; + gboolean (*cancel) (struct audio_device *dev, unsigned int id); +}; + +static GSList *clients = NULL; + +static int unix_sock = -1; + +static void client_free(struct unix_client *client) +{ + debug("client_free(%p)", client); + + if (client->cancel && client->dev && client->req_id > 0) + client->cancel(client->dev, client->req_id); + + if (client->sock >= 0) + close(client->sock); + + if (client->caps) { + g_slist_foreach(client->caps, (GFunc) g_free, NULL); + g_slist_free(client->caps); + } + + g_free(client->interface); + g_free(client); +} + +/* Pass file descriptor through local domain sockets (AF_LOCAL, formerly + * AF_UNIX) and the sendmsg() system call with the cmsg_type field of a "struct + * cmsghdr" set to SCM_RIGHTS and the data being an integer value equal to the + * handle of the file descriptor to be passed. */ +static int unix_sendmsg_fd(int sock, int fd) +{ + char cmsg_b[CMSG_SPACE(sizeof(int))], m = 'm'; + struct cmsghdr *cmsg; + struct iovec iov = { &m, sizeof(m) }; + struct msghdr msgh; + + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = &cmsg_b; + msgh.msg_controllen = CMSG_LEN(sizeof(int)); + + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + /* Initialize the payload */ + memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + + return sendmsg(sock, &msgh, MSG_NOSIGNAL); +} + +static void unix_ipc_sendmsg(struct unix_client *client, + const bt_audio_msg_header_t *msg) +{ + const char *type = bt_audio_strtype(msg->type); + const char *name = bt_audio_strname(msg->name); + + debug("Audio API: %s -> %s", type, name); + + if (send(client->sock, msg, msg->length, 0) < 0) + error("Error %s(%d)", strerror(errno), errno); +} + +static void unix_ipc_error(struct unix_client *client, uint8_t name, int err) +{ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + bt_audio_error_t *rsp = (void *) buf; + + if (!g_slist_find(clients, client)) + return; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_ERROR; + rsp->h.name = name; + rsp->h.length = sizeof(*rsp); + + rsp->posix_errno = err; + + unix_ipc_sendmsg(client, &rsp->h); +} + +static service_type_t select_service(struct audio_device *dev, const char *interface) +{ + if (!interface) { + if (dev->sink && avdtp_is_connected(&dev->src, &dev->dst)) + return TYPE_SINK; + else if (dev->headset && headset_is_active(dev)) + return TYPE_HEADSET; + else if (dev->sink) + return TYPE_SINK; + else if (dev->headset) + return TYPE_HEADSET; + } else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink) + return TYPE_SINK; + else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset) + return TYPE_HEADSET; + else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway) + return TYPE_GATEWAY; + + return TYPE_NONE; +} + +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 unix_client *client = user_data; + struct a2dp_data *a2dp = &client->d.a2dp; + + switch (new_state) { + case AVDTP_STATE_IDLE: + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + if (a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + a2dp->stream = NULL; + client->cb_id = 0; + break; + default: + break; + } +} + +static uint8_t headset_generate_capability(struct audio_device *dev, + codec_capabilities_t *codec) +{ + pcm_capabilities_t *pcm; + + codec->seid = BT_A2DP_SEID_RANGE + 1; + codec->transport = BT_CAPABILITIES_TRANSPORT_SCO; + codec->type = BT_HFP_CODEC_PCM; + codec->length = sizeof(*pcm); + + pcm = (void *) codec; + pcm->sampling_rate = 8000; + if (dev->headset) { + if (headset_get_nrec(dev)) + pcm->flags |= BT_PCM_FLAG_NREC; + if (!headset_get_sco_hci(dev)) + pcm->flags |= BT_PCM_FLAG_PCM_ROUTING; + codec->configured = headset_is_active(dev); + codec->lock = headset_get_lock(dev); + } else { + pcm->flags |= BT_PCM_FLAG_NREC; + codec->configured = TRUE; + codec->lock = 0; + } + + return codec->length; +} + +static void headset_discovery_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_rsp *rsp = (void *) buf; + uint8_t length; + + client->req_id = 0; + + if (!dev) + goto failed; + + memset(buf, 0, sizeof(buf)); + + length = headset_generate_capability(dev, (void *) rsp->data); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_GET_CAPABILITIES; + rsp->h.length = sizeof(*rsp) + length; + + ba2str(&dev->src, rsp->source); + ba2str(&dev->dst, rsp->destination); + strncpy(rsp->object, dev->path, sizeof(rsp->object)); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("discovery failed"); + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); +} + +static void headset_setup_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + + client->req_id = 0; + + if (!dev) + goto failed; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_SET_CONFIGURATION; + rsp->h.length = sizeof(*rsp); + + rsp->link_mtu = 48; + + client->data_fd = headset_get_sco_fd(dev); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("config failed"); + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); +} + +static void gateway_setup_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + + if (!dev) { + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); + return; + } + + client->req_id = 0; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_SET_CONFIGURATION; + rsp->h.length = sizeof(*rsp); + + rsp->link_mtu = 48; + + client->data_fd = gateway_get_sco_fd(dev); + + unix_ipc_sendmsg(client, &rsp->h); +} + +static void headset_resume_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + + client->req_id = 0; + + if (!dev) + goto failed; + + client->data_fd = headset_get_sco_fd(dev); + if (client->data_fd < 0) { + error("Unable to get a SCO fd"); + goto failed; + } + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_START_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.type = BT_INDICATION; + ind->h.name = BT_NEW_STREAM; + ind->h.length = sizeof(*ind); + + unix_ipc_sendmsg(client, &ind->h); + + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + goto failed; + } + + return; + +failed: + error("headset_resume_complete: resume failed"); + unix_ipc_error(client, BT_START_STREAM, EIO); +} + +static void gateway_resume_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_START_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.type = BT_INDICATION; + ind->h.name = BT_NEW_STREAM; + ind->h.length = sizeof(*ind); + + unix_ipc_sendmsg(client, &ind->h); + + client->data_fd = gateway_get_sco_fd(dev); + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + unix_ipc_error(client, BT_START_STREAM, EIO); + } + + client->req_id = 0; +} + +static void headset_suspend_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_stop_stream_rsp *rsp = (void *) buf; + + if (!dev) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_STOP_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("suspend failed"); + unix_ipc_error(client, BT_STOP_STREAM, EIO); +} + +static void print_mpeg12(struct mpeg_codec_cap *mpeg) +{ + debug("Media Codec: MPEG12" + " Channel Modes: %s%s%s%s" + " Frequencies: %s%s%s%s%s%s" + " Layers: %s%s%s" + " CRC: %s", + mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO ? "Mono " : "", + mpeg->channel_mode & MPEG_CHANNEL_MODE_DUAL_CHANNEL ? + "DualChannel " : "", + mpeg->channel_mode & MPEG_CHANNEL_MODE_STEREO ? "Stereo " : "", + mpeg->channel_mode & MPEG_CHANNEL_MODE_JOINT_STEREO ? + "JointStereo " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_16000 ? "16Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_22050 ? "22.05Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_24000 ? "24Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_32000 ? "32Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_44100 ? "44.1Khz " : "", + mpeg->frequency & MPEG_SAMPLING_FREQ_48000 ? "48Khz " : "", + mpeg->layer & MPEG_LAYER_MP1 ? "1 " : "", + mpeg->layer & MPEG_LAYER_MP2 ? "2 " : "", + mpeg->layer & MPEG_LAYER_MP3 ? "3 " : "", + mpeg->crc ? "Yes" : "No"); +} + +static void print_sbc(struct sbc_codec_cap *sbc) +{ + debug("Media Codec: SBC" + " Channel Modes: %s%s%s%s" + " Frequencies: %s%s%s%s" + " Subbands: %s%s" + " Blocks: %s%s%s%s" + " Bitpool: %d-%d", + sbc->channel_mode & SBC_CHANNEL_MODE_MONO ? "Mono " : "", + sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL ? + "DualChannel " : "", + sbc->channel_mode & SBC_CHANNEL_MODE_STEREO ? "Stereo " : "", + sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO ? "JointStereo" : "", + sbc->frequency & SBC_SAMPLING_FREQ_16000 ? "16Khz " : "", + sbc->frequency & SBC_SAMPLING_FREQ_32000 ? "32Khz " : "", + sbc->frequency & SBC_SAMPLING_FREQ_44100 ? "44.1Khz " : "", + sbc->frequency & SBC_SAMPLING_FREQ_48000 ? "48Khz " : "", + sbc->subbands & SBC_SUBBANDS_4 ? "4 " : "", + sbc->subbands & SBC_SUBBANDS_8 ? "8 " : "", + sbc->block_length & SBC_BLOCK_LENGTH_4 ? "4 " : "", + sbc->block_length & SBC_BLOCK_LENGTH_8 ? "8 " : "", + sbc->block_length & SBC_BLOCK_LENGTH_12 ? "12 " : "", + sbc->block_length & SBC_BLOCK_LENGTH_16 ? "16 " : "", + sbc->min_bitpool, sbc->max_bitpool); +} + +static int a2dp_append_codec(struct bt_get_capabilities_rsp *rsp, + struct avdtp_service_capability *cap, + uint8_t seid, + uint8_t configured, + uint8_t lock) +{ + struct avdtp_media_codec_capability *codec_cap = (void *) cap->data; + codec_capabilities_t *codec = (void *) rsp + rsp->h.length; + size_t space_left; + + if (rsp->h.length > BT_SUGGESTED_BUFFER_SIZE) + return -ENOMEM; + + space_left = BT_SUGGESTED_BUFFER_SIZE - rsp->h.length; + + /* endianess prevent direct cast */ + if (codec_cap->media_codec_type == A2DP_CODEC_SBC) { + struct sbc_codec_cap *sbc_cap = (void *) codec_cap; + sbc_capabilities_t *sbc = (void *) codec; + + if (space_left < sizeof(sbc_capabilities_t)) + return -ENOMEM; + + codec->length = sizeof(sbc_capabilities_t); + + sbc->channel_mode = sbc_cap->channel_mode; + sbc->frequency = sbc_cap->frequency; + sbc->allocation_method = sbc_cap->allocation_method; + sbc->subbands = sbc_cap->subbands; + sbc->block_length = sbc_cap->block_length; + sbc->min_bitpool = sbc_cap->min_bitpool; + sbc->max_bitpool = sbc_cap->max_bitpool; + + print_sbc(sbc_cap); + codec->type = BT_A2DP_SBC_SINK; + } else if (codec_cap->media_codec_type == A2DP_CODEC_MPEG12) { + struct mpeg_codec_cap *mpeg_cap = (void *) codec_cap; + mpeg_capabilities_t *mpeg = (void *) codec; + + if (space_left < sizeof(mpeg_capabilities_t)) + return -ENOMEM; + + codec->length = sizeof(mpeg_capabilities_t); + + mpeg->channel_mode = mpeg_cap->channel_mode; + mpeg->crc = mpeg_cap->crc; + mpeg->layer = mpeg_cap->layer; + mpeg->frequency = mpeg_cap->frequency; + mpeg->mpf = mpeg_cap->mpf; + mpeg->bitrate = mpeg_cap->bitrate; + + print_mpeg12(mpeg_cap); + codec->type = BT_A2DP_MPEG12_SINK; + } else { + size_t codec_length, type_length, total_length; + + codec_length = cap->length - (sizeof(struct avdtp_service_capability) + + sizeof(struct avdtp_media_codec_capability)); + type_length = sizeof(codec_cap->media_codec_type); + total_length = type_length + codec_length + + sizeof(codec_capabilities_t); + + if (space_left < total_length) + return -ENOMEM; + + codec->length = total_length; + memcpy(codec->data, &codec_cap->media_codec_type, type_length); + memcpy(codec->data + type_length, codec_cap->data, + codec_length); + codec->type = BT_A2DP_UNKNOWN_SINK; + } + + codec->seid = seid; + codec->configured = configured; + codec->lock = lock; + rsp->h.length += codec->length; + + debug("Append %s seid %d - length %d - total %d", + configured ? "configured" : "", seid, codec->length, + rsp->h.length); + + return 0; +} + +static void a2dp_discovery_complete(struct avdtp *session, GSList *seps, + struct avdtp_error *err, + void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_get_capabilities_rsp *rsp = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + GSList *l; + + if (!g_slist_find(clients, client)) { + debug("Client disconnected during discovery"); + return; + } + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + client->req_id = 0; + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_GET_CAPABILITIES; + rsp->h.length = sizeof(*rsp); + ba2str(&client->dev->src, rsp->source); + ba2str(&client->dev->dst, rsp->destination); + strncpy(rsp->object, client->dev->path, sizeof(rsp->object)); + + for (l = seps; l; l = g_slist_next(l)) { + struct avdtp_remote_sep *rsep = l->data; + struct a2dp_sep *sep; + struct avdtp_service_capability *cap; + struct avdtp_stream *stream; + uint8_t type, seid, configured = 0, lock = 0; + GSList *cl; + + type = avdtp_get_type(rsep); + + if (type != AVDTP_SEP_TYPE_SINK) + continue; + + cap = avdtp_get_codec(rsep); + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + seid = avdtp_get_seid(rsep); + + if (client->seid != 0 && client->seid != seid) + continue; + + stream = avdtp_get_stream(rsep); + if (stream) { + configured = 1; + if (client->seid == seid) + cap = avdtp_stream_get_codec(stream); + } + + for (cl = clients; cl; cl = cl->next) { + struct unix_client *c = cl->data; + struct a2dp_data *ca2dp = &c->d.a2dp; + + if (ca2dp && ca2dp->session == session && + c->seid == seid) { + lock = c->lock; + break; + } + } + + sep = a2dp_get_sep(session, stream); + if (sep && a2dp_sep_get_lock(sep)) + lock = BT_WRITE_LOCK; + + a2dp_append_codec(rsp, cap, seid, configured, lock); + } + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("discovery failed"); + unix_ipc_error(client, BT_GET_CAPABILITIES, EIO); + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + + avdtp_unref(a2dp->session); + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_config_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + uint16_t imtu, omtu; + GSList *caps; + + client->req_id = 0; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + + if (!stream) + goto failed; + + if (client->cb_id > 0) + avdtp_stream_remove_cb(a2dp->session, a2dp->stream, + client->cb_id); + + a2dp->sep = sep; + a2dp->stream = stream; + + if (!avdtp_stream_get_transport(stream, &client->data_fd, &imtu, &omtu, + &caps)) { + error("Unable to get stream transport"); + goto failed; + } + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_SET_CONFIGURATION; + rsp->h.length = sizeof(*rsp); + + /* FIXME: Use imtu when fd_opt is CFG_FD_OPT_READ */ + rsp->link_mtu = omtu; + + unix_ipc_sendmsg(client, &rsp->h); + + client->cb_id = avdtp_stream_add_cb(session, stream, + stream_state_changed, client); + + return; + +failed: + error("config failed"); + + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); + + avdtp_unref(a2dp->session); + + a2dp->session = NULL; + a2dp->stream = NULL; + a2dp->sep = NULL; +} + +static void a2dp_resume_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_START_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.type = BT_RESPONSE; + ind->h.name = BT_NEW_STREAM; + rsp->h.length = sizeof(*ind); + + unix_ipc_sendmsg(client, &ind->h); + + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + goto failed; + } + + return; + +failed: + error("resume failed"); + + unix_ipc_error(client, BT_START_STREAM, EIO); + + if (client->cb_id > 0) { + avdtp_stream_remove_cb(a2dp->session, a2dp->stream, + client->cb_id); + client->cb_id = 0; + } + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + + avdtp_unref(a2dp->session); + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_suspend_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_stop_stream_rsp *rsp = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_STOP_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("suspend failed"); + + unix_ipc_error(client, BT_STOP_STREAM, EIO); + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + + avdtp_unref(a2dp->session); + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void start_discovery(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + int err = 0; + + switch (client->type) { + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + err = avdtp_discover(a2dp->session, a2dp_discovery_complete, + client); + if (err) { + if (a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + goto failed; + } + break; + + case TYPE_HEADSET: + case TYPE_GATEWAY: + headset_discovery_complete(dev, client); + break; + + default: + error("No known services for device"); + goto failed; + } + + client->dev = dev; + + return; + +failed: + unix_ipc_error(client, BT_GET_CAPABILITIES, err ? : EIO); +} + +static void open_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_open_rsp *rsp = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_OPEN; + rsp->h.length = sizeof(*rsp); + + ba2str(&dev->src, rsp->source); + ba2str(&dev->dst, rsp->destination); + strncpy(rsp->object, dev->path, sizeof(rsp->object)); + + unix_ipc_sendmsg(client, &rsp->h); + + return; +} + +static void start_open(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + struct headset_data *hs; + struct avdtp_remote_sep *rsep; + gboolean unref_avdtp_on_fail = FALSE; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->session) { + a2dp->session = avdtp_get(&dev->src, &dev->dst); + unref_avdtp_on_fail = TRUE; + } + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (a2dp->sep) { + error("Client already has an opened session"); + goto failed; + } + + rsep = avdtp_get_remote_sep(a2dp->session, client->seid); + if (!rsep) { + error("Invalid seid %d", client->seid); + goto failed; + } + + a2dp->sep = a2dp_get(a2dp->session, rsep); + if (!a2dp->sep) { + error("seid %d not available or locked", client->seid); + goto failed; + } + + if (!a2dp_sep_lock(a2dp->sep, a2dp->session)) { + error("Unable to open seid %d", client->seid); + a2dp->sep = NULL; + goto failed; + } + + break; + + case TYPE_HEADSET: + hs = &client->d.hs; + + if (hs->locked) { + error("Client already has an opened session"); + goto failed; + } + + hs->locked = headset_lock(dev, client->lock); + if (!hs->locked) { + error("Unable to open seid %d", client->seid); + goto failed; + } + break; + + case TYPE_GATEWAY: + break; + default: + error("No known services for device"); + goto failed; + } + + client->dev = dev; + + open_complete(dev, client); + + return; + +failed: + if (unref_avdtp_on_fail && a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + unix_ipc_error(client, BT_OPEN, EINVAL); +} + +static void start_config(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + struct headset_data *hs; + unsigned int id; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (!a2dp->sep) { + error("seid %d not opened", client->seid); + goto failed; + } + + id = a2dp_config(a2dp->session, a2dp->sep, a2dp_config_complete, + client->caps, client); + client->cancel = a2dp_cancel; + break; + + case TYPE_HEADSET: + hs = &client->d.hs; + + if (!hs->locked) { + error("seid %d not opened", client->seid); + goto failed; + } + + id = headset_config_stream(dev, TRUE, headset_setup_complete, + client); + client->cancel = headset_cancel_stream; + break; + case TYPE_GATEWAY: + if (gateway_config_stream(dev, gateway_setup_complete, client) >= 0) { + client->cancel = gateway_cancel_stream; + id = 1; + } else + id = 0; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("config failed"); + goto failed; + } + + client->req_id = id; + + return; + +failed: + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); +} + +static void start_resume(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + struct headset_data *hs; + unsigned int id; + gboolean unref_avdtp_on_fail = FALSE; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->session) { + a2dp->session = avdtp_get(&dev->src, &dev->dst); + unref_avdtp_on_fail = TRUE; + } + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (!a2dp->sep) { + error("seid not opened"); + goto failed; + } + + id = a2dp_resume(a2dp->session, a2dp->sep, a2dp_resume_complete, + client); + client->cancel = a2dp_cancel; + + break; + + case TYPE_HEADSET: + hs = &client->d.hs; + + if (!hs->locked) { + error("seid not opened"); + goto failed; + } + + id = headset_request_stream(dev, headset_resume_complete, + client); + client->cancel = headset_cancel_stream; + break; + + case TYPE_GATEWAY: + if (gateway_request_stream(dev, gateway_resume_complete, client)) + id = 1; + else + id = 0; + client->cancel = gateway_cancel_stream; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("start_resume: resume failed"); + goto failed; + } + + client->req_id = id; + + return; + +failed: + if (unref_avdtp_on_fail && a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + unix_ipc_error(client, BT_START_STREAM, EIO); +} + +static void start_suspend(struct audio_device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + struct headset_data *hs; + unsigned int id; + gboolean unref_avdtp_on_fail = FALSE; + + switch (client->type) { + case TYPE_SINK: + case TYPE_SOURCE: + a2dp = &client->d.a2dp; + + if (!a2dp->session) { + a2dp->session = avdtp_get(&dev->src, &dev->dst); + unref_avdtp_on_fail = TRUE; + } + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (!a2dp->sep) { + error("Unable to get a sep"); + goto failed; + } + + id = a2dp_suspend(a2dp->session, a2dp->sep, + a2dp_suspend_complete, client); + client->cancel = a2dp_cancel; + break; + + case TYPE_HEADSET: + hs = &client->d.hs; + + if (!hs->locked) { + error("seid not opened"); + goto failed; + } + + id = headset_suspend_stream(dev, headset_suspend_complete, + client); + client->cancel = headset_cancel_stream; + break; + + case TYPE_GATEWAY: + gateway_suspend_stream(dev); + client->cancel = gateway_cancel_stream; + headset_suspend_complete(dev, client); + id = 1; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("suspend failed"); + goto failed; + } + + return; + +failed: + if (unref_avdtp_on_fail && a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + unix_ipc_error(client, BT_STOP_STREAM, EIO); +} + +static void close_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_close_rsp *rsp = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_CLOSE; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + return; +} + +static void start_close(struct audio_device *dev, struct unix_client *client, + gboolean reply) +{ + struct a2dp_data *a2dp; + struct headset_data *hs; + + if (!client->dev) + goto failed; + + switch (client->type) { + case TYPE_HEADSET: + hs = &client->d.hs; + + if (client->dev && hs->locked) { + headset_unlock(client->dev, client->lock); + hs->locked = FALSE; + } + break; + case TYPE_GATEWAY: + break; + case TYPE_SOURCE: + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (client->cb_id > 0) + avdtp_stream_remove_cb(a2dp->session, a2dp->stream, + client->cb_id); + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + if (a2dp->session) { + avdtp_unref(a2dp->session); + a2dp->session = NULL; + } + break; + default: + error("No known services for device"); + goto failed; + } + + if (!reply) + return; + + close_complete(dev, client); + client->dev = NULL; + + return; + +failed: + if (reply) + unix_ipc_error(client, BT_STOP_STREAM, EINVAL); +} + +static void handle_getcapabilities_req(struct unix_client *client, + struct bt_get_capabilities_req *req) +{ + struct audio_device *dev; + bdaddr_t src, dst; + int err = EIO; + + if (!check_nul(req->source) || !check_nul(req->destination) || + !check_nul(req->object)) { + err = EINVAL; + goto failed; + } + + str2ba(req->source, &src); + str2ba(req->destination, &dst); + + if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO) + client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); + else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + client->interface = g_strdup(AUDIO_SINK_INTERFACE); + + if (!manager_find_device(req->object, &src, &dst, NULL, FALSE)) + goto failed; + + dev = manager_find_device(req->object, &src, &dst, client->interface, + TRUE); + if (!dev && (req->flags & BT_FLAG_AUTOCONNECT)) + dev = manager_find_device(req->object, &src, &dst, + client->interface, FALSE); + + if (!dev && req->transport == BT_CAPABILITIES_TRANSPORT_SCO) { + g_free(client->interface); + client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE); + + dev = manager_find_device(req->object, &src, &dst, + client->interface, TRUE); + if (!dev && (req->flags & BT_FLAG_AUTOCONNECT)) + dev = manager_find_device(req->object, &src, &dst, + client->interface, FALSE); + } + + if (!dev) { + error("Unable to find a matching device"); + goto failed; + } + + client->type = select_service(dev, client->interface); + if (client->type == TYPE_NONE) { + error("No matching service found"); + goto failed; + } + + client->seid = req->seid; + + start_discovery(dev, client); + + return; + +failed: + unix_ipc_error(client, BT_GET_CAPABILITIES, err); +} + +static int handle_sco_open(struct unix_client *client, struct bt_open_req *req) +{ + if (!client->interface) + client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); + else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) && + !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE)) + return -EIO; + + debug("open sco - object=%s source=%s destination=%s lock=%s%s", + strcmp(req->object, "") ? req->object : "ANY", + strcmp(req->source, "") ? req->source : "ANY", + strcmp(req->destination, "") ? req->destination : "ANY", + req->lock & BT_READ_LOCK ? "read" : "", + req->lock & BT_WRITE_LOCK ? "write" : ""); + + return 0; +} + +static int handle_a2dp_open(struct unix_client *client, struct bt_open_req *req) +{ + if (!client->interface) + client->interface = g_strdup(AUDIO_SINK_INTERFACE); + else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE)) + return -EIO; + + debug("open a2dp - object=%s source=%s destination=%s lock=%s%s", + strcmp(req->object, "") ? req->object : "ANY", + strcmp(req->source, "") ? req->source : "ANY", + strcmp(req->destination, "") ? req->destination : "ANY", + req->lock & BT_READ_LOCK ? "read" : "", + req->lock & BT_WRITE_LOCK ? "write" : ""); + + return 0; +} + +static void handle_open_req(struct unix_client *client, struct bt_open_req *req) +{ + struct audio_device *dev; + bdaddr_t src, dst; + int err = 0; + + if (!check_nul(req->source) || !check_nul(req->destination) || + !check_nul(req->object)) { + err = EINVAL; + goto failed; + } + + str2ba(req->source, &src); + str2ba(req->destination, &dst); + + if (req->seid > BT_A2DP_SEID_RANGE) { + err = handle_sco_open(client, req); + if (err < 0) { + err = -err; + goto failed; + } + } else { + err = handle_a2dp_open(client, req); + if (err < 0) { + err = -err; + goto failed; + } + } + + if (!manager_find_device(req->object, &src, &dst, NULL, FALSE)) + goto failed; + + dev = manager_find_device(req->object, &src, &dst, client->interface, + TRUE); + if (!dev) + dev = manager_find_device(req->object, &src, &dst, + client->interface, FALSE); + + if (!dev) + goto failed; + + client->seid = req->seid; + client->lock = req->lock; + + start_open(dev, client); + + return; + +failed: + unix_ipc_error(client, BT_OPEN, err ? : EIO); +} + +static int handle_sco_transport(struct unix_client *client, + struct bt_set_configuration_req *req) +{ + struct audio_device *dev = client->dev; + + if (!client->interface) { + if (dev->headset) + client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); + else if (dev->gateway) + client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE); + else + return -EIO; + } else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) && + !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE)) + return -EIO; + + return 0; +} + +static int handle_a2dp_transport(struct unix_client *client, + struct bt_set_configuration_req *req) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + struct mpeg_codec_cap mpeg_cap; + + if (!client->interface) + client->interface = g_strdup(AUDIO_SINK_INTERFACE); + else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE)) + return -EIO; + + if (client->caps) { + g_slist_foreach(client->caps, (GFunc) g_free, NULL); + g_slist_free(client->caps); + client->caps = NULL; + } + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + client->caps = g_slist_append(client->caps, media_transport); + + if (req->codec.type == BT_A2DP_MPEG12_SINK) { + mpeg_capabilities_t *mpeg = (void *) &req->codec; + + memset(&mpeg_cap, 0, sizeof(mpeg_cap)); + + mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12; + mpeg_cap.channel_mode = mpeg->channel_mode; + mpeg_cap.crc = mpeg->crc; + mpeg_cap.layer = mpeg->layer; + mpeg_cap.frequency = mpeg->frequency; + mpeg_cap.mpf = mpeg->mpf; + mpeg_cap.bitrate = mpeg->bitrate; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap, + sizeof(mpeg_cap)); + + print_mpeg12(&mpeg_cap); + } else if (req->codec.type == BT_A2DP_SBC_SINK) { + sbc_capabilities_t *sbc = (void *) &req->codec; + + memset(&sbc_cap, 0, sizeof(sbc_cap)); + + sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC; + sbc_cap.channel_mode = sbc->channel_mode; + sbc_cap.frequency = sbc->frequency; + sbc_cap.allocation_method = sbc->allocation_method; + sbc_cap.subbands = sbc->subbands; + sbc_cap.block_length = sbc->block_length; + sbc_cap.min_bitpool = sbc->min_bitpool; + sbc_cap.max_bitpool = sbc->max_bitpool; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + print_sbc(&sbc_cap); + } else + return -EINVAL; + + client->caps = g_slist_append(client->caps, media_codec); + + return 0; +} + +static void handle_setconfiguration_req(struct unix_client *client, + struct bt_set_configuration_req *req) +{ + int err = 0; + + if (req->codec.seid != client->seid) { + error("Unable to set configuration: seid %d not opened", + client->seid); + goto failed; + } + + if (!client->dev) + goto failed; + + if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_SCO) { + err = handle_sco_transport(client, req); + if (err < 0) { + err = -err; + goto failed; + } + } else if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + err = handle_a2dp_transport(client, req); + if (err < 0) { + err = -err; + goto failed; + } + } + + start_config(client->dev, client); + + return; + +failed: + unix_ipc_error(client, BT_SET_CONFIGURATION, err ? : EIO); +} + +static void handle_streamstart_req(struct unix_client *client, + struct bt_start_stream_req *req) +{ + if (!client->dev) + goto failed; + + start_resume(client->dev, client); + + return; + +failed: + unix_ipc_error(client, BT_START_STREAM, EIO); +} + +static void handle_streamstop_req(struct unix_client *client, + struct bt_stop_stream_req *req) +{ + if (!client->dev) + goto failed; + + start_suspend(client->dev, client); + + return; + +failed: + unix_ipc_error(client, BT_STOP_STREAM, EIO); +} + +static void handle_close_req(struct unix_client *client, + struct bt_close_req *req) +{ + if (!client->dev) + goto failed; + + start_close(client->dev, client, TRUE); + + return; + +failed: + unix_ipc_error(client, BT_CLOSE, EIO); +} + +static void handle_control_req(struct unix_client *client, + struct bt_control_req *req) +{ + /* FIXME: really implement that */ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_CONTROL; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); +} + +static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + char buf[BT_SUGGESTED_BUFFER_SIZE]; + bt_audio_msg_header_t *msghdr = (void *) buf; + struct unix_client *client = data; + int len; + const char *type, *name; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + debug("Unix client disconnected (fd=%d)", client->sock); + + goto failed; + } + + memset(buf, 0, sizeof(buf)); + + len = recv(client->sock, buf, sizeof(buf), 0); + if (len < 0) { + error("recv: %s (%d)", strerror(errno), errno); + goto failed; + } + + type = bt_audio_strtype(msghdr->type); + name = bt_audio_strname(msghdr->name); + + debug("Audio API: %s <- %s", type, name); + + if (msghdr->length != len) { + error("Invalid message: length mismatch"); + goto failed; + } + + switch (msghdr->name) { + case BT_GET_CAPABILITIES: + handle_getcapabilities_req(client, + (struct bt_get_capabilities_req *) msghdr); + break; + case BT_OPEN: + handle_open_req(client, + (struct bt_open_req *) msghdr); + break; + case BT_SET_CONFIGURATION: + handle_setconfiguration_req(client, + (struct bt_set_configuration_req *) msghdr); + break; + case BT_START_STREAM: + handle_streamstart_req(client, + (struct bt_start_stream_req *) msghdr); + break; + case BT_STOP_STREAM: + handle_streamstop_req(client, + (struct bt_stop_stream_req *) msghdr); + break; + case BT_CLOSE: + handle_close_req(client, + (struct bt_close_req *) msghdr); + break; + case BT_CONTROL: + handle_control_req(client, + (struct bt_control_req *) msghdr); + break; + default: + error("Audio API: received unexpected message name %d", + msghdr->name); + } + + return TRUE; + +failed: + clients = g_slist_remove(clients, client); + start_close(client->dev, client, FALSE); + client_free(client); + return FALSE; +} + +static gboolean server_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct sockaddr_un addr; + socklen_t addrlen; + int sk, cli_sk; + struct unix_client *client; + GIOChannel *io; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + g_io_channel_close(chan); + return FALSE; + } + + sk = g_io_channel_unix_get_fd(chan); + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + cli_sk = accept(sk, (struct sockaddr *) &addr, &addrlen); + if (cli_sk < 0) { + error("accept: %s (%d)", strerror(errno), errno); + return TRUE; + } + + debug("Accepted new client connection on unix socket (fd=%d)", cli_sk); + set_nonblocking(cli_sk); + + client = g_new0(struct unix_client, 1); + client->sock = cli_sk; + clients = g_slist_append(clients, client); + + io = g_io_channel_unix_new(cli_sk); + g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + client_cb, client); + g_io_channel_unref(io); + + return TRUE; +} + +void unix_device_removed(struct audio_device *dev) +{ + GSList *l; + + debug("unix_device_removed(%p)", dev); + + l = clients; + while (l) { + struct unix_client *client = l->data; + + l = l->next; + + if (client->dev == dev) { + clients = g_slist_remove(clients, client); + start_close(client->dev, client, FALSE); + client_free(client); + } + } +} + +int unix_init(void) +{ + GIOChannel *io; + struct sockaddr_un addr = { + AF_UNIX, BT_IPC_SOCKET_NAME + }; + + int sk, err; + + sk = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sk < 0) { + err = errno; + error("Can't create unix socket: %s (%d)", strerror(err), err); + return -err; + } + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + error("Can't bind unix socket: %s (%d)", strerror(errno), + errno); + close(sk); + return -1; + } + + set_nonblocking(sk); + + if (listen(sk, 1) < 0) { + error("Can't listen on unix socket: %s (%d)", + strerror(errno), errno); + close(sk); + return -1; + } + + unix_sock = sk; + + io = g_io_channel_unix_new(sk); + g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + server_cb, NULL); + g_io_channel_unref(io); + + debug("Unix socket created: %d", sk); + + return 0; +} + +void unix_exit(void) +{ + g_slist_foreach(clients, (GFunc) client_free, NULL); + g_slist_free(clients); + if (unix_sock >= 0) { + close(unix_sock); + unix_sock = -1; + } +} diff --git a/audio/unix.h b/audio/unix.h new file mode 100644 index 000000000..12cf3efe7 --- /dev/null +++ b/audio/unix.h @@ -0,0 +1,28 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +void unix_device_removed(struct audio_device *dev); + +int unix_init(void); +void unix_exit(void); diff --git a/bluez.m4 b/bluez.m4 new file mode 100644 index 000000000..0257a3f6e --- /dev/null +++ b/bluez.m4 @@ -0,0 +1,40 @@ +AC_DEFUN([AM_PATH_BLUEZ], [ + if (test "${prefix}" = "NONE"); then + bluez_prefix=${ac_default_prefix} + else + bluez_prefix=${prefix} + fi + + AC_ARG_WITH(bluez, AC_HELP_STRING([--with-bluez=DIR], [BlueZ library is installed in DIR]), [ + if (test "${withval}" != "yes"); then + bluez_prefix=${withval} + fi + ]) + + ac_save_CPPFLAGS=$CPPFLAGS + ac_save_LDFLAGS=$LDFLAGS + + BLUEZ_CFLAGS="" + test -d "${bluez_prefix}/include" && BLUEZ_CFLAGS="$BLUEZ_CFLAGS -I${bluez_prefix}/include" + + CPPFLAGS="$CPPFLAGS $BLUEZ_CFLAGS" + AC_CHECK_HEADER(bluetooth/bluetooth.h,, AC_MSG_ERROR(Bluetooth header files not found)) + + BLUEZ_LIBS="" + if (test "${ac_default_prefix}" = "${bluez_prefix}"); then + test -d "${libdir}" && BLUEZ_LIBS="$BLUEZ_LIBS -L${libdir}" + else + test -d "${bluez_prefix}/lib64" && BLUEZ_LIBS="$BLUEZ_LIBS -L${bluez_prefix}/lib64" + test -d "${bluez_prefix}/lib" && BLUEZ_LIBS="$BLUEZ_LIBS -L${bluez_prefix}/lib" + fi + + LDFLAGS="$LDFLAGS $BLUEZ_LIBS" + AC_CHECK_LIB(bluetooth, hci_open_dev, BLUEZ_LIBS="$BLUEZ_LIBS -lbluetooth", AC_MSG_ERROR(Bluetooth library not found)) + AC_CHECK_LIB(bluetooth, sdp_connect,, AC_CHECK_LIB(sdp, sdp_connect, BLUEZ_LIBS="$BLUEZ_LIBS -lsdp")) + + CPPFLAGS=$ac_save_CPPFLAGS + LDFLAGS=$ac_save_LDFLAGS + + AC_SUBST(BLUEZ_CFLAGS) + AC_SUBST(BLUEZ_LIBS) +]) diff --git a/bluez.pc.in b/bluez.pc.in new file mode 100644 index 000000000..3d6e59616 --- /dev/null +++ b/bluez.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: BlueZ +Description: Bluetooth protocol stack for Linux +Version: @VERSION@ +Libs: -L${libdir} -lbluetooth +Cflags: -I${includedir} diff --git a/bootstrap b/bootstrap new file mode 100755 index 000000000..91756f94d --- /dev/null +++ b/bootstrap @@ -0,0 +1,7 @@ +#!/bin/sh + +aclocal && \ + autoheader && \ + libtoolize --automake --copy --force && \ + automake --add-missing --copy && \ + autoconf diff --git a/bootstrap-configure b/bootstrap-configure new file mode 100755 index 000000000..15b3ba7dc --- /dev/null +++ b/bootstrap-configure @@ -0,0 +1,32 @@ +#!/bin/sh + +if [ -f config.status ]; then + make maintainer-clean +fi + +if [ ! -f doc/gtk-doc.make ]; then + gtkdocize --copy --docdir doc +fi + +./bootstrap && \ + ./configure --enable-maintainer-mode \ + --enable-debug \ + --prefix=/usr \ + --mandir=/usr/share/man \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --libexecdir=/lib \ + --enable-netlink \ + --enable-tools \ + --enable-bccmd \ + --enable-dfutool \ + --enable-hid2hci \ + --enable-hidd \ + --enable-pand \ + --enable-dund \ + --enable-test \ + --enable-cups \ + --disable-pcmcia \ + --disable-manpages \ + --disable-udevrules \ + --disable-configfiles $* diff --git a/client/Makefile.am b/client/Makefile.am new file mode 100644 index 000000000..02742923c --- /dev/null +++ b/client/Makefile.am @@ -0,0 +1,2 @@ + +MAINTAINERCLEANFILES = Makefile.in diff --git a/common/Android.mk b/common/Android.mk new file mode 100755 index 000000000..74c0beca1 --- /dev/null +++ b/common/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + btio.c \ + glib-helper.c \ + logging.c \ + oui.c \ + sdp-xml.c \ + textfile.c \ + test_textfile.c \ + android_bluez.c + +LOCAL_CFLAGS+= \ + -O3 \ + -DNEED_DBUS_WATCH_GET_UNIX_FD + +ifeq ($(BOARD_HAVE_BLUETOOTH_BCM),true) +LOCAL_CFLAGS += \ + -DBOARD_HAVE_BLUETOOTH_BCM +endif + +LOCAL_C_INCLUDES:= \ + $(LOCAL_PATH)/../include \ + $(call include-path-for, glib) \ + $(call include-path-for, glib)/glib \ + $(call include-path-for, dbus) + +LOCAL_MODULE:=libbluez-common-static + +LOCAL_STATIC_LIBRARY:= \ + libglib_static + +include $(BUILD_STATIC_LIBRARY) diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 000000000..8fe154772 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,15 @@ + +noinst_LIBRARIES = libhelper.a + +libhelper_a_SOURCES = oui.h oui.c textfile.h textfile.c logging.h logging.c \ + glib-helper.h glib-helper.c sdp-xml.h sdp-xml.c btio.h btio.c + +noinst_PROGRAMS = test_textfile + +test_textfile_LDADD = libhelper.a + +AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@ + +EXTRA_DIST = ppoll.h uinput.h + +MAINTAINERCLEANFILES = Makefile.in diff --git a/common/android_bluez.c b/common/android_bluez.c new file mode 100644 index 000000000..69b2ea3e9 --- /dev/null +++ b/common/android_bluez.c @@ -0,0 +1,174 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2009 The Android Open Source Project + * + * 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 + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +/* Set UID to bluetooth w/ CAP_NET_RAW, CAP_NET_ADMIN and CAP_NET_BIND_SERVICE + * (Android's init.rc does not yet support applying linux capabilities) */ +void android_set_aid_and_cap() { + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + setuid(AID_BLUETOOTH); + + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + header.version = _LINUX_CAPABILITY_VERSION; + header.pid = 0; + cap.effective = cap.permitted = 1 << CAP_NET_RAW | + 1 << CAP_NET_ADMIN | + 1 << CAP_NET_BIND_SERVICE; + cap.inheritable = 0; + capset(&header, &cap); +} + +#ifdef BOARD_HAVE_BLUETOOTH_BCM +static int vendor_high_priority(int fd, uint16_t handle) { + unsigned char hci_sleep_cmd[] = { + 0x01, // HCI command packet + 0x57, 0xfc, // HCI_Write_High_Priority_Connection + 0x02, // Length + 0x00, 0x00 // Handle + }; + + hci_sleep_cmd[4] = (uint8_t)handle; + hci_sleep_cmd[5] = (uint8_t)(handle >> 8); + + int ret = write(fd, hci_sleep_cmd, sizeof(hci_sleep_cmd)); + if (ret < 0) { + error("write(): %s (%d)]", strerror(errno), errno); + return -1; + } else if (ret != sizeof(hci_sleep_cmd)) { + error("write(): unexpected length %d", ret); + return -1; + } + return 0; +} + +static int get_hci_sock() { + int sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + struct sockaddr_hci addr; + int opt; + + if(sock < 0) { + error("Can't create raw HCI socket!"); + return -1; + } + + opt = 1; + if (setsockopt(sock, SOL_HCI, HCI_DATA_DIR, &opt, sizeof(opt)) < 0) { + error("Error setting data direction\n"); + return -1; + } + + /* Bind socket to the HCI device */ + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = 0; // hci0 + if(bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + error("Can't attach to device hci0. %s(%d)\n", + strerror(errno), + errno); + return -1; + } + return sock; +} + +static int get_acl_handle(int fd, bdaddr_t *bdaddr) { + int i; + int ret = -1; + struct hci_conn_list_req *conn_list; + struct hci_conn_info *conn_info; + int max_conn = 10; + + conn_list = malloc(max_conn * ( + sizeof(struct hci_conn_list_req) + sizeof(struct hci_conn_info))); + if (!conn_list) { + error("Out of memory in %s\n", __FUNCTION__); + return -1; + } + + conn_list->dev_id = 0; /* hardcoded to HCI device 0 */ + conn_list->conn_num = max_conn; + + if (ioctl(fd, HCIGETCONNLIST, (void *)conn_list)) { + error("Failed to get connection list\n"); + goto out; + } + + for (i=0; i < conn_list->conn_num; i++) { + conn_info = &conn_list->conn_info[i]; + if (conn_info->type == ACL_LINK && + !memcmp((void *)&conn_info->bdaddr, (void *)bdaddr, + sizeof(bdaddr_t))) { + ret = conn_info->handle; + goto out; + } + } + ret = 0; + +out: + free(conn_list); + return ret; +} + +/* Request that the ACL link to a given Bluetooth connection be high priority, + * for improved coexistance support + */ +int android_set_high_priority(bdaddr_t *ba) { + int ret; + int fd = get_hci_sock(); + int acl_handle; + + if (fd < 0) + return fd; + + acl_handle = get_acl_handle(fd, ba); + if (acl_handle < 0) { + ret = acl_handle; + goto out; + } + + ret = vendor_high_priority(fd, acl_handle); + +out: + close(fd); + + return ret; +} + +#else + +int android_set_high_priority(bdaddr_t *ba) { + return 0; +} + +#endif diff --git a/common/btio.c b/common/btio.c new file mode 100644 index 000000000..d7e575d12 --- /dev/null +++ b/common/btio.c @@ -0,0 +1,1274 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009 Marcel Holtmann + * Copyright (C) 2009 Nokia Corporation + * + * + * 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 + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "logging.h" +#include "btio.h" + +#define ERROR_FAILED(gerr, str, err) \ + g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_FAILED, \ + str ": %s (%d)", strerror(err), err) + +#define DEFAULT_DEFER_TIMEOUT 30 + +struct set_opts { + bdaddr_t src; + bdaddr_t dst; + int defer; + int sec_level; + uint8_t channel; + uint16_t psm; + uint16_t mtu; + uint16_t imtu; + uint16_t omtu; + int master; +}; + +struct connect { + BtIOConnect connect; + gpointer user_data; + GDestroyNotify destroy; +}; + +struct accept { + BtIOConnect connect; + gpointer user_data; + GDestroyNotify destroy; +}; + +struct server { + BtIOConnect connect; + BtIOConfirm confirm; + gpointer user_data; + GDestroyNotify destroy; +}; + +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 *err = 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)) + g_set_error(&err, BT_IO_ERROR, BT_IO_ERROR_DISCONNECTED, + "HUP or ERR on socket"); + + accept->connect(io, err, accept->user_data); + + g_clear_error(&err); + + return FALSE; +} + +static gboolean connect_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct connect *conn = user_data; + GError *gerr = NULL; + + /* If the user aborted this connect attempt */ + if ((cond & G_IO_NVAL) || check_nval(io)) + return FALSE; + + if (cond & G_IO_OUT) { + int err = 0, sock = g_io_channel_unix_get_fd(io); + socklen_t len = sizeof(err); + + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0) + err = errno; + + if (err) + g_set_error(&gerr, BT_IO_ERROR, + BT_IO_ERROR_CONNECT_FAILED, "%s (%d)", + strerror(err), err); + } else if (cond & (G_IO_HUP | G_IO_ERR)) + g_set_error(&gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED, + "HUP or ERR on socket"); + + conn->connect(io, gerr, conn->user_data); + + if (gerr) + g_error_free(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); + + cli_sock = accept(srv_sock, NULL, NULL); + if (cli_sock < 0) { + error("accept: %s (%d)", strerror(errno), errno); + 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_DEFAULT, cond, server_cb, server, + (GDestroyNotify) server_remove); +} + +static void connect_add(GIOChannel *io, BtIOConnect connect, + 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; + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, 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_DEFAULT, cond, accept_cb, accept, + (GDestroyNotify) accept_remove); +} + +static int l2cap_bind(int sock, const bdaddr_t *src, uint16_t psm) +{ + struct sockaddr_l2 addr; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + addr.l2_psm = htobs(psm); + + return bind(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +static int l2cap_connect(int sock, const bdaddr_t *dst, uint16_t psm) +{ + int err; + struct sockaddr_l2 addr; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(psm); + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return err; + + return 0; +} + +static int l2cap_set_master(int sock, int master) +{ + int flags; + socklen_t len; + + len = sizeof(flags); + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0) + return -errno; + + if (master) { + 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_master(int sock, int master) +{ + int flags; + socklen_t len; + + len = sizeof(flags); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, &len) < 0) + return -errno; + + if (master) { + 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_HIGH) { + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "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 (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 gboolean l2cap_set(int sock, int sec_level, uint16_t imtu, + uint16_t omtu, int master, GError **err) +{ + if (imtu || omtu) { + 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) + l2o.imtu = imtu; + if (omtu) + l2o.omtu = omtu; + + if (setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, + sizeof(l2o)) < 0) { + ERROR_FAILED(err, "setsockopt(L2CAP_OPTIONS)", errno); + return FALSE; + } + } + + if (master >= 0 && l2cap_set_master(sock, master) < 0) { + ERROR_FAILED(err, "l2cap_set_master", 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) +{ + struct sockaddr_rc addr; + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + addr.rc_channel = channel; + + return bind(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +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 err; + + return 0; +} + +static gboolean rfcomm_set(int sock, int sec_level, int master, GError **err) +{ + if (sec_level && !set_sec_level(sock, BT_IO_RFCOMM, sec_level, err)) + return FALSE; + + if (master >= 0 && rfcomm_set_master(sock, master) < 0) { + ERROR_FAILED(err, "rfcomm_set_master", errno); + return FALSE; + } + + return TRUE; +} + +static int sco_bind(int sock, const bdaddr_t *src) +{ + struct sockaddr_sco addr; + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, src); + + return bind(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +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 err; + + return 0; +} + +static gboolean sco_set(int sock, uint16_t mtu, GError **err) +{ + struct sco_options sco_opt; + socklen_t len; + + if (!mtu) + return TRUE; + + 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; + } + + 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->defer = DEFAULT_DEFER_TIMEOUT; + opts->master = -1; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + str = va_arg(args, const char *); + if (strncasecmp(str, "hci", 3) == 0) + hci_devba(atoi(str + 3), &opts->src); + else + 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_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_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->channel = va_arg(args, int); + break; + case BT_IO_OPT_PSM: + opts->psm = 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_MASTER: + opts->master = va_arg(args, gboolean); + break; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean get_peers(int sock, struct sockaddr *src, struct sockaddr *dst, + 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; + } + + 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 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; + socklen_t len; + + len = sizeof(l2o); + memset(&l2o, 0, len); + if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) { + ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno); + return FALSE; + } + + if (!get_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, sizeof(src), err)) + return FALSE; + + 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: + ba2str(&dst.l2_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &dst.l2_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_L2CAP, + va_arg(args, int *), err)) + return FALSE; + break; + case BT_IO_OPT_PSM: + *(va_arg(args, uint16_t *)) = src.l2_psm ? + src.l2_psm : dst.l2_psm; + break; + case BT_IO_OPT_OMTU: + *(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_MASTER: + 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; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "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; + int flags; + socklen_t len; + uint8_t dev_class[3]; + uint16_t handle; + + if (!get_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, 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: + ba2str(&dst.rc_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + 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: + *(va_arg(args, uint8_t *)) = src.rc_channel ? + src.rc_channel : dst.rc_channel; + break; + case BT_IO_OPT_MASTER: + 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; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "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; + + 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_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, sizeof(src), 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, "RFCOMM_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, "RFCOMM_CONNINFO", errno); + return FALSE; + } + memcpy(va_arg(args, uint8_t *), dev_class, 3); + break; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "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_L2RAW: + 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); + } + + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "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)) { + int ret; + ret = read(sock, &c, 1); + } + + accept_add(io, connect, user_data, destroy); + + return TRUE; +} + +gboolean bt_io_set(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, ...) +{ + va_list args; + gboolean ret; + struct set_opts opts; + int sock; + + va_start(args, opt1); + ret = parse_set_opts(&opts, err, opt1, args); + va_end(args); + + if (!ret) + return ret; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2RAW: + case BT_IO_L2CAP: + return l2cap_set(sock, opts.sec_level, opts.imtu, opts.omtu, + opts.master, err); + case BT_IO_RFCOMM: + return rfcomm_set(sock, opts.sec_level, opts.master, err); + case BT_IO_SCO: + return sco_set(sock, opts.mtu, err); + } + + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown BtIO type %d", type); + return FALSE; +} + +gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, ...) +{ + va_list args; + gboolean ret; + + va_start(args, opt1); + ret = get_valist(io, type, err, opt1, args); + va_end(args); + + return ret; +} + +static GIOChannel *create_io(BtIOType type, gboolean server, + struct set_opts *opts, GError **err) +{ + int sock; + GIOChannel *io; + + switch (type) { + case BT_IO_L2RAW: + sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); + if (sock < 0) { + ERROR_FAILED(err, "socket(RAW, L2CAP)", errno); + return NULL; + } + if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0) < 0) { + ERROR_FAILED(err, "l2cap_bind", errno); + return NULL; + } + if (!l2cap_set(sock, opts->sec_level, 0, 0, -1, err)) + return NULL; + break; + 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, server ? opts->psm : 0) < 0) { + ERROR_FAILED(err, "l2cap_bind", errno); + return NULL; + } + if (!l2cap_set(sock, opts->sec_level, opts->imtu, opts->omtu, + opts->master, err)) + return NULL; + 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) < 0) { + ERROR_FAILED(err, "rfcomm_bind", errno); + return NULL; + } + if (!rfcomm_set(sock, opts->sec_level, opts->master, err)) + return NULL; + 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) < 0) { + ERROR_FAILED(err, "sco_bind", errno); + return NULL; + } + if (!sco_set(sock, opts->mtu, err)) + return NULL; + break; + default: + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown BtIO type %d", 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; +} + +GIOChannel *bt_io_connect(BtIOType type, 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; + + va_start(args, opt1); + ret = parse_set_opts(&opts, gerr, opt1, args); + va_end(args); + + if (ret == FALSE) + return NULL; + + io = create_io(type, FALSE, &opts, gerr); + if (io == NULL) + return NULL; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2RAW: + err = l2cap_connect(sock, &opts.dst, 0); + break; + case BT_IO_L2CAP: + err = l2cap_connect(sock, &opts.dst, opts.psm); + 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; + default: + g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Unknown BtIO type %d", type); + return NULL; + } + + if (err < 0) { + g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED, + "connect: %s (%d)", strerror(-err), -err); + g_io_channel_unref(io); + return NULL; + } + + connect_add(io, connect, user_data, destroy); + + return io; +} + +GIOChannel *bt_io_listen(BtIOType type, 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; + + if (type == BT_IO_L2RAW) { + g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS, + "Server L2CAP RAW sockets not supported"); + return NULL; + } + + va_start(args, opt1); + ret = parse_set_opts(&opts, err, opt1, args); + va_end(args); + + if (ret == FALSE) + return NULL; + + io = create_io(type, TRUE, &opts, err); + if (io == NULL) + return NULL; + + sock = g_io_channel_unix_get_fd(io); + + if (confirm) + setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer, + sizeof(opts.defer)); + + 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"); +} + diff --git a/common/btio.h b/common/btio.h new file mode 100644 index 000000000..2e50d98bb --- /dev/null +++ b/common/btio.h @@ -0,0 +1,94 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009 Marcel Holtmann + * Copyright (C) 2009 Nokia Corporation + * + * + * 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 + * + */ +#ifndef BT_IO_H +#define BT_IO_H + +#include + +typedef enum { + BT_IO_ERROR_DISCONNECTED, + BT_IO_ERROR_CONNECT_FAILED, + BT_IO_ERROR_FAILED, + BT_IO_ERROR_INVALID_ARGS, +} BtIOError; + +#define BT_IO_ERROR bt_io_error_quark() + +GQuark bt_io_error_quark(void); + +typedef enum { + BT_IO_L2RAW, + BT_IO_L2CAP, + BT_IO_RFCOMM, + BT_IO_SCO, +} BtIOType; + +typedef enum { + BT_IO_OPT_INVALID = 0, + BT_IO_OPT_SOURCE, + BT_IO_OPT_SOURCE_BDADDR, + BT_IO_OPT_DEST, + BT_IO_OPT_DEST_BDADDR, + BT_IO_OPT_DEFER_TIMEOUT, + BT_IO_OPT_SEC_LEVEL, + BT_IO_OPT_CHANNEL, + BT_IO_OPT_PSM, + BT_IO_OPT_MTU, + BT_IO_OPT_OMTU, + BT_IO_OPT_IMTU, + BT_IO_OPT_MASTER, + BT_IO_OPT_HANDLE, + BT_IO_OPT_CLASS, +} BtIOOption; + +typedef enum { + BT_IO_SEC_SDP = 0, + BT_IO_SEC_LOW, + BT_IO_SEC_MEDIUM, + BT_IO_SEC_HIGH, +} BtIOSecLevel; + +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_set(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, ...); + +gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, ...); + +GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect, + gpointer user_data, GDestroyNotify destroy, + GError **err, BtIOOption opt1, ...); + +GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect, + BtIOConfirm confirm, gpointer user_data, + GDestroyNotify destroy, GError **err, + BtIOOption opt1, ...); + +#endif diff --git a/common/glib-helper.c b/common/glib-helper.c new file mode 100644 index 000000000..727c55da2 --- /dev/null +++ b/common/glib-helper.c @@ -0,0 +1,784 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "glib-helper.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; + guint timer; +}; + +static GSList *cached_sdp_sessions = NULL; + +struct hci_cmd_data { + bt_hci_result_t cb; + uint16_t handle; + uint16_t ocf; + gpointer caller_data; +}; + +static gboolean cached_session_expired(gpointer user_data) +{ + struct cached_sdp_session *cached = user_data; + + cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached); + + sdp_close(cached->session); + + g_free(cached); + + return FALSE; +} + +static sdp_session_t *get_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; + + g_source_remove(c->timer); + + session = c->session; + + cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c); + g_free(c); + + return session; + } + + return sdp_connect(src, dst, SDP_NON_BLOCKING); +} + +static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst, + sdp_session_t *session) +{ + struct cached_sdp_session *cached; + + 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 = g_timeout_add_seconds(CACHE_TIMEOUT, + cached_session_expired, + cached); +} + +int set_nonblocking(int fd) +{ + long arg; + + arg = fcntl(fd, F_GETFL); + if (arg < 0) + return -errno; + + /* Return if already nonblocking */ + if (arg & O_NONBLOCK) + return 0; + + arg |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, arg) < 0) + return -errno; + + return 0; +} + +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; +}; + +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; + + 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, void *user_data) +{ + struct search_context *ctxt = user_data; + int err = 0; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + err = EIO; + goto failed; + } + + if (sdp_process(ctxt->session) < 0) + goto failed; + + return TRUE; + +failed: + if (err) { + sdp_close(ctxt->session); + ctxt->session = NULL; + + if (ctxt->cb) + ctxt->cb(NULL, err, ctxt->user_data); + + search_context_cleanup(ctxt); + } + + return FALSE; +} + +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 = 0; + + sk = g_io_channel_unix_get_fd(chan); + ctxt->io_id = 0; + + len = sizeof(err); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + err = errno; + goto failed; + } + + 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) +{ + sdp_session_t *s; + GIOChannel *chan; + + if (!ctxt) + return -EINVAL; + + s = get_sdp_session(src, dst); + 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; + + chan = g_io_channel_unix_new(sdp_get_socket(s)); + (*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; +} + +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) +{ + struct search_context *ctxt = NULL; + int err; + + if (!cb) + return -EINVAL; + + err = create_search_context(&ctxt, src, dst, uuid); + if (err < 0) + return err; + + ctxt->cb = cb; + ctxt->destroy = destroy; + ctxt->user_data = user_data; + + context_list = g_slist_append(context_list, ctxt); + + return 0; +} + +int bt_discover_services(const bdaddr_t *src, const bdaddr_t *dst, + bt_callback_t cb, void *user_data, bt_destroy_t destroy) +{ + uuid_t uuid; + + sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); + + return bt_search_service(src, dst, &uuid, cb, user_data, destroy); +} + +static int find_by_bdaddr(const void *data, const void *user_data) +{ + const struct search_context *ctxt = data, *search = user_data; + + return (bacmp(&ctxt->dst, &search->dst) && + bacmp(&ctxt->src, &search->src)); +} + +int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct search_context search, *ctxt; + GSList *match; + + memset(&search, 0, sizeof(search)); + bacpy(&search.src, src); + bacpy(&search.dst, dst); + + /* Ongoing SDP Discovery */ + match = g_slist_find_custom(context_list, &search, find_by_bdaddr); + if (!match) + return -ENODATA; + + ctxt = match->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; +} + +char *bt_uuid2string(uuid_t *uuid) +{ + gchar *str; + uuid_t uuid128; + unsigned int data0; + unsigned short data1; + unsigned short data2; + unsigned short data3; + unsigned int data4; + unsigned short data5; + + 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); + + str = g_try_malloc0(MAX_LEN_UUID_STR); + if (!str) + return NULL; + + sprintf(str, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + g_ntohl(data0), g_ntohs(data1), + g_ntohs(data2), g_ntohs(data3), + g_ntohl(data4), g_ntohs(data5)); + + return str; +} + +static struct { + const char *name; + uint16_t class; +} bt_services[] = { + { "vcp", VIDEO_CONF_SVCLASS_ID }, + { "pbap", PBAP_SVCLASS_ID }, + { "sap", SAP_SVCLASS_ID }, + { "ftp", OBEX_FILETRANS_SVCLASS_ID }, + { "bpp", BASIC_PRINTING_SVCLASS_ID }, + { "bip", IMAGING_SVCLASS_ID }, + { "synch", IRMC_SYNC_SVCLASS_ID }, + { "dun", DIALUP_NET_SVCLASS_ID }, + { "opp", OBEX_OBJPUSH_SVCLASS_ID }, + { "fax", FAX_SVCLASS_ID }, + { "spp", SERIAL_PORT_SVCLASS_ID }, + { "hsp", HEADSET_SVCLASS_ID }, + { "hfp", HANDSFREE_SVCLASS_ID }, + { } +}; + +uint16_t bt_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 gboolean is_uuid128(const char *string) +{ + return (strlen(string) == 36 && + string[8] == '-' && + string[13] == '-' && + string[18] == '-' && + string[23] == '-'); +} + +char *bt_name2string(const char *pattern) +{ + uuid_t uuid; + uint16_t uuid16; + int i; + + /* UUID 128 string format */ + if (is_uuid128(pattern)) + return g_strdup(pattern); + + /* Friendly service name format */ + uuid16 = bt_name2class(pattern); + if (uuid16) + goto proceed; + + /* HEX format */ + uuid16 = strtol(pattern, NULL, 16); + for (i = 0; bt_services[i].class; i++) { + if (bt_services[i].class == uuid16) + 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 = 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; + } else { + uint16_t class = bt_name2class(string); + if (class) { + sdp_uuid16_create(uuid, class); + return 0; + } + } + + return -1; +} + +gchar *bt_list2string(GSList *list) +{ + GSList *l; + gchar *str, *tmp; + + if (!list) + return NULL; + + str = g_strdup((const gchar *) list->data); + + for (l = list->next; l; l = l->next) { + tmp = g_strconcat(str, " " , (const gchar *) l->data, NULL); + g_free(str); + str = tmp; + } + + return str; +} + +GSList *bt_string2list(const gchar *str) +{ + GSList *l = NULL; + gchar **uuids; + int i = 0; + + if (!str) + return NULL; + + /* FIXME: eglib doesn't support g_strsplit */ + uuids = g_strsplit(str, " ", 0); + if (!uuids) + return NULL; + + while (uuids[i]) { + l = g_slist_append(l, uuids[i]); + i++; + } + + g_free(uuids); + + return l; +} + +static gboolean hci_event_watch(GIOChannel *io, + GIOCondition cond, gpointer user_data) +{ + unsigned char buf[HCI_MAX_EVENT_SIZE], *body; + struct hci_cmd_data *cmd = user_data; + evt_cmd_status *evt_status; + evt_auth_complete *evt_auth; + evt_encrypt_change *evt_enc; + hci_event_hdr *hdr; + set_conn_encrypt_cp cp; + int dd; + uint16_t ocf; + uint8_t status = HCI_OE_POWER_OFF; + + if (cond & G_IO_NVAL) { + cmd->cb(status, cmd->caller_data); + return FALSE; + } + + if (cond & (G_IO_ERR | G_IO_HUP)) + goto failed; + + dd = g_io_channel_unix_get_fd(io); + + if (read(dd, buf, sizeof(buf)) < 0) + goto failed; + + hdr = (hci_event_hdr *) (buf + 1); + body = buf + (1 + HCI_EVENT_HDR_SIZE); + + switch (hdr->evt) { + case EVT_CMD_STATUS: + evt_status = (evt_cmd_status *) body; + ocf = cmd_opcode_ocf(evt_status->opcode); + if (ocf != cmd->ocf) + return TRUE; + switch (ocf) { + case OCF_AUTH_REQUESTED: + case OCF_SET_CONN_ENCRYPT: + if (evt_status->status != 0) { + /* Baseband rejected command */ + status = evt_status->status; + goto failed; + } + break; + default: + return TRUE; + } + /* Wait for the next event */ + return TRUE; + case EVT_AUTH_COMPLETE: + evt_auth = (evt_auth_complete *) body; + if (evt_auth->handle != cmd->handle) { + /* Skipping */ + return TRUE; + } + + if (evt_auth->status != 0x00) { + status = evt_auth->status; + /* Abort encryption */ + goto failed; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = cmd->handle; + cp.encrypt = 1; + + cmd->ocf = OCF_SET_CONN_ENCRYPT; + + if (hci_send_cmd(dd, OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT, + SET_CONN_ENCRYPT_CP_SIZE, &cp) < 0) { + status = HCI_COMMAND_DISALLOWED; + goto failed; + } + /* Wait for encrypt change event */ + return TRUE; + case EVT_ENCRYPT_CHANGE: + evt_enc = (evt_encrypt_change *) body; + if (evt_enc->handle != cmd->handle) + return TRUE; + + /* Procedure finished: reporting status */ + status = evt_enc->status; + break; + default: + /* Skipping */ + return TRUE; + } + +failed: + cmd->cb(status, cmd->caller_data); + g_io_channel_shutdown(io, TRUE, NULL); + + return FALSE; +} + +int bt_acl_encrypt(const bdaddr_t *src, const bdaddr_t *dst, + bt_hci_result_t cb, gpointer user_data) +{ + GIOChannel *io; + struct hci_cmd_data *cmd; + struct hci_conn_info_req *cr; + auth_requested_cp cp; + struct hci_filter nf; + int dd, dev_id, err; + char src_addr[18]; + uint32_t link_mode; + uint16_t handle; + + ba2str(src, src_addr); + dev_id = hci_devid(src_addr); + if (dev_id < 0) + return -errno; + + dd = hci_open_dev(dev_id); + if (dd < 0) + return -errno; + + cr = g_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info)); + cr->type = ACL_LINK; + bacpy(&cr->bdaddr, dst); + + err = ioctl(dd, HCIGETCONNINFO, cr); + link_mode = cr->conn_info->link_mode; + handle = cr->conn_info->handle; + g_free(cr); + + if (err < 0) { + err = errno; + goto failed; + } + + if (link_mode & HCI_LM_ENCRYPT) { + /* Already encrypted */ + err = EALREADY; + goto failed; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = htobs(handle); + + if (hci_send_cmd(dd, OGF_LINK_CTL, OCF_AUTH_REQUESTED, + AUTH_REQUESTED_CP_SIZE, &cp) < 0) { + err = errno; + goto failed; + } + + cmd = g_new0(struct hci_cmd_data, 1); + cmd->handle = handle; + cmd->ocf = OCF_AUTH_REQUESTED; + cmd->cb = cb; + cmd->caller_data = user_data; + + hci_filter_clear(&nf); + hci_filter_set_ptype(HCI_EVENT_PKT, &nf); + hci_filter_set_event(EVT_CMD_STATUS, &nf); + hci_filter_set_event(EVT_AUTH_COMPLETE, &nf); + hci_filter_set_event(EVT_ENCRYPT_CHANGE, &nf); + + if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) { + err = errno; + goto failed; + } + + io = g_io_channel_unix_new(dd); + g_io_channel_set_close_on_unref(io, FALSE); + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, + G_IO_HUP | G_IO_ERR | G_IO_NVAL | G_IO_IN, + hci_event_watch, cmd, g_free); + g_io_channel_unref(io); + + return 0; + +failed: + close(dd); + + return -err; +} diff --git a/common/glib-helper.h b/common/glib-helper.h new file mode 100644 index 000000000..751f3a7d7 --- /dev/null +++ b/common/glib-helper.h @@ -0,0 +1,46 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +int set_nonblocking(int fd); + +typedef void (*bt_callback_t) (sdp_list_t *recs, int err, gpointer user_data); +typedef void (*bt_destroy_t) (gpointer user_data); +typedef void (*bt_hci_result_t) (uint8_t status, gpointer user_data); + +int bt_discover_services(const bdaddr_t *src, const bdaddr_t *dst, + bt_callback_t cb, void *user_data, bt_destroy_t destroy); + +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); +int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst); + +gchar *bt_uuid2string(uuid_t *uuid); +uint16_t bt_name2class(const char *string); +char *bt_name2string(const char *string); +int bt_string2uuid(uuid_t *uuid, const char *string); +gchar *bt_list2string(GSList *list); +GSList *bt_string2list(const gchar *str); + +int bt_acl_encrypt(const bdaddr_t *src, const bdaddr_t *dst, + bt_hci_result_t cb, gpointer user_data); diff --git a/common/logging.c b/common/logging.c new file mode 100644 index 000000000..6c6f925a3 --- /dev/null +++ b/common/logging.c @@ -0,0 +1,108 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "logging.h" + +static volatile int debug_enabled = 0; + +static inline void vinfo(const char *format, va_list ap) +{ + vsyslog(LOG_INFO, format, ap); +} + +void info(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vinfo(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 debug(const char *format, ...) +{ + va_list ap; + + if (!debug_enabled) + return; + + va_start(ap, format); + + vsyslog(LOG_DEBUG, format, ap); + + va_end(ap); +} + +void toggle_debug(void) +{ + debug_enabled = (debug_enabled + 1) % 2; +} + +void enable_debug(void) +{ + debug_enabled = 1; +} + +void disable_debug(void) +{ + debug_enabled = 0; +} + +void start_logging(const char *ident, const char *message, ...) +{ + va_list ap; + + openlog(ident, LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); + + va_start(ap, message); + + vinfo(message, ap); + + va_end(ap); +} + +void stop_logging(void) +{ + closelog(); +} diff --git a/common/logging.h b/common/logging.h new file mode 100644 index 000000000..95087339c --- /dev/null +++ b/common/logging.h @@ -0,0 +1,38 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#ifndef __LOGGING_H +#define __LOGGING_H + +void info(const char *format, ...) __attribute__((format(printf, 1, 2))); +void error(const char *format, ...) __attribute__((format(printf, 1, 2))); +void debug(const char *format, ...) __attribute__((format(printf, 1, 2))); +void toggle_debug(void); +void enable_debug(void); +void disable_debug(void); +void start_logging(const char *ident, const char *message, ...); +void stop_logging(void); + +#define DBG(fmt, arg...) debug("%s: " fmt "\n" , __FUNCTION__ , ## arg) + +#endif /* __LOGGING_H */ diff --git a/common/oui.c b/common/oui.c new file mode 100644 index 000000000..986397375 --- /dev/null +++ b/common/oui.c @@ -0,0 +1,109 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "oui.h" + +/* http://standards.ieee.org/regauth/oui/oui.txt */ + +#define OUIFILE "/var/lib/misc/oui.txt" + +char *ouitocomp(const char *oui) +{ + struct stat st; + char *str, *map, *off, *end; + int fd; + + fd = open("oui.txt", O_RDONLY); + if (fd < 0) { + fd = open(OUIFILE, O_RDONLY); + if (fd < 0) { + fd = open("/usr/share/misc/oui.txt", O_RDONLY); + if (fd < 0) + return NULL; + } + } + + if (fstat(fd, &st) < 0) { + close(fd); + return NULL; + } + + str = malloc(128); + if (!str) { + close(fd); + return NULL; + } + + memset(str, 0, 128); + + map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + free(str); + close(fd); + return NULL; + } + + off = strstr(map, oui); + if (off) { + off += 18; + end = strpbrk(off, "\r\n"); + strncpy(str, off, end - off); + } else { + free(str); + str = NULL; + } + + munmap(map, st.st_size); + + close(fd); + + return str; +} + +int oui2comp(const char *oui, char *comp, size_t size) +{ + char *tmp; + + tmp = ouitocomp(oui); + if (!tmp) + return -1; + + snprintf(comp, size, "%s", tmp); + + free(tmp); + + return 0; +} diff --git a/common/oui.h b/common/oui.h new file mode 100644 index 000000000..79aa30b91 --- /dev/null +++ b/common/oui.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +char *ouitocomp(const char *oui); +int oui2comp(const char *oui, char *comp, size_t size); diff --git a/common/ppoll.h b/common/ppoll.h new file mode 100644 index 000000000..7d09d44d9 --- /dev/null +++ b/common/ppoll.h @@ -0,0 +1,16 @@ +#ifdef ppoll +#undef ppoll +#endif + +#define ppoll compat_ppoll + +static inline int compat_ppoll(struct pollfd *fds, nfds_t nfds, + const struct timespec *timeout, const sigset_t *sigmask) +{ + if (timeout == NULL) + return poll(fds, nfds, -1); + else if (timeout->tv_sec == 0) + return poll(fds, nfds, 500); + else + return poll(fds, nfds, timeout->tv_sec * 1000); +} diff --git a/common/sdp-xml.c b/common/sdp-xml.c new file mode 100644 index 000000000..18473d05e --- /dev/null +++ b/common/sdp-xml.c @@ -0,0 +1,799 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "logging.h" +#include "sdp-xml.h" + +#define STRBUFSIZE 1024 +#define MAXINDENT 64 + +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]; + char next_indent[MAXINDENT]; + + if (!value) + return; + + if (indent_level >= MAXINDENT) + indent_level = MAXINDENT - 2; + + for (i = 0; i < indent_level; i++) { + indent[i] = '\t'; + next_indent[i] = '\t'; + } + + indent[i] = '\0'; + next_indent[i] = '\t'; + next_indent[i + 1] = '\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 = 0; + + 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)); + + /* 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 = (char *) + malloc(sizeof(char) * + (value->unitSize + 1 + num_chars_to_escape * 4)); + 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 = (struct conversion_data *) data; + sdp_data_t *value = (sdp_data_t *) val; + char buf[STRBUFSIZE]; + + buf[STRBUFSIZE - 1] = '\0'; + snprintf(buf, STRBUFSIZE - 1, "\t\n", + value->attrId); + cd->appender(cd->data, buf); + + if (data) + convert_raw_data_to_xml(value, 2, cd->data, cd->appender); + else + cd->appender(cd->data, "\t\tNULL\n"); + + 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"); + } +} + +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]; + + val.data[j++] = strtoul(buf, 0, 16); + i += 2; + } + + return sdp_data_alloc(SDP_UUID128, &val); +} + +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; +} + +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); + + /* 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; +} + +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 (length > UCHAR_MAX) + dtd = SDP_URL_STR16; + + ret = sdp_data_alloc_with_length(dtd, url, length); + + debug("URL size %d length %d: -->%s<--", ret->unitSize, length, url); + + free(url); + + return ret; +} + +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 (length > UCHAR_MAX) + dtd = SDP_TEXT_STR16; + + ret = sdp_data_alloc_with_length(dtd, text, length); + + debug("Text size %d length %d: -->%s<--", ret->unitSize, length, text); + + free(text); + + return ret; +} + +sdp_data_t *sdp_xml_parse_nil(const char *data) +{ + return sdp_data_alloc(SDP_DATA_NIL, 0); +} + +#define DEFAULT_XML_DATA_SIZE 1024 + +struct sdp_xml_data *sdp_xml_data_alloc() +{ + 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); + elem->text[0] = '\0'; + + return elem; +} + +void sdp_xml_data_free(struct sdp_xml_data *elem) +{ + if (elem->data) + sdp_data_free(elem->data); + + if (elem->name) + free(elem->name); + + if (elem->text) + + free(elem->text); + free(elem); +} + +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; +} + +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; +} diff --git a/common/sdp-xml.h b/common/sdp-xml.h new file mode 100644 index 000000000..191f9dff5 --- /dev/null +++ b/common/sdp-xml.h @@ -0,0 +1,59 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2009 Marcel Holtmann + * + * + * 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 + * + */ + + +#ifndef __SDP_XML_H +#define __SDP_XML_H + +#include + +#define SDP_XML_ENCODING_NORMAL 0 +#define SDP_XML_ENCODING_HEX 1 + +void convert_sdp_record_to_xml(sdp_record_t *rec, + void *user_data, void (*append_func) (void *, const char *)); + +sdp_data_t *sdp_xml_parse_nil(const char *data); +sdp_data_t *sdp_xml_parse_text(const char *data, char encoding); +sdp_data_t *sdp_xml_parse_url(const char *data); +sdp_data_t *sdp_xml_parse_int(const char *data, uint8_t dtd); +sdp_data_t *sdp_xml_parse_uuid(const char *data, sdp_record_t *record); + +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 sdp_xml_data *sdp_xml_data_alloc(); +void sdp_xml_data_free(struct sdp_xml_data *elem); +struct sdp_xml_data *sdp_xml_data_expand(struct sdp_xml_data *elem); + +sdp_data_t *sdp_xml_parse_datatype(const char *el, struct sdp_xml_data *elem, + sdp_record_t *record); + +#endif /* __SDP_XML_H */ diff --git a/common/test_textfile.c b/common/test_textfile.c new file mode 100644 index 000000000..29ca493c9 --- /dev/null +++ b/common/test_textfile.c @@ -0,0 +1,188 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "textfile.h" + +static void print_entry(char *key, char *value, void *data) +{ + printf("%s %s\n", key, value); +} + +int main(int argc, char *argv[]) +{ + char filename[] = "/tmp/textfile"; + char key[18], value[512], *str; + unsigned int i, j, size, max = 10; + int fd, err; + + size = getpagesize(); + printf("System uses a page size of %d bytes\n\n", size); + + fd = creat(filename, 0644); + err = ftruncate(fd, 0); + + memset(value, 0, sizeof(value)); + for (i = 0; i < (size / sizeof(value)); i++) + err = write(fd, value, sizeof(value)); + + close(fd); + + sprintf(key, "11:11:11:11:11:11"); + str = textfile_get(filename, key); + + err = truncate(filename, 0); + + + sprintf(key, "00:00:00:00:00:00"); + if (textfile_del(filename, key) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + memset(value, 0, sizeof(value)); + if (textfile_put(filename, key, value) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + str = textfile_get(filename, key); + if (!str) + fprintf(stderr, "No value for %s\n", key); + else + free(str); + + snprintf(value, sizeof(value), "Test"); + if (textfile_put(filename, key, value) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + if (textfile_put(filename, key, value) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + if (textfile_put(filename, key, value) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + if (textfile_del(filename, key) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + str = textfile_get(filename, key); + if (str) { + fprintf(stderr, "Found value for %s\n", key); + free(str); + } + + for (i = 1; i < max + 1; i++) { + sprintf(key, "00:00:00:00:00:%02X", i); + + memset(value, 0, sizeof(value)); + for (j = 0; j < i; j++) + value[j] = 'x'; + + printf("%s %s\n", key, value); + + if (textfile_put(filename, key, value) < 0) { + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + break; + } + + str = textfile_get(filename, key); + if (!str) + fprintf(stderr, "No value for %s\n", key); + else + free(str); + } + + + sprintf(key, "00:00:00:00:00:%02X", max); + + memset(value, 0, sizeof(value)); + for (j = 0; j < max; j++) + value[j] = 'y'; + + if (textfile_put(filename, key, value) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + sprintf(key, "00:00:00:00:00:%02X", 1); + + memset(value, 0, sizeof(value)); + for (j = 0; j < max; j++) + value[j] = 'z'; + + if (textfile_put(filename, key, value) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + printf("\n"); + + for (i = 1; i < max + 1; i++) { + sprintf(key, "00:00:00:00:00:%02X", i); + + str = textfile_get(filename, key); + if (str) { + printf("%s %s\n", key, str); + free(str); + } + } + + + sprintf(key, "00:00:00:00:00:%02X", 2); + + if (textfile_del(filename, key) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + sprintf(key, "00:00:00:00:00:%02X", max - 3); + + if (textfile_del(filename, key) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + printf("\n"); + + textfile_foreach(filename, print_entry, NULL); + + + sprintf(key, "00:00:00:00:00:%02X", 1); + + if (textfile_del(filename, key) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + sprintf(key, "00:00:00:00:00:%02X", max); + + if (textfile_del(filename, key) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + sprintf(key, "00:00:00:00:00:%02X", max + 1); + + if (textfile_del(filename, key) < 0) + fprintf(stderr, "%s (%d)\n", strerror(errno), errno); + + printf("\n"); + + textfile_foreach(filename, print_entry, NULL); + + return 0; +} diff --git a/common/textfile.c b/common/textfile.c new file mode 100644 index 000000000..a755ab759 --- /dev/null +++ b/common/textfile.c @@ -0,0 +1,469 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "textfile.h" + +#ifndef HAVE_FDATASYNC +#define fdatasync fsync +#endif + +int create_dirs(const char *filename, const mode_t mode) +{ + struct stat st; + char dir[PATH_MAX + 1], *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; + + umask(S_IWGRP | S_IWOTH); + create_dirs(filename, S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + + 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 *path, const char *address, const char *name) +{ + return snprintf(buf, size, "%s/%s/%s", path, 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) + return ptr; + + 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 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, pos; 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) { + pos = 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) { + if (value) { + munmap(map, size); + pos = lseek(fd, size, SEEK_SET); + err = write_key_value(fd, key, value); + } + goto unlock; + } + + base = off - map; + + end = strpbrk(off, "\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; + } + pos = 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; + } + pos = 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 = strpbrk(off, "\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_caseput(const char *pathname, const char *key, const char *value) +{ + return write_key(pathname, key, value, 1); +} + +int textfile_del(const char *pathname, const char *key) +{ + return write_key(pathname, key, NULL, 0); +} + +int textfile_casedel(const char *pathname, const char *key) +{ + return write_key(pathname, key, NULL, 1); +} + +char *textfile_get(const char *pathname, const char *key) +{ + return read_key(pathname, key, 0); +} + +char *textfile_caseget(const char *pathname, const char *key) +{ + return read_key(pathname, key, 1); +} + +int textfile_foreach(const char *pathname, + void (*func)(char *key, char *value, void *data), 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 (1) { + end = strpbrk(off, " "); + 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; + + end = strpbrk(off, "\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; +} diff --git a/common/textfile.h b/common/textfile.h new file mode 100644 index 000000000..a49e2494a --- /dev/null +++ b/common/textfile.h @@ -0,0 +1,42 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#ifndef __TEXTFILE_H +#define __TEXTFILE_H + +int create_dirs(const char *filename, const mode_t mode); +int create_file(const char *filename, const mode_t mode); +int create_name(char *buf, size_t size, const char *path, + const char *address, const char *name); + +int textfile_put(const char *pathname, const char *key, const char *value); +int textfile_caseput(const char *pathname, const char *key, const char *value); +int textfile_del(const char *pathname, const char *key); +int textfile_casedel(const char *pathname, const char *key); +char *textfile_get(const char *pathname, const char *key); +char *textfile_caseget(const char *pathname, const char *key); + +int textfile_foreach(const char *pathname, + void (*func)(char *key, char *value, void *data), void *data); + +#endif /* __TEXTFILE_H */ diff --git a/common/uinput.h b/common/uinput.h new file mode 100644 index 000000000..230dfb7c5 --- /dev/null +++ b/common/uinput.h @@ -0,0 +1,724 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#ifndef __UINPUT_H +#define __UINPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/* Events */ + +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 +#define EV_LED 0x11 +#define EV_SND 0x12 +#define EV_REP 0x14 +#define EV_FF 0x15 +#define EV_PWR 0x16 +#define EV_FF_STATUS 0x17 +#define EV_MAX 0x1f + +/* Synchronization events */ + +#define SYN_REPORT 0 +#define SYN_CONFIG 1 + +/* + * Keys and buttons + * + * Most of the keys/buttons are modeled after USB HUT 1.12 + * (see http://www.usb.org/developers/hidpage). + * Abbreviations in the comments: + * AC - Application Control + * AL - Application Launch Button + * SC - System Control + */ + +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 + +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 /* SC System Power Down */ +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 + +#define KEY_KPCOMMA 121 +#define KEY_HANGEUL 122 +#define KEY_HANGUEL KEY_HANGEUL +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 + +#define KEY_STOP 128 /* AC Stop */ +#define KEY_AGAIN 129 +#define KEY_PROPS 130 /* AC Properties */ +#define KEY_UNDO 131 /* AC Undo */ +#define KEY_FRONT 132 +#define KEY_COPY 133 /* AC Copy */ +#define KEY_OPEN 134 /* AC Open */ +#define KEY_PASTE 135 /* AC Paste */ +#define KEY_FIND 136 /* AC Search */ +#define KEY_CUT 137 /* AC Cut */ +#define KEY_HELP 138 /* AL Integrated Help Center */ +#define KEY_MENU 139 /* Menu (show menu) */ +#define KEY_CALC 140 /* AL Calculator */ +#define KEY_SETUP 141 +#define KEY_SLEEP 142 /* SC System Sleep */ +#define KEY_WAKEUP 143 /* System Wake Up */ +#define KEY_FILE 144 /* AL Local Machine Browser */ +#define KEY_SENDFILE 145 +#define KEY_DELETEFILE 146 +#define KEY_XFER 147 +#define KEY_PROG1 148 +#define KEY_PROG2 149 +#define KEY_WWW 150 /* AL Internet Browser */ +#define KEY_MSDOS 151 +#define KEY_COFFEE 152 /* AL Terminal Lock/Screensaver */ +#define KEY_SCREENLOCK KEY_COFFEE +#define KEY_DIRECTION 153 +#define KEY_CYCLEWINDOWS 154 +#define KEY_MAIL 155 +#define KEY_BOOKMARKS 156 /* AC Bookmarks */ +#define KEY_COMPUTER 157 +#define KEY_BACK 158 /* AC Back */ +#define KEY_FORWARD 159 /* AC Forward */ +#define KEY_CLOSECD 160 +#define KEY_EJECTCD 161 +#define KEY_EJECTCLOSECD 162 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_RECORD 167 +#define KEY_REWIND 168 +#define KEY_PHONE 169 /* Media Select Telephone */ +#define KEY_ISO 170 +#define KEY_CONFIG 171 /* AL Consumer Control Configuration */ +#define KEY_HOMEPAGE 172 /* AC Home */ +#define KEY_REFRESH 173 /* AC Refresh */ +#define KEY_EXIT 174 /* AC Exit */ +#define KEY_MOVE 175 +#define KEY_EDIT 176 +#define KEY_SCROLLUP 177 +#define KEY_SCROLLDOWN 178 +#define KEY_KPLEFTPAREN 179 +#define KEY_KPRIGHTPAREN 180 +#define KEY_NEW 181 /* AC New */ +#define KEY_REDO 182 /* AC Redo/Repeat */ + +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 + +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_PROG3 202 +#define KEY_PROG4 203 +#define KEY_SUSPEND 205 +#define KEY_CLOSE 206 /* AC Close */ +#define KEY_PLAY 207 +#define KEY_FASTFORWARD 208 +#define KEY_BASSBOOST 209 +#define KEY_PRINT 210 /* AC Print */ +#define KEY_HP 211 +#define KEY_CAMERA 212 +#define KEY_SOUND 213 +#define KEY_QUESTION 214 +#define KEY_EMAIL 215 +#define KEY_CHAT 216 +#define KEY_SEARCH 217 +#define KEY_CONNECT 218 +#define KEY_FINANCE 219 /* AL Checkbook/Finance */ +#define KEY_SPORT 220 +#define KEY_SHOP 221 +#define KEY_ALTERASE 222 +#define KEY_CANCEL 223 /* AC Cancel */ +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MEDIA 226 + +#define KEY_SWITCHVIDEOMODE 227 /* Cycle between available video + outputs (Monitor/LCD/TV-out/etc) */ +#define KEY_KBDILLUMTOGGLE 228 +#define KEY_KBDILLUMDOWN 229 +#define KEY_KBDILLUMUP 230 + +#define KEY_SEND 231 /* AC Send */ +#define KEY_REPLY 232 /* AC Reply */ +#define KEY_FORWARDMAIL 233 /* AC Forward Msg */ +#define KEY_SAVE 234 /* AC Save */ +#define KEY_DOCUMENTS 235 + +#define KEY_BATTERY 236 + +#define KEY_BLUETOOTH 237 +#define KEY_WLAN 238 +#define KEY_UWB 239 + +#define KEY_UNKNOWN 240 + +#define KEY_VIDEO_NEXT 241 /* drive next video source */ +#define KEY_VIDEO_PREV 242 /* drive previous video source */ +#define KEY_BRIGHTNESS_CYCLE 243 /* brightness up, after max is min */ +#define KEY_BRIGHTNESS_ZERO 244 /* brightness off, use ambient */ +#define KEY_DISPLAY_OFF 245 /* display device to off state */ + +#define KEY_WIMAX 246 + +/* Range 248 - 255 is reserved for special needs of AT keyboard driver */ + +#define BTN_MISC 0x100 +#define BTN_0 0x100 +#define BTN_1 0x101 +#define BTN_2 0x102 +#define BTN_3 0x103 +#define BTN_4 0x104 +#define BTN_5 0x105 +#define BTN_6 0x106 +#define BTN_7 0x107 +#define BTN_8 0x108 +#define BTN_9 0x109 + +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 + +#define BTN_JOYSTICK 0x120 +#define BTN_TRIGGER 0x120 +#define BTN_THUMB 0x121 +#define BTN_THUMB2 0x122 +#define BTN_TOP 0x123 +#define BTN_TOP2 0x124 +#define BTN_PINKIE 0x125 +#define BTN_BASE 0x126 +#define BTN_BASE2 0x127 +#define BTN_BASE3 0x128 +#define BTN_BASE4 0x129 +#define BTN_BASE5 0x12a +#define BTN_BASE6 0x12b +#define BTN_DEAD 0x12f + +#define BTN_GAMEPAD 0x130 +#define BTN_A 0x130 +#define BTN_B 0x131 +#define BTN_C 0x132 +#define BTN_X 0x133 +#define BTN_Y 0x134 +#define BTN_Z 0x135 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c +#define BTN_THUMBL 0x13d +#define BTN_THUMBR 0x13e + +#define BTN_DIGI 0x140 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_RUBBER 0x141 +#define BTN_TOOL_BRUSH 0x142 +#define BTN_TOOL_PENCIL 0x143 +#define BTN_TOOL_AIRBRUSH 0x144 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOOL_MOUSE 0x146 +#define BTN_TOOL_LENS 0x147 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_STYLUS2 0x14c +#define BTN_TOOL_DOUBLETAP 0x14d +#define BTN_TOOL_TRIPLETAP 0x14e + +#define BTN_WHEEL 0x150 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 + +#define KEY_OK 0x160 +#define KEY_SELECT 0x161 +#define KEY_GOTO 0x162 +#define KEY_CLEAR 0x163 +#define KEY_POWER2 0x164 +#define KEY_OPTION 0x165 +#define KEY_INFO 0x166 /* AL OEM Features/Tips/Tutorial */ +#define KEY_TIME 0x167 +#define KEY_VENDOR 0x168 +#define KEY_ARCHIVE 0x169 +#define KEY_PROGRAM 0x16a /* Media Select Program Guide */ +#define KEY_CHANNEL 0x16b +#define KEY_FAVORITES 0x16c +#define KEY_EPG 0x16d +#define KEY_PVR 0x16e /* Media Select Home */ +#define KEY_MHP 0x16f +#define KEY_LANGUAGE 0x170 +#define KEY_TITLE 0x171 +#define KEY_SUBTITLE 0x172 +#define KEY_ANGLE 0x173 +#define KEY_ZOOM 0x174 +#define KEY_MODE 0x175 +#define KEY_KEYBOARD 0x176 +#define KEY_SCREEN 0x177 +#define KEY_PC 0x178 /* Media Select Computer */ +#define KEY_TV 0x179 /* Media Select TV */ +#define KEY_TV2 0x17a /* Media Select Cable */ +#define KEY_VCR 0x17b /* Media Select VCR */ +#define KEY_VCR2 0x17c /* VCR Plus */ +#define KEY_SAT 0x17d /* Media Select Satellite */ +#define KEY_SAT2 0x17e +#define KEY_CD 0x17f /* Media Select CD */ +#define KEY_TAPE 0x180 /* Media Select Tape */ +#define KEY_RADIO 0x181 +#define KEY_TUNER 0x182 /* Media Select Tuner */ +#define KEY_PLAYER 0x183 +#define KEY_TEXT 0x184 +#define KEY_DVD 0x185 /* Media Select DVD */ +#define KEY_AUX 0x186 +#define KEY_MP3 0x187 +#define KEY_AUDIO 0x188 +#define KEY_VIDEO 0x189 +#define KEY_DIRECTORY 0x18a +#define KEY_LIST 0x18b +#define KEY_MEMO 0x18c /* Media Select Messages */ +#define KEY_CALENDAR 0x18d +#define KEY_RED 0x18e +#define KEY_GREEN 0x18f +#define KEY_YELLOW 0x190 +#define KEY_BLUE 0x191 +#define KEY_CHANNELUP 0x192 /* Channel Increment */ +#define KEY_CHANNELDOWN 0x193 /* Channel Decrement */ +#define KEY_FIRST 0x194 +#define KEY_LAST 0x195 /* Recall Last */ +#define KEY_AB 0x196 +#define KEY_NEXT 0x197 +#define KEY_RESTART 0x198 +#define KEY_SLOW 0x199 +#define KEY_SHUFFLE 0x19a +#define KEY_BREAK 0x19b +#define KEY_PREVIOUS 0x19c +#define KEY_DIGITS 0x19d +#define KEY_TEEN 0x19e +#define KEY_TWEN 0x19f +#define KEY_VIDEOPHONE 0x1a0 /* Media Select Video Phone */ +#define KEY_GAMES 0x1a1 /* Media Select Games */ +#define KEY_ZOOMIN 0x1a2 /* AC Zoom In */ +#define KEY_ZOOMOUT 0x1a3 /* AC Zoom Out */ +#define KEY_ZOOMRESET 0x1a4 /* AC Zoom */ +#define KEY_WORDPROCESSOR 0x1a5 /* AL Word Processor */ +#define KEY_EDITOR 0x1a6 /* AL Text Editor */ +#define KEY_SPREADSHEET 0x1a7 /* AL Spreadsheet */ +#define KEY_GRAPHICSEDITOR 0x1a8 /* AL Graphics Editor */ +#define KEY_PRESENTATION 0x1a9 /* AL Presentation App */ +#define KEY_DATABASE 0x1aa /* AL Database App */ +#define KEY_NEWS 0x1ab /* AL Newsreader */ +#define KEY_VOICEMAIL 0x1ac /* AL Voicemail */ +#define KEY_ADDRESSBOOK 0x1ad /* AL Contacts/Address Book */ +#define KEY_MESSENGER 0x1ae /* AL Instant Messaging */ +#define KEY_DISPLAYTOGGLE 0x1af /* Turn display (LCD) on and off */ +#define KEY_SPELLCHECK 0x1b0 /* AL Spell Check */ +#define KEY_LOGOFF 0x1b1 /* AL Logoff */ + +#define KEY_DOLLAR 0x1b2 +#define KEY_EURO 0x1b3 + +#define KEY_FRAMEBACK 0x1b4 /* Consumer - transport controls */ +#define KEY_FRAMEFORWARD 0x1b5 +#define KEY_CONTEXT_MENU 0x1b6 /* GenDesc - system context menu */ +#define KEY_MEDIA_REPEAT 0x1b7 /* Consumer - transport control */ + +#define KEY_DEL_EOL 0x1c0 +#define KEY_DEL_EOS 0x1c1 +#define KEY_INS_LINE 0x1c2 +#define KEY_DEL_LINE 0x1c3 + +#define KEY_FN 0x1d0 +#define KEY_FN_ESC 0x1d1 +#define KEY_FN_F1 0x1d2 +#define KEY_FN_F2 0x1d3 +#define KEY_FN_F3 0x1d4 +#define KEY_FN_F4 0x1d5 +#define KEY_FN_F5 0x1d6 +#define KEY_FN_F6 0x1d7 +#define KEY_FN_F7 0x1d8 +#define KEY_FN_F8 0x1d9 +#define KEY_FN_F9 0x1da +#define KEY_FN_F10 0x1db +#define KEY_FN_F11 0x1dc +#define KEY_FN_F12 0x1dd +#define KEY_FN_1 0x1de +#define KEY_FN_2 0x1df +#define KEY_FN_D 0x1e0 +#define KEY_FN_E 0x1e1 +#define KEY_FN_F 0x1e2 +#define KEY_FN_S 0x1e3 +#define KEY_FN_B 0x1e4 + +#define KEY_BRL_DOT1 0x1f1 +#define KEY_BRL_DOT2 0x1f2 +#define KEY_BRL_DOT3 0x1f3 +#define KEY_BRL_DOT4 0x1f4 +#define KEY_BRL_DOT5 0x1f5 +#define KEY_BRL_DOT6 0x1f6 +#define KEY_BRL_DOT7 0x1f7 +#define KEY_BRL_DOT8 0x1f8 +#define KEY_BRL_DOT9 0x1f9 +#define KEY_BRL_DOT10 0x1fa + +/* We avoid low common keys in module aliases so they don't get huge. */ +#define KEY_MIN_INTERESTING KEY_MUTE +#define KEY_MAX 0x1ff +#define KEY_CNT (KEY_MAX+1) + +/* + * Relative axes + */ + +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 +#define REL_RX 0x03 +#define REL_RY 0x04 +#define REL_RZ 0x05 +#define REL_HWHEEL 0x06 +#define REL_DIAL 0x07 +#define REL_WHEEL 0x08 +#define REL_MISC 0x09 +#define REL_MAX 0x0f +#define REL_CNT (REL_MAX+1) + +/* + * Absolute axes + */ + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_HAT1X 0x12 +#define ABS_HAT1Y 0x13 +#define ABS_HAT2X 0x14 +#define ABS_HAT2Y 0x15 +#define ABS_HAT3X 0x16 +#define ABS_HAT3Y 0x17 +#define ABS_PRESSURE 0x18 +#define ABS_DISTANCE 0x19 +#define ABS_TILT_X 0x1a +#define ABS_TILT_Y 0x1b +#define ABS_TOOL_WIDTH 0x1c +#define ABS_VOLUME 0x20 +#define ABS_MISC 0x28 +#define ABS_MAX 0x3f +#define ABS_CNT (ABS_MAX+1) + +/* + * Switch events + */ + +#define SW_LID 0x00 /* set = lid shut */ +#define SW_TABLET_MODE 0x01 /* set = tablet mode */ +#define SW_HEADPHONE_INSERT 0x02 /* set = inserted */ +#define SW_RFKILL_ALL 0x03 /* rfkill master switch, type "any" + set = radio enabled */ +#define SW_RADIO SW_RFKILL_ALL /* deprecated */ +#define SW_MICROPHONE_INSERT 0x04 /* set = inserted */ +#define SW_DOCK 0x05 /* set = plugged into dock */ +#define SW_MAX 0x0f +#define SW_CNT (SW_MAX+1) + +/* + * Misc events + */ + +#define MSC_SERIAL 0x00 +#define MSC_PULSELED 0x01 +#define MSC_GESTURE 0x02 +#define MSC_RAW 0x03 +#define MSC_SCAN 0x04 +#define MSC_MAX 0x07 +#define MSC_CNT (MSC_MAX+1) + +/* + * LEDs + */ + +#define LED_NUML 0x00 +#define LED_CAPSL 0x01 +#define LED_SCROLLL 0x02 +#define LED_COMPOSE 0x03 +#define LED_KANA 0x04 +#define LED_SLEEP 0x05 +#define LED_SUSPEND 0x06 +#define LED_MUTE 0x07 +#define LED_MISC 0x08 +#define LED_MAIL 0x09 +#define LED_CHARGING 0x0a +#define LED_MAX 0x0f +#define LED_CNT (LED_MAX+1) + +/* + * Autorepeat values + */ + +#define REP_DELAY 0x00 +#define REP_PERIOD 0x01 +#define REP_MAX 0x01 + +/* + * Sounds + */ + +#define SND_CLICK 0x00 +#define SND_BELL 0x01 +#define SND_TONE 0x02 +#define SND_MAX 0x07 +#define SND_CNT (SND_MAX+1) + +/* + * IDs. + */ + +#define ID_BUS 0 +#define ID_VENDOR 1 +#define ID_PRODUCT 2 +#define ID_VERSION 3 + +#define BUS_PCI 0x01 +#define BUS_ISAPNP 0x02 +#define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 +#define BUS_VIRTUAL 0x06 + +#define BUS_ISA 0x10 +#define BUS_I8042 0x11 +#define BUS_XTKBD 0x12 +#define BUS_RS232 0x13 +#define BUS_GAMEPORT 0x14 +#define BUS_PARPORT 0x15 +#define BUS_AMIGA 0x16 +#define BUS_ADB 0x17 +#define BUS_I2C 0x18 +#define BUS_HOST 0x19 +#define BUS_GSC 0x1A +#define BUS_ATARI 0x1B + +/* User input interface */ + +#define UINPUT_IOCTL_BASE 'U' + +#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1) +#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2) + +#define UI_SET_EVBIT _IOW(UINPUT_IOCTL_BASE, 100, int) +#define UI_SET_KEYBIT _IOW(UINPUT_IOCTL_BASE, 101, int) +#define UI_SET_RELBIT _IOW(UINPUT_IOCTL_BASE, 102, int) +#define UI_SET_ABSBIT _IOW(UINPUT_IOCTL_BASE, 103, int) +#define UI_SET_MSCBIT _IOW(UINPUT_IOCTL_BASE, 104, int) +#define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int) +#define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int) +#define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int) +#define UI_SET_PHYS _IOW(UINPUT_IOCTL_BASE, 108, char*) +#define UI_SET_SWBIT _IOW(UINPUT_IOCTL_BASE, 109, int) + +#ifndef NBITS +#define NBITS(x) ((((x) - 1) / (sizeof(long) * 8)) + 1) +#endif + +#define UINPUT_MAX_NAME_SIZE 80 + +struct uinput_id { + uint16_t bustype; + uint16_t vendor; + uint16_t product; + uint16_t version; +}; + +struct uinput_dev { + char name[UINPUT_MAX_NAME_SIZE]; + struct uinput_id id; + int ff_effects_max; + int absmax[ABS_MAX + 1]; + int absmin[ABS_MAX + 1]; + int absfuzz[ABS_MAX + 1]; + int absflat[ABS_MAX + 1]; +}; + +struct uinput_event { + struct timeval time; + uint16_t type; + uint16_t code; + int32_t value; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __UINPUT_H */ diff --git a/compat/Makefile.am b/compat/Makefile.am new file mode 100644 index 000000000..58254adce --- /dev/null +++ b/compat/Makefile.am @@ -0,0 +1,47 @@ + +bin_PROGRAMS = +man_MANS = + +if HIDD +bin_PROGRAMS += hidd + +hidd_SOURCES = hidd.c hidd.h sdp.h sdp.c fakehid.c + +hidd_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@ -lm + +if MANPAGES +man_MANS += hidd.1 +endif +endif + +if PAND +bin_PROGRAMS += pand + +pand_SOURCES = pand.c pand.h bnep.c sdp.h sdp.c + +pand_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@ + +if MANPAGES +man_MANS += pand.1 +endif +endif + +if DUND +bin_PROGRAMS += dund + +dund_SOURCES = dund.c dund.h lib.h sdp.h sdp.c dun.c msdun.c + +dund_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@ + +if MANPAGES +man_MANS += dund.1 +endif +endif + +AM_CFLAGS = @BLUEZ_CFLAGS@ + +INCLUDES = -I$(top_srcdir)/common + +EXTRA_DIST = fakehid.txt hidd.1 pand.1 dund.1 + +MAINTAINERCLEANFILES = Makefile.in diff --git a/compat/bnep.c b/compat/bnep.c new file mode 100644 index 000000000..0498c789f --- /dev/null +++ b/compat/bnep.c @@ -0,0 +1,323 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "pand.h" + +static int ctl; + +/* Compatibility with old ioctls */ +#define OLD_BNEPCONADD 1 +#define OLD_BNEPCONDEL 2 +#define OLD_BNEPGETCONLIST 3 +#define OLD_BNEPGETCONINFO 4 + +static unsigned long bnepconnadd; +static unsigned long bnepconndel; +static unsigned long bnepgetconnlist; +static unsigned long bnepgetconninfo; + +static struct { + char *str; + uint16_t uuid; +} __svc[] = { + { "PANU", BNEP_SVC_PANU }, + { "NAP", BNEP_SVC_NAP }, + { "GN", BNEP_SVC_GN }, + { NULL } +}; + +int bnep_str2svc(char *svc, uint16_t *uuid) +{ + int i; + for (i = 0; __svc[i].str; i++) + if (!strcasecmp(svc, __svc[i].str)) { + *uuid = __svc[i].uuid; + return 0; + } + return -1; +} + +char *bnep_svc2str(uint16_t uuid) +{ + int i; + for (i = 0; __svc[i].str; i++) + if (__svc[i].uuid == uuid) + return __svc[i].str; + return NULL; +} + +int bnep_init(void) +{ + ctl = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_BNEP); + if (ctl < 0) { + perror("Failed to open control socket"); + return 1; + } + + /* Temporary ioctl compatibility hack */ + { + struct bnep_connlist_req req; + struct bnep_conninfo ci[1]; + + req.cnum = 1; + req.ci = ci; + + if (!ioctl(ctl, BNEPGETCONNLIST, &req)) { + /* New ioctls */ + bnepconnadd = BNEPCONNADD; + bnepconndel = BNEPCONNDEL; + bnepgetconnlist = BNEPGETCONNLIST; + bnepgetconninfo = BNEPGETCONNINFO; + } else { + /* Old ioctls */ + bnepconnadd = OLD_BNEPCONADD; + bnepconndel = OLD_BNEPCONDEL; + bnepgetconnlist = OLD_BNEPGETCONLIST; + bnepgetconninfo = OLD_BNEPGETCONINFO; + } + } + + return 0; +} + +int bnep_cleanup(void) +{ + close(ctl); + return 0; +} + +int bnep_show_connections(void) +{ + struct bnep_connlist_req req; + struct bnep_conninfo ci[48]; + unsigned int i; + + req.cnum = 48; + req.ci = ci; + if (ioctl(ctl, bnepgetconnlist, &req)) { + perror("Failed to get connection list"); + return -1; + } + + for (i = 0; i < req.cnum; i++) { + printf("%s %s %s\n", ci[i].device, + batostr((bdaddr_t *) ci[i].dst), + bnep_svc2str(ci[i].role)); + } + return 0; +} + +int bnep_kill_connection(uint8_t *dst) +{ + struct bnep_conndel_req req; + + memcpy(req.dst, dst, ETH_ALEN); + req.flags = 0; + if (ioctl(ctl, bnepconndel, &req)) { + perror("Failed to kill connection"); + return -1; + } + return 0; +} + +int bnep_kill_all_connections(void) +{ + struct bnep_connlist_req req; + struct bnep_conninfo ci[48]; + unsigned int i; + + req.cnum = 48; + req.ci = ci; + if (ioctl(ctl, bnepgetconnlist, &req)) { + perror("Failed to get connection list"); + return -1; + } + + for (i = 0; i < req.cnum; i++) { + struct bnep_conndel_req req; + memcpy(req.dst, ci[i].dst, ETH_ALEN); + req.flags = 0; + ioctl(ctl, bnepconndel, &req); + } + return 0; +} + +static int bnep_connadd(int sk, uint16_t role, char *dev) +{ + struct bnep_connadd_req req; + + strncpy(req.device, dev, 16); + req.device[15] = '\0'; + req.sock = sk; + req.role = role; + if (ioctl(ctl, bnepconnadd, &req)) + return -1; + strncpy(dev, req.device, 16); + return 0; +} + +struct __service_16 { + uint16_t dst; + uint16_t src; +} __attribute__ ((packed)); + +struct __service_32 { + uint16_t unused1; + uint16_t dst; + uint16_t unused2; + uint16_t src; +} __attribute__ ((packed)); + +struct __service_128 { + uint16_t unused1; + uint16_t dst; + uint16_t unused2[8]; + uint16_t src; + uint16_t unused3[7]; +} __attribute__ ((packed)); + +int bnep_accept_connection(int sk, uint16_t role, char *dev) +{ + struct bnep_setup_conn_req *req; + struct bnep_control_rsp *rsp; + unsigned char pkt[BNEP_MTU]; + ssize_t r; + + r = recv(sk, pkt, BNEP_MTU, 0); + if (r <= 0) + return -1; + + errno = EPROTO; + + if ((size_t) r < sizeof(*req)) + return -1; + + req = (void *) pkt; + if (req->type != BNEP_CONTROL || req->ctrl != BNEP_SETUP_CONN_REQ) + return -1; + + /* FIXME: Check role UUIDs */ + + rsp = (void *) pkt; + rsp->type = BNEP_CONTROL; + rsp->ctrl = BNEP_SETUP_CONN_RSP; + rsp->resp = htons(BNEP_SUCCESS); + if (send(sk, rsp, sizeof(*rsp), 0) < 0) + return -1; + + return bnep_connadd(sk, role, dev); +} + +/* Create BNEP connection + * sk - Connect L2CAP socket + * role - Local role + * service - Remote service + * dev - Network device (contains actual dev name on return) + */ +int bnep_create_connection(int sk, uint16_t role, uint16_t svc, char *dev) +{ + struct bnep_setup_conn_req *req; + struct bnep_control_rsp *rsp; + struct __service_16 *s; + struct timeval timeo; + unsigned char pkt[BNEP_MTU]; + ssize_t r; + + /* 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->dst = htons(svc); + s->src = htons(role); + + memset(&timeo, 0, sizeof(timeo)); + timeo.tv_sec = 30; + + setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); + + if (send(sk, pkt, sizeof(*req) + sizeof(*s), 0) < 0) + return -1; + +receive: + /* Get response */ + r = recv(sk, pkt, BNEP_MTU, 0); + if (r <= 0) + return -1; + + memset(&timeo, 0, sizeof(timeo)); + timeo.tv_sec = 0; + + setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); + + errno = EPROTO; + + if ((size_t) r < sizeof(*rsp)) + return -1; + + rsp = (void *) pkt; + if (rsp->type != BNEP_CONTROL) + return -1; + + if (rsp->ctrl != BNEP_SETUP_CONN_RSP) + goto receive; + + r = ntohs(rsp->resp); + + switch (r) { + case BNEP_SUCCESS: + break; + + case BNEP_CONN_INVALID_DST: + case BNEP_CONN_INVALID_SRC: + case BNEP_CONN_INVALID_SVC: + errno = EPROTO; + return -1; + + case BNEP_CONN_NOT_ALLOWED: + errno = EACCES; + return -1; + } + + return bnep_connadd(sk, role, dev); +} diff --git a/compat/dun.c b/compat/dun.c new file mode 100644 index 000000000..c74e5ee2e --- /dev/null +++ b/compat/dun.c @@ -0,0 +1,331 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "dund.h" +#include "lib.h" + +#define PROC_BASE "/proc" + +static int for_each_port(int (*func)(struct rfcomm_dev_info *, unsigned long), unsigned long arg) +{ + struct rfcomm_dev_list_req *dl; + struct rfcomm_dev_info *di; + long r = 0; + int sk, i; + + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM); + if (sk < 0 ) { + perror("Can't open RFCOMM control socket"); + exit(1); + } + + dl = malloc(sizeof(*dl) + RFCOMM_MAX_DEV * sizeof(*di)); + if (!dl) { + perror("Can't allocate request memory"); + close(sk); + exit(1); + } + + dl->dev_num = RFCOMM_MAX_DEV; + di = dl->dev_info; + + if (ioctl(sk, RFCOMMGETDEVLIST, (void *) dl) < 0) { + perror("Can't get device list"); + exit(1); + } + + for (i = 0; i < dl->dev_num; i++) { + r = func(di + i, arg); + if (r) break; + } + + close(sk); + return r; +} + +static int uses_rfcomm(char *path, char *dev) +{ + struct dirent *de; + DIR *dir; + + dir = opendir(path); + if (!dir) + return 0; + + if (chdir(path) < 0) + return 0; + + while ((de = readdir(dir)) != NULL) { + char link[PATH_MAX + 1]; + int len = readlink(de->d_name, link, sizeof(link)); + if (len > 0) { + link[len] = 0; + if (strstr(link, dev)) + return 1; + } + } + + closedir(dir); + + return 0; +} + +static int find_pppd(int id, pid_t *pid) +{ + struct dirent *de; + char path[PATH_MAX + 1]; + char dev[10]; + int empty = 1; + DIR *dir; + + dir = opendir(PROC_BASE); + if (!dir) { + perror(PROC_BASE); + return -1; + } + + sprintf(dev, "rfcomm%d", id); + + *pid = 0; + while ((de = readdir(dir)) != NULL) { + empty = 0; + if (isdigit(de->d_name[0])) { + sprintf(path, "%s/%s/fd", PROC_BASE, de->d_name); + if (uses_rfcomm(path, dev)) { + *pid = atoi(de->d_name); + break; + } + } + } + closedir(dir); + + if (empty) + fprintf(stderr, "%s is empty (not mounted ?)\n", PROC_BASE); + + return *pid != 0; +} + +static int dun_exec(char *tty, char *prog, char **args) +{ + int pid = fork(); + int fd; + + switch (pid) { + case -1: + return -1; + + case 0: + break; + + default: + return pid; + } + + setsid(); + + /* Close all FDs */ + for (fd = 3; fd < 20; fd++) + close(fd); + + execvp(prog, args); + + syslog(LOG_ERR, "Error while executing %s", prog); + + exit(1); +} + +static int dun_create_tty(int sk, char *tty, int size) +{ + struct sockaddr_rc sa; + struct stat st; + socklen_t alen; + int id, try = 30; + + struct rfcomm_dev_req req = { + flags: (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP), + dev_id: -1 + }; + + alen = sizeof(sa); + if (getpeername(sk, (struct sockaddr *) &sa, &alen) < 0) + return -1; + bacpy(&req.dst, &sa.rc_bdaddr); + + alen = sizeof(sa); + if (getsockname(sk, (struct sockaddr *) &sa, &alen) < 0) + return -1; + bacpy(&req.src, &sa.rc_bdaddr); + req.channel = sa.rc_channel; + + id = ioctl(sk, RFCOMMCREATEDEV, &req); + if (id < 0) + return id; + + snprintf(tty, size, "/dev/rfcomm%d", id); + while (stat(tty, &st) < 0) { + snprintf(tty, size, "/dev/bluetooth/rfcomm/%d", id); + if (stat(tty, &st) < 0) { + snprintf(tty, size, "/dev/rfcomm%d", id); + if (try--) { + usleep(100 * 1000); + continue; + } + + memset(&req, 0, sizeof(req)); + req.dev_id = id; + ioctl(sk, RFCOMMRELEASEDEV, &req); + + return -1; + } + } + + return id; +} + +int dun_init(void) +{ + return 0; +} + +int dun_cleanup(void) +{ + return 0; +} + +static int show_conn(struct rfcomm_dev_info *di, unsigned long arg) +{ + pid_t pid; + + if (di->state == BT_CONNECTED && + (di->flags & (1<flags & (1<flags & (1<id, &pid)) { + char dst[18]; + ba2str(&di->dst, dst); + + printf("rfcomm%d: %s channel %d pppd pid %d\n", + di->id, dst, di->channel, pid); + } + } + return 0; +} + +static int kill_conn(struct rfcomm_dev_info *di, unsigned long arg) +{ + bdaddr_t *dst = (bdaddr_t *) arg; + pid_t pid; + + if (di->state == BT_CONNECTED && + (di->flags & (1<flags & (1<flags & (1<dst, dst)) + return 0; + + if (find_pppd(di->id, &pid)) { + if (kill(pid, SIGINT) < 0) + perror("Kill"); + + if (!dst) + return 0; + return 1; + } + } + return 0; +} + +int dun_show_connections(void) +{ + for_each_port(show_conn, 0); + return 0; +} + +int dun_kill_connection(uint8_t *dst) +{ + for_each_port(kill_conn, (unsigned long) dst); + return 0; +} + +int dun_kill_all_connections(void) +{ + for_each_port(kill_conn, 0); + return 0; +} + +int dun_open_connection(int sk, char *pppd, char **args, int wait) +{ + char tty[100]; + int pid; + + if (dun_create_tty(sk, tty, sizeof(tty) - 1) < 0) { + syslog(LOG_ERR, "RFCOMM TTY creation failed. %s(%d)", strerror(errno), errno); + return -1; + } + + args[0] = "pppd"; + args[1] = tty; + args[2] = "nodetach"; + + pid = dun_exec(tty, pppd, args); + if (pid < 0) { + syslog(LOG_ERR, "Exec failed. %s(%d)", strerror(errno), errno); + return -1; + } + + if (wait) { + int status; + waitpid(pid, &status, 0); + /* FIXME: Check for waitpid errors */ + } + + return 0; +} diff --git a/compat/dund.1 b/compat/dund.1 new file mode 100644 index 000000000..09fb7f755 --- /dev/null +++ b/compat/dund.1 @@ -0,0 +1,72 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.29. +.TH BlueZ "1" "February 2003" "DUN daemon" "User Commands" +.SH NAME +dund \- BlueZ Bluetooth dial-up networking daemon +.SH DESCRIPTION +DUN daemon +.SH SYNOPSIS +dund [pppd options] +.SH OPTIONS +.TP +\fB\-\-show\fR \fB\-\-list\fR \fB\-l\fR +Show active DUN connections +.TP +\fB\-\-listen\fR \fB\-s\fR +Listen for DUN connections +.TP +\fB\-\-dialup\fR \fB\-u\fR +Listen for dialup/telephone connections +.TP +\fB\-\-connect\fR \fB\-c\fR +Create DUN connection +.TP +\fB\-\-mrouter\fR \fB\-m\fR +Create mRouter connection +.TP +\fB\-\-search\fR \fB\-Q[duration]\fR +Search and connect +.TP +\fB\-\-kill\fR \fB\-k\fR +Kill DUN connection +.TP +\fB\-\-killall\fR \fB\-K\fR +Kill all DUN connections +.TP +\fB\-\-channel\fR \fB\-C\fR +RFCOMM channel +.TP +\fB\-\-device\fR \fB\-i\fR +Source bdaddr +.TP +\fB\-\-nosdp\fR \fB\-D\fR +Disable SDP +.TP +\fB\-\-auth\fR \fB\-A\fR +Enable authentification +.TP +\fB\-\-encrypt\fR \fB\-E\fR +Enable encryption +.TP +\fB\-\-secure\fR \fB\-S\fR +Secure connection +.TP +\fB\-\-master\fR \fB\-M\fR +Become the master of a piconet +.TP +\fB\-\-nodetach\fR \fB\-n\fR +Do not become a daemon +.TP +\fB\-\-persist\fR \fB\-p[interval]\fR +Persist mode +.TP +\fB\-\-pppd\fR \fB\-d\fR +Location of the PPP daemon (pppd) +.TP +\fB\-\-msdun\fR \fB\-X\fR [timeo] +Enable Microsoft dialup networking support +.TP +\fB\-\-activesync\fR \fB\-a\fR +Enable Microsoft ActiveSync networking +.TP +\fB\-\-cache\fR \fB\-C\fR [valid] +Enable address cache diff --git a/compat/dund.c b/compat/dund.c new file mode 100644 index 000000000..fc8ba3036 --- /dev/null +++ b/compat/dund.c @@ -0,0 +1,638 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "sdp.h" +#include "dund.h" +#include "lib.h" + +volatile sig_atomic_t __io_canceled; + +/* MS dialup networking support (i.e. CLIENT / CLIENTSERVER thing) */ +static int msdun = 0; + +static char *pppd = "/usr/sbin/pppd"; +static char *pppd_opts[DUN_MAX_PPP_OPTS] = + { + /* First 3 are reserved */ + "", "", "", + "noauth", + "noipdefault", + NULL + }; + +static int detach = 1; +static int persist; +static int use_sdp = 1; +static int auth; +static int encrypt; +static int secure; +static int master; +static int type = LANACCESS; +static int search_duration = 10; +static uint use_cache; + +static int channel; + +static struct { + uint valid; + char dst[40]; + bdaddr_t bdaddr; + int channel; +} cache; + +static bdaddr_t src_addr = *BDADDR_ANY; +static int src_dev = -1; + +volatile int terminate; + +enum { + NONE, + SHOW, + LISTEN, + CONNECT, + KILL +} modes; + +static int create_connection(char *dst, bdaddr_t *bdaddr, int mrouter); + +static int do_listen(void) +{ + struct sockaddr_rc sa; + int sk, lm; + + if (type == MROUTER) { + if (!cache.valid) + return -1; + + if (create_connection(cache.dst, &cache.bdaddr, type) < 0) { + syslog(LOG_ERR, "Cannot connect to mRouter device. %s(%d)", + strerror(errno), errno); + return -1; + } + } + + if (!channel) + channel = DUN_DEFAULT_CHANNEL; + + if (use_sdp) + dun_sdp_register(&src_addr, channel, type); + + if (type == MROUTER) + syslog(LOG_INFO, "Waiting for mRouter callback on channel %d", channel); + + /* Create RFCOMM socket */ + sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)", + strerror(errno), errno); + return -1; + } + + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = channel; + sa.rc_bdaddr = src_addr; + + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) { + syslog(LOG_ERR, "Bind failed. %s(%d)", strerror(errno), errno); + return -1; + } + + /* Set link mode */ + lm = 0; + if (master) + lm |= RFCOMM_LM_MASTER; + if (auth) + lm |= RFCOMM_LM_AUTH; + if (encrypt) + lm |= RFCOMM_LM_ENCRYPT; + if (secure) + lm |= RFCOMM_LM_SECURE; + + if (lm && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) { + syslog(LOG_ERR, "Failed to set link mode. %s(%d)", strerror(errno), errno); + return -1; + } + + listen(sk, 10); + + while (!terminate) { + socklen_t alen = sizeof(sa); + int nsk; + char ba[40]; + char ch[10]; + + nsk = accept(sk, (struct sockaddr *) &sa, &alen); + if (nsk < 0) { + syslog(LOG_ERR, "Accept failed. %s(%d)", strerror(errno), errno); + continue; + } + + switch (fork()) { + case 0: + break; + case -1: + syslog(LOG_ERR, "Fork failed. %s(%d)", strerror(errno), errno); + default: + close(nsk); + if (type == MROUTER) { + close(sk); + terminate = 1; + } + continue; + } + + close(sk); + + if (msdun && ms_dun(nsk, 1, msdun) < 0) { + syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno); + exit(0); + } + + ba2str(&sa.rc_bdaddr, ba); + sprintf(ch, "%d", channel); + + /* Setup environment */ + setenv("DUN_BDADDR", ba, 1); + setenv("DUN_CHANNEL", ch, 1); + + if (!dun_open_connection(nsk, pppd, pppd_opts, 0)) + syslog(LOG_INFO, "New connection from %s", ba); + + close(nsk); + exit(0); + } + + if (use_sdp) + dun_sdp_unregister(); + return 0; +} + +/* Connect and initiate RFCOMM session + * Returns: + * -1 - critical error (exit persist mode) + * 1 - non critical error + * 0 - success + */ +static int create_connection(char *dst, bdaddr_t *bdaddr, int mrouter) +{ + struct sockaddr_rc sa; + int sk, err = 0, ch; + + if (use_cache && cache.valid && cache.channel) { + /* Use cached channel */ + ch = cache.channel; + + } else if (!channel) { + syslog(LOG_INFO, "Searching for %s on %s", mrouter ? "SP" : "LAP", dst); + + if (dun_sdp_search(&src_addr, bdaddr, &ch, mrouter) <= 0) + return 0; + } else + ch = channel; + + syslog(LOG_INFO, "Connecting to %s channel %d", dst, ch); + + sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)", + strerror(errno), errno); + return -1; + } + + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = 0; + sa.rc_bdaddr = src_addr; + + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) + syslog(LOG_ERR, "Bind failed. %s(%d)", + strerror(errno), errno); + + sa.rc_channel = ch; + sa.rc_bdaddr = *bdaddr; + + if (!connect(sk, (struct sockaddr *) &sa, sizeof(sa)) ) { + if (mrouter) { + sleep(1); + close(sk); + return 0; + } + + syslog(LOG_INFO, "Connection established"); + + if (msdun && ms_dun(sk, 0, msdun) < 0) { + syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno); + err = 1; + goto out; + } + + if (!dun_open_connection(sk, pppd, pppd_opts, (persist > 0))) + err = 0; + else + err = 1; + } else { + syslog(LOG_ERR, "Connect to %s failed. %s(%d)", + dst, strerror(errno), errno); + err = 1; + } + +out: + if (use_cache) { + if (!err) { + /* Succesesful connection, validate cache */ + strcpy(cache.dst, dst); + bacpy(&cache.bdaddr, bdaddr); + cache.channel = ch; + cache.valid = use_cache; + } else { + cache.channel = 0; + cache.valid--; + } + } + + close(sk); + return err; +} + +/* Search and connect + * Returns: + * -1 - critical error (exit persist mode) + * 1 - non critical error + * 0 - success + */ +static int do_connect(void) +{ + inquiry_info *ii; + int reconnect = 0; + int i, n, r = 0; + + do { + if (reconnect) + sleep(persist); + reconnect = 1; + + if (cache.valid) { + /* Use cached bdaddr */ + r = create_connection(cache.dst, &cache.bdaddr, 0); + if (r < 0) { + terminate = 1; + break; + } + continue; + } + + syslog(LOG_INFO, "Inquiring"); + + /* FIXME: Should we use non general LAP here ? */ + + ii = NULL; + n = hci_inquiry(src_dev, search_duration, 0, NULL, &ii, 0); + if (n < 0) { + syslog(LOG_ERR, "Inquiry failed. %s(%d)", strerror(errno), errno); + continue; + } + + for (i = 0; i < n; i++) { + char dst[40]; + ba2str(&ii[i].bdaddr, dst); + + r = create_connection(dst, &ii[i].bdaddr, 0); + if (r < 0) { + terminate = 1; + break; + } + } + bt_free(ii); + } while (!terminate && persist); + + return r; +} + +static void do_show(void) +{ + dun_show_connections(); +} + +static void do_kill(char *dst) +{ + if (dst) { + bdaddr_t ba; + str2ba(dst, &ba); + dun_kill_connection((void *) &ba); + } else + dun_kill_all_connections(); +} + +static void sig_hup(int sig) +{ + return; +} + +static void sig_term(int sig) +{ + io_cancel(); + terminate = 1; +} + +static struct option main_lopts[] = { + { "help", 0, 0, 'h' }, + { "listen", 0, 0, 's' }, + { "connect", 1, 0, 'c' }, + { "search", 2, 0, 'Q' }, + { "kill", 1, 0, 'k' }, + { "killall", 0, 0, 'K' }, + { "channel", 1, 0, 'P' }, + { "device", 1, 0, 'i' }, + { "nosdp", 0, 0, 'D' }, + { "list", 0, 0, 'l' }, + { "show", 0, 0, 'l' }, + { "nodetach", 0, 0, 'n' }, + { "persist", 2, 0, 'p' }, + { "auth", 0, 0, 'A' }, + { "encrypt", 0, 0, 'E' }, + { "secure", 0, 0, 'S' }, + { "master", 0, 0, 'M' }, + { "cache", 0, 0, 'C' }, + { "pppd", 1, 0, 'd' }, + { "msdun", 2, 0, 'X' }, + { "activesync", 0, 0, 'a' }, + { "mrouter", 1, 0, 'm' }, + { "dialup", 0, 0, 'u' }, + { 0, 0, 0, 0 } +}; + +static const char *main_sopts = "hsc:k:Kr:i:lnp::DQ::AESMP:C::P:Xam:u"; + +static const char *main_help = + "Bluetooth LAP (LAN Access over PPP) daemon version %s\n" + "Usage:\n" + "\tdund [pppd options]\n" + "Options:\n" + "\t--show --list -l Show active LAP connections\n" + "\t--listen -s Listen for LAP connections\n" + "\t--dialup -u Pretend to be a dialup/telephone\n" + "\t--connect -c Create LAP connection\n" + "\t--mrouter -m Create mRouter connection\n" + "\t--search -Q[duration] Search and connect\n" + "\t--kill -k Kill LAP connection\n" + "\t--killall -K Kill all LAP connections\n" + "\t--channel -P RFCOMM channel\n" + "\t--device -i Source bdaddr\n" + "\t--nosdp -D Disable SDP\n" + "\t--auth -A Enable authentication\n" + "\t--encrypt -E Enable encryption\n" + "\t--secure -S Secure connection\n" + "\t--master -M Become the master of a piconet\n" + "\t--nodetach -n Do not become a daemon\n" + "\t--persist -p[interval] Persist mode\n" + "\t--pppd -d Location of the PPP daemon (pppd)\n" + "\t--msdun -X[timeo] Enable Microsoft dialup networking support\n" + "\t--activesync -a Enable Microsoft ActiveSync networking\n" + "\t--cache -C[valid] Enable address cache\n"; + +int main(int argc, char *argv[]) +{ + char *dst = NULL, *src = NULL; + struct sigaction sa; + int mode = NONE; + int opt; + + while ((opt=getopt_long(argc, argv, main_sopts, main_lopts, NULL)) != -1) { + switch(opt) { + case 'l': + mode = SHOW; + detach = 0; + break; + + case 's': + mode = LISTEN; + type = LANACCESS; + break; + + case 'c': + mode = CONNECT; + dst = strdup(optarg); + break; + + case 'Q': + mode = CONNECT; + dst = NULL; + if (optarg) + search_duration = atoi(optarg); + break; + + case 'k': + mode = KILL; + detach = 0; + dst = strdup(optarg); + break; + + case 'K': + mode = KILL; + detach = 0; + dst = NULL; + break; + + case 'P': + channel = atoi(optarg); + break; + + case 'i': + src = strdup(optarg); + break; + + case 'D': + use_sdp = 0; + break; + + case 'A': + auth = 1; + break; + + case 'E': + encrypt = 1; + break; + + case 'S': + secure = 1; + break; + + case 'M': + master = 1; + break; + + case 'n': + detach = 0; + break; + + case 'p': + if (optarg) + persist = atoi(optarg); + else + persist = 5; + break; + + case 'C': + if (optarg) + use_cache = atoi(optarg); + else + use_cache = 2; + break; + + case 'd': + pppd = strdup(optarg); + break; + + case 'X': + if (optarg) + msdun = atoi(optarg); + else + msdun = 10; + break; + + case 'a': + msdun = 10; + type = ACTIVESYNC; + break; + + case 'm': + mode = LISTEN; + dst = strdup(optarg); + type = MROUTER; + break; + + case 'u': + mode = LISTEN; + type = DIALUP; + break; + + case 'h': + default: + printf(main_help, VERSION); + exit(0); + } + } + + argc -= optind; + argv += optind; + + /* The rest is pppd options */ + if (argc > 0) { + for (opt = 3; argc && opt < DUN_MAX_PPP_OPTS - 1; + argc--, opt++) + pppd_opts[opt] = *argv++; + pppd_opts[opt] = NULL; + } + + io_init(); + + if (dun_init()) + return -1; + + /* Check non daemon modes first */ + switch (mode) { + case SHOW: + do_show(); + return 0; + + case KILL: + do_kill(dst); + return 0; + + case NONE: + printf(main_help, VERSION); + return 0; + } + + /* Initialize signals */ + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + if (detach && daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + + openlog("dund", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); + syslog(LOG_INFO, "Bluetooth DUN daemon version %s", VERSION); + + if (src) { + src_dev = hci_devid(src); + if (src_dev < 0 || hci_devba(src_dev, &src_addr) < 0) { + syslog(LOG_ERR, "Invalid source. %s(%d)", strerror(errno), errno); + return -1; + } + } + + if (dst) { + strncpy(cache.dst, dst, sizeof(cache.dst) - 1); + str2ba(dst, &cache.bdaddr); + + /* Disable cache invalidation */ + use_cache = cache.valid = ~0; + } + + switch (mode) { + case CONNECT: + do_connect(); + break; + + case LISTEN: + do_listen(); + break; + } + + return 0; +} diff --git a/compat/dund.h b/compat/dund.h new file mode 100644 index 000000000..0408e0989 --- /dev/null +++ b/compat/dund.h @@ -0,0 +1,40 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#define DUN_CONFIG_DIR "/etc/bluetooth/dun" + +#define DUN_DEFAULT_CHANNEL 1 + +#define DUN_MAX_PPP_OPTS 40 + +int dun_init(void); +int dun_cleanup(void); + +int dun_show_connections(void); +int dun_kill_connection(uint8_t *dst); +int dun_kill_all_connections(void); + +int dun_open_connection(int sk, char *pppd, char **pppd_opts, int wait); + +int ms_dun(int fd, int server, int timeo); diff --git a/compat/fakehid.c b/compat/fakehid.c new file mode 100644 index 000000000..438185d3b --- /dev/null +++ b/compat/fakehid.c @@ -0,0 +1,669 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hidd.h" +#include "uinput.h" + +#include + +#ifdef NEED_PPOLL +#include "ppoll.h" +#endif + +static volatile sig_atomic_t __io_canceled = 0; + +static void sig_hup(int sig) +{ +} + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static void send_event(int fd, uint16_t type, uint16_t code, int32_t value) +{ + struct uinput_event event; + int len; + + if (fd <= fileno(stderr)) + return; + + memset(&event, 0, sizeof(event)); + event.type = type; + event.code = code; + event.value = value; + + len = write(fd, &event, sizeof(event)); +} + +static int uinput_create(char *name, int keyboard, int mouse) +{ + struct uinput_dev dev; + int fd, aux; + + fd = open("/dev/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/input/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/misc/uinput", O_RDWR); + if (fd < 0) { + fprintf(stderr, "Can't open input device: %s (%d)\n", + strerror(errno), errno); + return -1; + } + } + } + + memset(&dev, 0, sizeof(dev)); + + if (name) + strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1); + + dev.id.bustype = BUS_BLUETOOTH; + dev.id.vendor = 0x0000; + dev.id.product = 0x0000; + dev.id.version = 0x0000; + + if (write(fd, &dev, sizeof(dev)) < 0) { + fprintf(stderr, "Can't write device information: %s (%d)\n", + strerror(errno), errno); + close(fd); + return -1; + } + + if (mouse) { + ioctl(fd, UI_SET_EVBIT, EV_REL); + + for (aux = REL_X; aux <= REL_MISC; aux++) + ioctl(fd, UI_SET_RELBIT, aux); + } + + if (keyboard) { + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_EVBIT, EV_LED); + ioctl(fd, UI_SET_EVBIT, EV_REP); + + for (aux = KEY_RESERVED; aux <= KEY_UNKNOWN; aux++) + ioctl(fd, UI_SET_KEYBIT, aux); + + //for (aux = LED_NUML; aux <= LED_MISC; aux++) + // ioctl(fd, UI_SET_LEDBIT, aux); + } + + if (mouse) { + ioctl(fd, UI_SET_EVBIT, EV_KEY); + + for (aux = BTN_LEFT; aux <= BTN_BACK; aux++) + ioctl(fd, UI_SET_KEYBIT, aux); + } + + ioctl(fd, UI_DEV_CREATE); + + return fd; +} + +static int rfcomm_connect(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + struct sockaddr_rc addr; + int sk; + + sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + fprintf(stderr, "Can't create socket: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Can't bind socket: %s (%d)\n", + strerror(errno), errno); + close(sk); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Can't connect: %s (%d)\n", + strerror(errno), errno); + close(sk); + return -1; + } + + return sk; +} + +static void func(int fd) +{ +} + +static void back(int fd) +{ +} + +static void next(int fd) +{ +} + +static void button(int fd, unsigned int button, int is_press) +{ + switch (button) { + case 1: + send_event(fd, EV_KEY, BTN_LEFT, is_press); + break; + case 3: + send_event(fd, EV_KEY, BTN_RIGHT, is_press); + break; + } + + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static void move(int fd, unsigned int direction) +{ + double angle; + int32_t x, y; + + angle = (direction * 22.5) * 3.1415926 / 180; + x = (int) (sin(angle) * 8); + y = (int) (cos(angle) * -8); + + send_event(fd, EV_REL, REL_X, x); + send_event(fd, EV_REL, REL_Y, y); + + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static inline void epox_decode(int fd, unsigned char event) +{ + switch (event) { + case 48: + func(fd); break; + case 55: + back(fd); break; + case 56: + next(fd); break; + case 53: + button(fd, 1, 1); break; + case 121: + button(fd, 1, 0); break; + case 113: + break; + case 54: + button(fd, 3, 1); break; + case 120: + button(fd, 3, 0); break; + case 112: + break; + case 51: + move(fd, 0); break; + case 97: + move(fd, 1); break; + case 65: + move(fd, 2); break; + case 98: + move(fd, 3); break; + case 50: + move(fd, 4); break; + case 99: + move(fd, 5); break; + case 67: + move(fd, 6); break; + case 101: + move(fd, 7); break; + case 52: + move(fd, 8); break; + case 100: + move(fd, 9); break; + case 66: + move(fd, 10); break; + case 102: + move(fd, 11); break; + case 49: + move(fd, 12); break; + case 103: + move(fd, 13); break; + case 57: + move(fd, 14); break; + case 104: + move(fd, 15); break; + case 69: + break; + default: + printf("Unknown event code %d\n", event); + break; + } +} + +int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("Bluetooth Presenter", 0, 1); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + epox_decode(fd, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} + +int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + printf("Not implemented\n"); + return -1; +} + +/* The strange meta key close to Ctrl has been assigned to Esc, + Fn key to CtrlR and the left space to Alt*/ + +static unsigned char jthree_keycodes[63] = { + KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, + KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, + KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, + KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, + KEY_LEFTALT, KEY_TAB, KEY_CAPSLOCK, KEY_ESC, + KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, + KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, + KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_ENTER, + KEY_N, KEY_M, KEY_COMMA, KEY_DOT, KEY_SLASH, KEY_UP, + KEY_SPACE, KEY_COMPOSE, KEY_LEFT, KEY_DOWN, KEY_RIGHT, + KEY_LEFTCTRL, KEY_RIGHTSHIFT, KEY_LEFTSHIFT, KEY_DELETE, KEY_RIGHTCTRL, KEY_RIGHTALT, +}; + +static inline void jthree_decode(int fd, unsigned char event) +{ + if (event > 63) + send_event(fd, EV_KEY, jthree_keycodes[event & 0x3f], 0); + else + send_event(fd, EV_KEY, jthree_keycodes[event - 1], 1); +} + +int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("J-Three Keyboard", 1, 0); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + jthree_decode(fd, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} + +static const int celluon_xlate_num[10] = { + KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 +}; + +static const int celluon_xlate_char[26] = { + KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, + KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, + KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z +}; + +static int celluon_xlate(int c) +{ + if (c >= '0' && c <= '9') + return celluon_xlate_num[c - '0']; + + if (c >= 'A' && c <= 'Z') + return celluon_xlate_char[c - 'A']; + + switch (c) { + case 0x08: + return KEY_BACKSPACE; + case 0x09: + return KEY_TAB; + case 0x0d: + return KEY_ENTER; + case 0x11: + return KEY_LEFTCTRL; + case 0x14: + return KEY_CAPSLOCK; + case 0x20: + return KEY_SPACE; + case 0x25: + return KEY_LEFT; + case 0x26: + return KEY_UP; + case 0x27: + return KEY_RIGHT; + case 0x28: + return KEY_DOWN; + case 0x2e: + return KEY_DELETE; + case 0x5b: + return KEY_MENU; + case 0xa1: + return KEY_RIGHTSHIFT; + case 0xa0: + return KEY_LEFTSHIFT; + case 0xba: + return KEY_SEMICOLON; + case 0xbd: + return KEY_MINUS; + case 0xbc: + return KEY_COMMA; + case 0xbb: + return KEY_EQUAL; + case 0xbe: + return KEY_DOT; + case 0xbf: + return KEY_SLASH; + case 0xc0: + return KEY_GRAVE; + case 0xdb: + return KEY_LEFTBRACE; + case 0xdc: + return KEY_BACKSLASH; + case 0xdd: + return KEY_RIGHTBRACE; + case 0xde: + return KEY_APOSTROPHE; + case 0xff03: + return KEY_HOMEPAGE; + case 0xff04: + return KEY_TIME; + case 0xff06: + return KEY_OPEN; + case 0xff07: + return KEY_LIST; + case 0xff08: + return KEY_MAIL; + case 0xff30: + return KEY_CALC; + case 0xff1a: /* Map FN to ALT */ + return KEY_LEFTALT; + case 0xff2f: + return KEY_INFO; + default: + printf("Unknown key %x\n", c); + return c; + } +} + +struct celluon_state { + int len; /* Expected length of current packet */ + int count; /* Number of bytes received */ + int action; + int key; +}; + +static void celluon_decode(int fd, struct celluon_state *s, uint8_t c) +{ + if (s->count < 2 && c != 0xa5) { + /* Lost Sync */ + s->count = 0; + return; + } + + switch (s->count) { + case 0: + /* New packet - Reset state */ + s->len = 30; + s->key = 0; + break; + case 1: + break; + case 6: + s->action = c; + break; + case 28: + s->key = c; + if (c == 0xff) + s->len = 31; + break; + case 29: + case 30: + if (s->count == s->len - 1) { + /* TODO: Verify checksum */ + if (s->action < 2) { + send_event(fd, EV_KEY, celluon_xlate(s->key), + s->action); + } + s->count = -1; + } else { + s->key = (s->key << 8) | c; + } + break; + } + + s->count++; + + return; +} + +int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + struct celluon_state s; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("Celluon Keyboard", 1, 0); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + memset(&s, 0, sizeof(s)); + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + celluon_decode(fd, &s, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} diff --git a/compat/fakehid.txt b/compat/fakehid.txt new file mode 100644 index 000000000..000d0ee2f --- /dev/null +++ b/compat/fakehid.txt @@ -0,0 +1,134 @@ +EPox Presenter +============== + +# hcitool inq +Inquiring ... + 00:04:61:aa:bb:cc clock offset: 0x1ded class: 0x004000 + +# hcitool info 00:04:61:aa:bb:cc +Requesting information ... + BD Address: 00:04:61:aa:bb:cc + OUI Company: EPOX Computer Co., Ltd. (00-04-61) + Device Name: EPox BT-PM01B aabbcc + LMP Version: 1.1 (0x1) LMP Subversion: 0xf78 + Manufacturer: Cambridge Silicon Radio (10) + Features: 0xff 0xff 0x0f 0x00 0x00 0x00 0x00 0x00 + <3-slot packets> <5-slot packets> + + + + + +# sdptool records --raw 00:04:61:aa:bb:cc +Sequence + Attribute 0x0000 - ServiceRecordHandle + UINT32 0x00010000 + Attribute 0x0001 - ServiceClassIDList + Sequence + UUID16 0x1101 - SerialPort + Attribute 0x0004 - ProtocolDescriptorList + Sequence + Sequence + UUID16 0x0100 - L2CAP + Sequence + UUID16 0x0003 - RFCOMM + UINT8 0x01 + Attribute 0x0100 + String Cable Replacement + + +J-Three Keyboard +================ + +# hcitool inq +Inquiring ... + 00:0A:3A:aa:bb:cc clock offset: 0x3039 class: 0x001f00 + +# hcitool info 00:0A:3A:aa:bb:cc +Password: +Requesting information ... + BD Address: 00:0A:3A:aa:bb:cc + OUI Company: J-THREE INTERNATIONAL Holding Co., Ltd. (00-0A-3A) + Device Name: KEYBOARD + LMP Version: 1.1 (0x1) LMP Subversion: 0x2c2 + Manufacturer: Cambridge Silicon Radio (10) + Features: 0xbc 0x06 0x07 0x00 0x00 0x00 0x00 0x00 + + + + +# sdptool records --raw 00:0A:3A:aa:bb:cc +Sequence + Attribute 0x0000 - ServiceRecordHandle + UINT32 0x00010000 + Attribute 0x0001 - ServiceClassIDList + Sequence + UUID16 0x1101 - SerialPort + Attribute 0x0004 - ProtocolDescriptorList + Sequence + Sequence + UUID16 0x0100 - L2CAP + Sequence + UUID16 0x0003 - RFCOMM + UINT8 0x01 + Attribute 0x0006 - LanguageBaseAttributeIDList + Sequence + UINT16 0x656e + UINT16 0x006a + UINT16 0x0100 + Attribute 0x0100 + String SPP slave + + +Celluon Laserkey Keyboard +========================= + +# hcitool inq +Inquiring ... + 00:0B:24:aa:bb:cc clock offset: 0x3ab6 class: 0x400210 + +# hcitool info 00:0B:24:aa:bb:cc +Requesting information ... + BD Address: 00:0B:24:aa:bb:cc + OUI Company: AirLogic (00-0B-24) + Device Name: CL800BT + LMP Version: 1.1 (0x1) LMP Subversion: 0x291 + Manufacturer: Cambridge Silicon Radio (10) + Features: 0xff 0xff 0x0f 0x00 0x00 0x00 0x00 0x00 + <3-slot packets> <5-slot packets> + + + + + +# sdptool records --raw 00:0B:24:aa:bb:cc +Sequence + Attribute 0x0000 - ServiceRecordHandle + UINT32 0x00010000 + Attribute 0x0001 - ServiceClassIDList + Sequence + UUID16 0x1101 - SerialPort + Attribute 0x0004 - ProtocolDescriptorList + Sequence + Sequence + UUID16 0x0100 - L2CAP + Sequence + UUID16 0x0003 - RFCOMM + UINT8 0x01 + Attribute 0x0100 + String Serial Port + +Packet format is as follows (all fields little-endian): + 0 uint16 magic # 0x5a5a + 2 uint32 unknown # ??? + 6 uint8 action # 0 = keyup, 1 = keydown, 2 = repeat + # 3, 4, 5, 6 = ??? (Mouse mode) + 7 uint8 unknown[9] # ??? + 16 uint8 action2 # ??? same as action + 17 uint16 x # Horizontal coordinate + 19 uint16 y # Vertical coordinate + 21 uint16 time # Some sort of timestamp + 23 uint8 unknown[5] # ??? + 28 uint8 key[] # single byte keycode or 0xff byte + # follwed by special keycode byte. + Each packet followed by a checksum byte. diff --git a/compat/hidd.1 b/compat/hidd.1 new file mode 100644 index 000000000..b186ac247 --- /dev/null +++ b/compat/hidd.1 @@ -0,0 +1,41 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.33. +.TH HIDD "1" "May 2004" "hidd - Bluetooth HID daemon" "User Commands" +.SH NAME +hidd \- Bluetooth HID daemon +.SH DESCRIPTION +hidd - Bluetooth HID daemon +.SS "Usage:" +.IP +hidd [options] [commands] +.SH OPTIONS +.TP +\fB\-i\fR +Local HCI device or BD Address +.TP +\fB\-t\fR +Set idle timeout (in minutes) +.TP +\fB\-n\fR, \fB\-\-nodaemon\fR +Don't fork daemon to background +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help +.SS "Commands:" +.TP +\fB\-\-server\fR +Start HID server +.TP +\fB\-\-search\fR +Search for HID devices +.TP +\fB\-\-connect\fR +Connect remote HID device +.TP +\fB\-\-kill\fR +Terminate HID connection +.TP +\fB\-\-killall\fR +Terminate all connections +.TP +\fB\-\-show\fR +List current HID connections diff --git a/compat/hidd.c b/compat/hidd.c new file mode 100644 index 000000000..212c09269 --- /dev/null +++ b/compat/hidd.c @@ -0,0 +1,861 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sdp.h" +#include "hidd.h" + +#ifdef NEED_PPOLL +#include "ppoll.h" +#endif + +enum { + NONE, + SHOW, + SERVER, + SEARCH, + CONNECT, + KILL +}; + +static volatile sig_atomic_t __io_canceled = 0; + +static void sig_hup(int sig) +{ +} + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static int l2cap_connect(bdaddr_t *src, bdaddr_t *dst, unsigned short psm) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + int sk; + + if ((sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + memset(&opts, 0, sizeof(opts)); + opts.imtu = HIDP_DEFAULT_MTU; + opts.omtu = HIDP_DEFAULT_MTU; + opts.flush_to = 0xffff; + + setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)); + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(psm); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + return sk; +} + +static int l2cap_listen(const bdaddr_t *bdaddr, unsigned short psm, int lm, int backlog) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + int sk; + + if ((sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, bdaddr); + addr.l2_psm = htobs(psm); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + setsockopt(sk, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm)); + + memset(&opts, 0, sizeof(opts)); + opts.imtu = HIDP_DEFAULT_MTU; + opts.omtu = HIDP_DEFAULT_MTU; + opts.flush_to = 0xffff; + + setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)); + + if (listen(sk, backlog) < 0) { + close(sk); + return -1; + } + + return sk; +} + +static int l2cap_accept(int sk, bdaddr_t *bdaddr) +{ + struct sockaddr_l2 addr; + socklen_t addrlen; + int nsk; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if ((nsk = accept(sk, (struct sockaddr *) &addr, &addrlen)) < 0) + return -1; + + if (bdaddr) + bacpy(bdaddr, &addr.l2_bdaddr); + + return nsk; +} + +static int request_authentication(bdaddr_t *src, bdaddr_t *dst) +{ + struct hci_conn_info_req *cr; + char addr[18]; + int err, dd, dev_id; + + ba2str(src, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) + return dev_id; + + dd = hci_open_dev(dev_id); + if (dd < 0) + return dd; + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) + return -ENOMEM; + + bacpy(&cr->bdaddr, dst); + cr->type = ACL_LINK; + err = ioctl(dd, HCIGETCONNINFO, (unsigned long) cr); + if (err < 0) { + free(cr); + hci_close_dev(dd); + return err; + } + + err = hci_authenticate_link(dd, htobs(cr->conn_info->handle), 25000); + + free(cr); + hci_close_dev(dd); + + return err; +} + +static int request_encryption(bdaddr_t *src, bdaddr_t *dst) +{ + struct hci_conn_info_req *cr; + char addr[18]; + int err, dd, dev_id; + + ba2str(src, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) + return dev_id; + + dd = hci_open_dev(dev_id); + if (dd < 0) + return dd; + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) + return -ENOMEM; + + bacpy(&cr->bdaddr, dst); + cr->type = ACL_LINK; + err = ioctl(dd, HCIGETCONNINFO, (unsigned long) cr); + if (err < 0) { + free(cr); + hci_close_dev(dd); + return err; + } + + err = hci_encrypt_link(dd, htobs(cr->conn_info->handle), 1, 25000); + + free(cr); + hci_close_dev(dd); + + return err; +} + +static void enable_sixaxis(int csk) +{ + const unsigned char buf[] = { + 0x53 /*HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE*/, + 0xf4, 0x42, 0x03, 0x00, 0x00 }; + int err; + + err = write(csk, buf, sizeof(buf)); +} + +static int create_device(int ctl, int csk, int isk, uint8_t subclass, int nosdp, int nocheck, int bootonly, int encrypt, int timeout) +{ + struct hidp_connadd_req req; + struct sockaddr_l2 addr; + socklen_t addrlen; + bdaddr_t src, dst; + char bda[18]; + int err; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if (getsockname(csk, (struct sockaddr *) &addr, &addrlen) < 0) + return -1; + + bacpy(&src, &addr.l2_bdaddr); + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if (getpeername(csk, (struct sockaddr *) &addr, &addrlen) < 0) + return -1; + + bacpy(&dst, &addr.l2_bdaddr); + + memset(&req, 0, sizeof(req)); + req.ctrl_sock = csk; + req.intr_sock = isk; + req.flags = 0; + req.idle_to = timeout * 60; + + err = get_stored_device_info(&src, &dst, &req); + if (!err) + goto create; + + if (!nocheck) { + ba2str(&dst, bda); + syslog(LOG_ERR, "Rejected connection from unknown device %s", bda); + /* Return no error to avoid run_server() complaining too */ + return 0; + } + + if (!nosdp) { + err = get_sdp_device_info(&src, &dst, &req); + if (err < 0) + goto error; + } else { + struct l2cap_conninfo conn; + socklen_t size; + uint8_t class[3]; + + memset(&conn, 0, sizeof(conn)); + size = sizeof(conn); + if (getsockopt(csk, SOL_L2CAP, L2CAP_CONNINFO, &conn, &size) < 0) + memset(class, 0, 3); + else + memcpy(class, conn.dev_class, 3); + + if (class[1] == 0x25 && (class[2] == 0x00 || class[2] == 0x01)) + req.subclass = class[0]; + else + req.subclass = 0xc0; + } + +create: + if (subclass != 0x00) + req.subclass = subclass; + + ba2str(&dst, bda); + syslog(LOG_INFO, "New HID device %s (%s)", bda, req.name); + + if (encrypt && (req.subclass & 0x40)) { + err = request_authentication(&src, &dst); + if (err < 0) { + syslog(LOG_ERR, "Authentication for %s failed", bda); + goto error; + } + + err = request_encryption(&src, &dst); + if (err < 0) + syslog(LOG_ERR, "Encryption for %s failed", bda); + } + + if (bootonly) { + req.rd_size = 0; + req.flags |= (1 << HIDP_BOOT_PROTOCOL_MODE); + } + + if (req.vendor == 0x054c && req.product == 0x0268) + enable_sixaxis(csk); + + err = ioctl(ctl, HIDPCONNADD, &req); + +error: + if (req.rd_data) + free(req.rd_data); + + return err; +} + +static void run_server(int ctl, int csk, int isk, uint8_t subclass, int nosdp, int nocheck, int bootonly, int encrypt, int timeout) +{ + struct pollfd p[2]; + sigset_t sigs; + short events; + int err, ncsk, nisk; + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p[0].fd = csk; + p[0].events = POLLIN | POLLERR | POLLHUP; + + p[1].fd = isk; + p[1].events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p[0].revents = 0; + p[1].revents = 0; + + if (ppoll(p, 2, NULL, &sigs) < 1) + continue; + + events = p[0].revents | p[1].revents; + + if (events & POLLIN) { + ncsk = l2cap_accept(csk, NULL); + nisk = l2cap_accept(isk, NULL); + + err = create_device(ctl, ncsk, nisk, subclass, nosdp, nocheck, bootonly, encrypt, timeout); + if (err < 0) + syslog(LOG_ERR, "HID create error %d (%s)", + errno, strerror(errno)); + + close(nisk); + sleep(1); + close(ncsk); + } + } +} + +static char *hidp_state[] = { + "unknown", + "connected", + "open", + "bound", + "listening", + "connecting", + "connecting", + "config", + "disconnecting", + "closed" +}; + +static char *hidp_flagstostr(uint32_t flags) +{ + static char str[100]; + str[0] = 0; + + strcat(str, "["); + + if (flags & (1 << HIDP_BOOT_PROTOCOL_MODE)) + strcat(str, "boot-protocol"); + + strcat(str, "]"); + + return str; +} + +static void do_show(int ctl) +{ + struct hidp_connlist_req req; + struct hidp_conninfo ci[16]; + char addr[18]; + unsigned int i; + + req.cnum = 16; + req.ci = ci; + + if (ioctl(ctl, HIDPGETCONNLIST, &req) < 0) { + perror("Can't get connection list"); + close(ctl); + exit(1); + } + + for (i = 0; i < req.cnum; i++) { + ba2str(&ci[i].bdaddr, addr); + printf("%s %s [%04x:%04x] %s %s\n", addr, ci[i].name, + ci[i].vendor, ci[i].product, hidp_state[ci[i].state], + ci[i].flags ? hidp_flagstostr(ci[i].flags) : ""); + } +} + +static void do_connect(int ctl, bdaddr_t *src, bdaddr_t *dst, uint8_t subclass, int fakehid, int bootonly, int encrypt, int timeout) +{ + struct hidp_connadd_req req; + uint16_t uuid = HID_SVCLASS_ID; + uint8_t channel = 0; + char name[256]; + int csk, isk, err; + + memset(&req, 0, sizeof(req)); + + err = get_sdp_device_info(src, dst, &req); + if (err < 0 && fakehid) + err = get_alternate_device_info(src, dst, + &uuid, &channel, name, sizeof(name) - 1); + + if (err < 0) { + perror("Can't get device information"); + close(ctl); + exit(1); + } + + switch (uuid) { + case HID_SVCLASS_ID: + goto connect; + + case SERIAL_PORT_SVCLASS_ID: + if (subclass == 0x40 || !strcmp(name, "Cable Replacement")) { + if (epox_presenter(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + if (subclass == 0x1f || !strcmp(name, "SPP slave")) { + if (jthree_keyboard(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + if (subclass == 0x02 || !strcmp(name, "Serial Port")) { + if (celluon_keyboard(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + break; + + case HEADSET_SVCLASS_ID: + case HANDSFREE_SVCLASS_ID: + if (headset_presenter(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + + return; + +connect: + csk = l2cap_connect(src, dst, L2CAP_PSM_HIDP_CTRL); + if (csk < 0) { + perror("Can't create HID control channel"); + close(ctl); + exit(1); + } + + isk = l2cap_connect(src, dst, L2CAP_PSM_HIDP_INTR); + if (isk < 0) { + perror("Can't create HID interrupt channel"); + close(csk); + close(ctl); + exit(1); + } + + err = create_device(ctl, csk, isk, subclass, 1, 1, bootonly, encrypt, timeout); + if (err < 0) { + fprintf(stderr, "HID create error %d (%s)\n", + errno, strerror(errno)); + close(isk); + sleep(1); + close(csk); + close(ctl); + exit(1); + } +} + +static void do_search(int ctl, bdaddr_t *bdaddr, uint8_t subclass, int fakehid, int bootonly, int encrypt, int timeout) +{ + inquiry_info *info = NULL; + bdaddr_t src, dst; + int i, dev_id, num_rsp, length, flags; + char addr[18]; + uint8_t class[3]; + + ba2str(bdaddr, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) { + dev_id = hci_get_route(NULL); + hci_devba(dev_id, &src); + } else + bacpy(&src, bdaddr); + + length = 8; /* ~10 seconds */ + num_rsp = 0; + flags = IREQ_CACHE_FLUSH; + + printf("Searching ...\n"); + + num_rsp = hci_inquiry(dev_id, length, num_rsp, NULL, &info, flags); + + for (i = 0; i < num_rsp; i++) { + memcpy(class, (info+i)->dev_class, 3); + if (class[1] == 0x25 && (class[2] == 0x00 || class[2] == 0x01)) { + bacpy(&dst, &(info+i)->bdaddr); + ba2str(&dst, addr); + + printf("\tConnecting to device %s\n", addr); + do_connect(ctl, &src, &dst, subclass, fakehid, bootonly, encrypt, timeout); + } + } + + if (!fakehid) + goto done; + + for (i = 0; i < num_rsp; i++) { + memcpy(class, (info+i)->dev_class, 3); + if ((class[0] == 0x00 && class[2] == 0x00 && + (class[1] == 0x40 || class[1] == 0x1f)) || + (class[0] == 0x10 && class[1] == 0x02 && class[2] == 0x40)) { + bacpy(&dst, &(info+i)->bdaddr); + ba2str(&dst, addr); + + printf("\tConnecting to device %s\n", addr); + do_connect(ctl, &src, &dst, subclass, 1, bootonly, 0, timeout); + } + } + +done: + bt_free(info); + + if (!num_rsp) { + fprintf(stderr, "\tNo devices in range or visible\n"); + close(ctl); + exit(1); + } +} + +static void do_kill(int ctl, bdaddr_t *bdaddr, uint32_t flags) +{ + struct hidp_conndel_req req; + struct hidp_connlist_req cl; + struct hidp_conninfo ci[16]; + unsigned int i; + + if (!bacmp(bdaddr, BDADDR_ALL)) { + cl.cnum = 16; + cl.ci = ci; + + if (ioctl(ctl, HIDPGETCONNLIST, &cl) < 0) { + perror("Can't get connection list"); + close(ctl); + exit(1); + } + + for (i = 0; i < cl.cnum; i++) { + bacpy(&req.bdaddr, &ci[i].bdaddr); + req.flags = flags; + + if (ioctl(ctl, HIDPCONNDEL, &req) < 0) { + perror("Can't release connection"); + close(ctl); + exit(1); + } + } + + } else { + bacpy(&req.bdaddr, bdaddr); + req.flags = flags; + + if (ioctl(ctl, HIDPCONNDEL, &req) < 0) { + perror("Can't release connection"); + close(ctl); + exit(1); + } + } +} + +static void usage(void) +{ + printf("hidd - Bluetooth HID daemon version %s\n\n", VERSION); + + printf("Usage:\n" + "\thidd [options] [commands]\n" + "\n"); + + printf("Options:\n" + "\t-i Local HCI device or BD Address\n" + "\t-t Set idle timeout (in minutes)\n" + "\t-b Overwrite the boot mode subclass\n" + "\t-n, --nodaemon Don't fork daemon to background\n" + "\t-h, --help Display help\n" + "\n"); + + printf("Commands:\n" + "\t--server Start HID server\n" + "\t--search Search for HID devices\n" + "\t--connect Connect remote HID device\n" + "\t--unplug Unplug the HID connection\n" + "\t--kill Terminate HID connection\n" + "\t--killall Terminate all connections\n" + "\t--show List current HID connections\n" + "\n"); +} + +static struct option main_options[] = { + { "help", 0, 0, 'h' }, + { "nodaemon", 0, 0, 'n' }, + { "subclass", 1, 0, 'b' }, + { "timeout", 1, 0, 't' }, + { "device", 1, 0, 'i' }, + { "master", 0, 0, 'M' }, + { "encrypt", 0, 0, 'E' }, + { "nosdp", 0, 0, 'D' }, + { "nocheck", 0, 0, 'Z' }, + { "bootonly", 0, 0, 'B' }, + { "hidonly", 0, 0, 'H' }, + { "show", 0, 0, 'l' }, + { "list", 0, 0, 'l' }, + { "server", 0, 0, 'd' }, + { "listen", 0, 0, 'd' }, + { "search", 0, 0, 's' }, + { "create", 1, 0, 'c' }, + { "connect", 1, 0, 'c' }, + { "disconnect", 1, 0, 'k' }, + { "terminate", 1, 0, 'k' }, + { "release", 1, 0, 'k' }, + { "kill", 1, 0, 'k' }, + { "killall", 0, 0, 'K' }, + { "unplug", 1, 0, 'u' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + bdaddr_t bdaddr, dev; + uint32_t flags = 0; + uint8_t subclass = 0x00; + char addr[18]; + int log_option = LOG_NDELAY | LOG_PID; + int opt, ctl, csk, isk; + int mode = SHOW, detach = 1, nosdp = 0, nocheck = 0, bootonly = 0; + int fakehid = 1, encrypt = 0, timeout = 30, lm = 0; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt = getopt_long(argc, argv, "+i:nt:b:MEDZBHldsc:k:Ku:h", main_options, NULL)) != -1) { + switch(opt) { + case 'i': + if (!strncasecmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &bdaddr); + else + str2ba(optarg, &bdaddr); + break; + case 'n': + detach = 0; + break; + case 't': + timeout = atoi(optarg); + break; + case 'b': + if (!strncasecmp(optarg, "0x", 2)) + subclass = (uint8_t) strtol(optarg, NULL, 16); + else + subclass = atoi(optarg); + break; + case 'M': + lm |= L2CAP_LM_MASTER; + break; + case 'E': + encrypt = 1; + break; + case 'D': + nosdp = 1; + break; + case 'Z': + nocheck = 1; + break; + case 'B': + bootonly = 1; + break; + case 'H': + fakehid = 0; + break; + case 'l': + mode = SHOW; + break; + case 'd': + mode = SERVER; + break; + case 's': + mode = SEARCH; + break; + case 'c': + str2ba(optarg, &dev); + mode = CONNECT; + break; + case 'k': + str2ba(optarg, &dev); + mode = KILL; + break; + case 'K': + bacpy(&dev, BDADDR_ALL); + mode = KILL; + break; + case 'u': + str2ba(optarg, &dev); + flags = (1 << HIDP_VIRTUAL_CABLE_UNPLUG); + mode = KILL; + break; + case 'h': + usage(); + exit(0); + default: + exit(0); + } + } + + ba2str(&bdaddr, addr); + + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); + if (ctl < 0) { + perror("Can't open HIDP control socket"); + exit(1); + } + + switch (mode) { + case SERVER: + csk = l2cap_listen(&bdaddr, L2CAP_PSM_HIDP_CTRL, lm, 10); + if (csk < 0) { + perror("Can't listen on HID control channel"); + close(ctl); + exit(1); + } + + isk = l2cap_listen(&bdaddr, L2CAP_PSM_HIDP_INTR, lm, 10); + if (isk < 0) { + perror("Can't listen on HID interrupt channel"); + close(ctl); + close(csk); + exit(1); + } + break; + + case SEARCH: + do_search(ctl, &bdaddr, subclass, fakehid, bootonly, encrypt, timeout); + close(ctl); + exit(0); + + case CONNECT: + do_connect(ctl, &bdaddr, &dev, subclass, fakehid, bootonly, encrypt, timeout); + close(ctl); + exit(0); + + case KILL: + do_kill(ctl, &dev, flags); + close(ctl); + exit(0); + + default: + do_show(ctl); + close(ctl); + exit(0); + } + + if (detach) { + if (daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + } else + log_option |= LOG_PERROR; + + openlog("hidd", log_option, LOG_DAEMON); + + if (bacmp(&bdaddr, BDADDR_ANY)) + syslog(LOG_INFO, "Bluetooth HID daemon (%s)", addr); + else + syslog(LOG_INFO, "Bluetooth HID daemon"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + run_server(ctl, csk, isk, subclass, nosdp, nocheck, bootonly, encrypt, timeout); + + syslog(LOG_INFO, "Exit"); + + close(csk); + close(isk); + close(ctl); + + return 0; +} diff --git a/compat/hidd.h b/compat/hidd.h new file mode 100644 index 000000000..362815a83 --- /dev/null +++ b/compat/hidd.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#define L2CAP_PSM_HIDP_CTRL 0x11 +#define L2CAP_PSM_HIDP_INTR 0x13 + +int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); diff --git a/compat/lib.h b/compat/lib.h new file mode 100644 index 000000000..d6373a7c8 --- /dev/null +++ b/compat/lib.h @@ -0,0 +1,86 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2009 Marcel Holtmann + * + * + * 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 + * + */ + +#include +#include +#include + +#ifndef min +#define min(a,b) ( (a)<(b) ? (a):(b) ) +#endif + +/* IO cancelation */ +extern volatile sig_atomic_t __io_canceled; + +static inline void io_init(void) +{ + __io_canceled = 0; +} + +static inline void io_cancel(void) +{ + __io_canceled = 1; +} + +/* Read exactly len bytes (Signal safe)*/ +static inline int read_n(int fd, char *buf, int len) +{ + register int t = 0, w; + + while (!__io_canceled && len > 0) { + if ((w = read(fd, buf, len)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!w) + return 0; + len -= w; + buf += w; + t += w; + } + + return t; +} + +/* Write exactly len bytes (Signal safe)*/ +static inline int write_n(int fd, char *buf, int len) +{ + register int t = 0, w; + + while (!__io_canceled && len > 0) { + if ((w = write(fd, buf, len)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!w) + return 0; + len -= w; + buf += w; + t += w; + } + + return t; +} diff --git a/compat/msdun.c b/compat/msdun.c new file mode 100644 index 000000000..1759ef666 --- /dev/null +++ b/compat/msdun.c @@ -0,0 +1,153 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib.h" +#include "dund.h" + +#define MS_PPP 2 +#define MS_SUCCESS 1 +#define MS_FAILED -1 +#define MS_TIMEOUT -2 + +static sigjmp_buf jmp; +static int retry; +static int timeout; + +static void sig_alarm(int sig) +{ + siglongjmp(jmp, MS_TIMEOUT); +} + +static int w4_str(int fd, char *str) +{ + char buf[40]; + unsigned len = 0; + int r; + + while (1) { + r = read(fd, buf + len, sizeof(buf) - len - 1); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + break; + } + if (!r) + break; + + len += r; + + if (len < strlen(str)) + continue; + buf[len] = 0; + + if (strstr(buf, str)) + return MS_SUCCESS; + + /* Detect PPP */ + if (strchr(buf, '~')) + return MS_PPP; + } + return MS_FAILED; +} + +static int ms_server(int fd) +{ + switch (w4_str(fd, "CLIENT")) { + case MS_SUCCESS: + write_n(fd, "CLIENTSERVER", 12); + case MS_PPP: + return MS_SUCCESS; + default: + return MS_FAILED; + } +} + +static int ms_client(int fd) +{ + write_n(fd, "CLIENT", 6); + return w4_str(fd, "CLIENTSERVER"); +} + +int ms_dun(int fd, int server, int timeo) +{ + sig_t osig; + + retry = 4; + timeout = timeo; + + if (!server) + timeout /= retry; + + osig = signal(SIGALRM, sig_alarm); + + while (1) { + int r = sigsetjmp(jmp, 1); + if (r) { + if (r == MS_TIMEOUT && !server && --retry) + continue; + + alarm(0); + signal(SIGALRM, osig); + + switch (r) { + case MS_SUCCESS: + case MS_PPP: + errno = 0; + return 0; + + case MS_FAILED: + errno = EPROTO; + break; + + case MS_TIMEOUT: + errno = ETIMEDOUT; + break; + } + return -1; + } + + alarm(timeout); + + if (server) + r = ms_server(fd); + else + r = ms_client(fd); + + siglongjmp(jmp, r); + } +} diff --git a/compat/pand.1 b/compat/pand.1 new file mode 100644 index 000000000..4603b8bf5 --- /dev/null +++ b/compat/pand.1 @@ -0,0 +1,77 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.29. +.TH BlueZ "1" "February 2003" "PAN daemon" "User Commands" +.SH NAME +pand \- BlueZ Bluetooth PAN daemon +.SH DESCRIPTION +The pand PAN daemon allows your computer to connect to ethernet +networks using Bluetooth. +.SH SYNPOSIS +pand +.SH OPTIONS +.TP +\fB\-\-show\fR \fB\-\-list\fR \fB\-l\fR +Show active PAN connections +.TP +\fB\-\-listen\fR \fB\-s\fR +Listen for PAN connections +.TP +\fB\-\-connect\fR \fB\-c\fR +Create PAN connection +.TP +\fB\-\-search\fR \fB\-Q[duration]\fR +Search and connect +.TP +\fB\-\-kill\fR \fB\-k\fR +Kill PAN connection +.TP +\fB\-\-killall\fR \fB\-K\fR +Kill all PAN connections +.TP +\fB\-\-role\fR \fB\-r\fR +Local PAN role (PANU, NAP, GN) +.TP +\fB\-\-service\fR \fB\-d\fR +Remote PAN service (PANU, NAP, GN) +.TP +\fB\-\-ethernet\fR \fB\-e\fR +Network interface name +.TP +\fB\-\-device\fR \fB\-i\fR +Source bdaddr +.TP +\fB\-\-nosdp\fR \fB\-D\fR +Disable SDP +.TP +\fB\-\-encrypt\fR \fB\-E\fR +Enable encryption +.TP +\fB\-\-secure\fR \fB\-S\fR +Secure connection +.TP +\fB\-\-master\fR \fB\-M\fR +Become the master of a piconet +.TP +\fB\-\-nodetach\fR \fB\-n\fR +Do not become a daemon +.TP +\fB\-\-persist\fR \fB\-p[interval]\fR +Persist mode +.TP +\fB\-\-cache\fR \fB\-C[valid]\fR +Cache addresses +.TP +\fB\-\-pidfile\fR \fB\-P \fR +Create PID file +.TP +\fB\-\-devup\fR \fB\-u