Git init
authorKibum Kim <kb0929.kim@samsung.com>
Fri, 6 Jan 2012 15:44:05 +0000 (00:44 +0900)
committerKibum Kim <kb0929.kim@samsung.com>
Fri, 6 Jan 2012 15:44:05 +0000 (00:44 +0900)
382 files changed:
.gitignore [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
COPYING.LIB [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
Makefile.tools [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
TODO [new file with mode: 0644]
acinclude.m4 [new file with mode: 0644]
attrib/att.c [new file with mode: 0644]
attrib/att.h [new file with mode: 0644]
attrib/client.c [new file with mode: 0644]
attrib/client.h [new file with mode: 0644]
attrib/example.c [new file with mode: 0644]
attrib/example.h [new file with mode: 0644]
attrib/gatt.c [new file with mode: 0644]
attrib/gatt.h [new file with mode: 0644]
attrib/gattrib.c [new file with mode: 0644]
attrib/gattrib.h [new file with mode: 0644]
attrib/gatttool.c [new file with mode: 0644]
attrib/gatttool.h [new file with mode: 0644]
attrib/interactive.c [new file with mode: 0644]
attrib/main.c [new file with mode: 0644]
attrib/manager.c [new file with mode: 0644]
attrib/manager.h [new file with mode: 0644]
attrib/utils.c [new file with mode: 0644]
audio/a2dp-codecs.h [new file with mode: 0644]
audio/a2dp.c [new file with mode: 0644]
audio/a2dp.h [new file with mode: 0644]
audio/audio.conf [new file with mode: 0644]
audio/avctp.c [new file with mode: 0644]
audio/avctp.h [new file with mode: 0644]
audio/avdtp.c [new file with mode: 0644]
audio/avdtp.h [new file with mode: 0644]
audio/avrcp.c [new file with mode: 0644]
audio/avrcp.h [new file with mode: 0644]
audio/bluetooth.conf [new file with mode: 0644]
audio/control.c [new file with mode: 0644]
audio/control.h [new file with mode: 0644]
audio/ctl_bluetooth.c [new file with mode: 0644]
audio/device.c [new file with mode: 0644]
audio/device.h [new file with mode: 0644]
audio/gateway.c [new file with mode: 0644]
audio/gateway.h [new file with mode: 0644]
audio/gsta2dpsink.c [new file with mode: 0644]
audio/gsta2dpsink.h [new file with mode: 0644]
audio/gstavdtpsink.c [new file with mode: 0644]
audio/gstavdtpsink.h [new file with mode: 0644]
audio/gstbluetooth.c [new file with mode: 0644]
audio/gstpragma.h [new file with mode: 0644]
audio/gstrtpsbcpay.c [new file with mode: 0644]
audio/gstrtpsbcpay.h [new file with mode: 0644]
audio/gstsbcdec.c [new file with mode: 0644]
audio/gstsbcdec.h [new file with mode: 0644]
audio/gstsbcenc.c [new file with mode: 0644]
audio/gstsbcenc.h [new file with mode: 0644]
audio/gstsbcparse.c [new file with mode: 0644]
audio/gstsbcparse.h [new file with mode: 0644]
audio/gstsbcutil.c [new file with mode: 0644]
audio/gstsbcutil.h [new file with mode: 0644]
audio/headset.c [new file with mode: 0644]
audio/headset.h [new file with mode: 0644]
audio/ipc.c [new file with mode: 0644]
audio/ipc.h [new file with mode: 0644]
audio/main.c [new file with mode: 0644]
audio/manager.c [new file with mode: 0644]
audio/manager.h [new file with mode: 0644]
audio/media.c [new file with mode: 0644]
audio/media.h [new file with mode: 0644]
audio/module-bluetooth-sink.c [new file with mode: 0644]
audio/pcm_bluetooth.c [new file with mode: 0644]
audio/rtp.h [new file with mode: 0644]
audio/sink.c [new file with mode: 0644]
audio/sink.h [new file with mode: 0644]
audio/source.c [new file with mode: 0644]
audio/source.h [new file with mode: 0644]
audio/telephony-dummy.c [new file with mode: 0644]
audio/telephony-maemo5.c [new file with mode: 0644]
audio/telephony-maemo6.c [new file with mode: 0644]
audio/telephony-ofono.c [new file with mode: 0644]
audio/telephony-tizen.c [new file with mode: 0644]
audio/telephony.h [new file with mode: 0644]
audio/transport.c [new file with mode: 0644]
audio/transport.h [new file with mode: 0644]
audio/unix.c [new file with mode: 0644]
audio/unix.h [new file with mode: 0644]
bluez.pc.in [new file with mode: 0644]
btio/btio.c [new file with mode: 0644]
btio/btio.h [new file with mode: 0644]
compat/bnep.c [new file with mode: 0644]
compat/dun.c [new file with mode: 0644]
compat/dund.1 [new file with mode: 0644]
compat/dund.c [new file with mode: 0644]
compat/dund.h [new file with mode: 0644]
compat/fakehid.c [new file with mode: 0644]
compat/hidd.1 [new file with mode: 0644]
compat/hidd.c [new file with mode: 0644]
compat/hidd.h [new file with mode: 0644]
compat/lib.h [new file with mode: 0644]
compat/msdun.c [new file with mode: 0644]
compat/pand.1 [new file with mode: 0644]
compat/pand.c [new file with mode: 0644]
compat/pand.h [new file with mode: 0644]
compat/sdp.c [new file with mode: 0644]
compat/sdp.h [new file with mode: 0644]
configure.ac [new file with mode: 0644]
cups/cups.h [new file with mode: 0644]
cups/hcrp.c [new file with mode: 0644]
cups/main.c [new file with mode: 0644]
cups/sdp.c [new file with mode: 0644]
cups/spp.c [new file with mode: 0644]
debian/bluez.install.in [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/libbluetooth-dev.install.in [new file with mode: 0644]
debian/libbluetooth3.install.in [new file with mode: 0644]
debian/rules [new file with mode: 0755]
doc/adapter-api.txt [new file with mode: 0644]
doc/agent-api.txt [new file with mode: 0644]
doc/assigned-numbers.txt [new file with mode: 0644]
doc/attribute-api.txt [new file with mode: 0644]
doc/audio-api.txt [new file with mode: 0644]
doc/control-api.txt [new file with mode: 0644]
doc/device-api.txt [new file with mode: 0644]
doc/health-api.txt [new file with mode: 0644]
doc/hfp-api.txt [new file with mode: 0644]
doc/input-api.txt [new file with mode: 0644]
doc/manager-api.txt [new file with mode: 0644]
doc/media-api.txt [new file with mode: 0644]
doc/network-api.txt [new file with mode: 0644]
doc/sap-api.txt [new file with mode: 0644]
doc/serial-api.txt [new file with mode: 0644]
doc/service-api.txt [new file with mode: 0644]
doc/version.xml.in [new file with mode: 0644]
gdbus/gdbus.h [new file with mode: 0644]
gdbus/mainloop.c [new file with mode: 0644]
gdbus/object.c [new file with mode: 0644]
gdbus/polkit.c [new file with mode: 0644]
gdbus/watch.c [new file with mode: 0644]
health/hdp.c [new file with mode: 0644]
health/hdp.h [new file with mode: 0644]
health/hdp_main.c [new file with mode: 0644]
health/hdp_manager.c [new file with mode: 0644]
health/hdp_manager.h [new file with mode: 0644]
health/hdp_types.h [new file with mode: 0644]
health/hdp_util.c [new file with mode: 0644]
health/hdp_util.h [new file with mode: 0644]
health/mcap.c [new file with mode: 0644]
health/mcap.h [new file with mode: 0644]
health/mcap_internal.h [new file with mode: 0644]
health/mcap_lib.h [new file with mode: 0644]
health/mcap_sync.c [new file with mode: 0644]
input/device.c [new file with mode: 0644]
input/device.h [new file with mode: 0644]
input/fakehid.c [new file with mode: 0644]
input/fakehid.h [new file with mode: 0644]
input/input.conf [new file with mode: 0644]
input/main.c [new file with mode: 0644]
input/manager.c [new file with mode: 0644]
input/manager.h [new file with mode: 0644]
input/server.c [new file with mode: 0644]
input/server.h [new file with mode: 0644]
lib/bluetooth.c [new file with mode: 0644]
lib/bluetooth.h [new file with mode: 0644]
lib/bnep.h [new file with mode: 0644]
lib/cmtp.h [new file with mode: 0644]
lib/hci.c [new file with mode: 0644]
lib/hci.h [new file with mode: 0644]
lib/hci_lib.h [new file with mode: 0644]
lib/hidp.h [new file with mode: 0644]
lib/l2cap.h [new file with mode: 0644]
lib/mgmt.h [new file with mode: 0644]
lib/rfcomm.h [new file with mode: 0644]
lib/sco.h [new file with mode: 0644]
lib/sdp.c [new file with mode: 0644]
lib/sdp.h [new file with mode: 0644]
lib/sdp_lib.h [new file with mode: 0644]
lib/uuid.c [new file with mode: 0644]
lib/uuid.h [new file with mode: 0644]
network/common.c [new file with mode: 0644]
network/common.h [new file with mode: 0644]
network/connection.c [new file with mode: 0644]
network/connection.h [new file with mode: 0644]
network/main.c [new file with mode: 0644]
network/manager.c [new file with mode: 0644]
network/manager.h [new file with mode: 0644]
network/network.conf [new file with mode: 0644]
network/server.c [new file with mode: 0644]
network/server.h [new file with mode: 0644]
packaging/bluez.spec [new file with mode: 0644]
plugins/echo.c [new file with mode: 0644]
plugins/formfactor.c [new file with mode: 0644]
plugins/hal.c [new file with mode: 0644]
plugins/hciops.c [new file with mode: 0644]
plugins/maemo6.c [new file with mode: 0644]
plugins/mgmtops.c [new file with mode: 0644]
plugins/pnat.c [new file with mode: 0644]
plugins/service.c [new file with mode: 0644]
plugins/storage.c [new file with mode: 0644]
sap/main.c [new file with mode: 0644]
sap/manager.c [new file with mode: 0644]
sap/manager.h [new file with mode: 0644]
sap/sap-dummy.c [new file with mode: 0644]
sap/sap.h [new file with mode: 0644]
sap/server.c [new file with mode: 0644]
sap/server.h [new file with mode: 0644]
sbc/formats.h [new file with mode: 0644]
sbc/sbc.c [new file with mode: 0644]
sbc/sbc.h [new file with mode: 0644]
sbc/sbc_math.h [new file with mode: 0644]
sbc/sbc_primitives.c [new file with mode: 0644]
sbc/sbc_primitives.h [new file with mode: 0644]
sbc/sbc_primitives_armv6.c [new file with mode: 0644]
sbc/sbc_primitives_armv6.h [new file with mode: 0644]
sbc/sbc_primitives_iwmmxt.c [new file with mode: 0644]
sbc/sbc_primitives_iwmmxt.h [new file with mode: 0644]
sbc/sbc_primitives_mmx.c [new file with mode: 0644]
sbc/sbc_primitives_mmx.h [new file with mode: 0644]
sbc/sbc_primitives_neon.c [new file with mode: 0644]
sbc/sbc_primitives_neon.h [new file with mode: 0644]
sbc/sbc_tables.h [new file with mode: 0644]
sbc/sbcdec.c [new file with mode: 0644]
sbc/sbcenc.c [new file with mode: 0644]
sbc/sbcinfo.c [new file with mode: 0644]
sbc/sbctester.c [new file with mode: 0644]
scripts/bluetooth-hid2hci.rules [new file with mode: 0644]
scripts/bluetooth-serial.rules [new file with mode: 0644]
scripts/bluetooth.rules.in [new file with mode: 0644]
scripts/bluetooth_serial [new file with mode: 0644]
serial/main.c [new file with mode: 0644]
serial/manager.c [new file with mode: 0644]
serial/manager.h [new file with mode: 0644]
serial/port.c [new file with mode: 0644]
serial/port.h [new file with mode: 0644]
serial/proxy.c [new file with mode: 0644]
serial/proxy.h [new file with mode: 0644]
serial/serial.conf [new file with mode: 0644]
src/adapter.c [new file with mode: 0644]
src/adapter.h [new file with mode: 0644]
src/agent.c [new file with mode: 0644]
src/agent.h [new file with mode: 0644]
src/attrib-server.c [new file with mode: 0644]
src/attrib-server.h [new file with mode: 0644]
src/bluetooth.conf [new file with mode: 0644]
src/bluetooth.ver [new file with mode: 0644]
src/bluetoothd.8.in [new file with mode: 0644]
src/dbus-common.c [new file with mode: 0644]
src/dbus-common.h [new file with mode: 0644]
src/device.c [new file with mode: 0644]
src/device.h [new file with mode: 0644]
src/error.c [new file with mode: 0644]
src/error.h [new file with mode: 0644]
src/event.c [new file with mode: 0644]
src/event.h [new file with mode: 0644]
src/genbuiltin [new file with mode: 0755]
src/glib-helper.c [new file with mode: 0644]
src/glib-helper.h [new file with mode: 0644]
src/hcid.h [new file with mode: 0644]
src/log.c [new file with mode: 0644]
src/log.h [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/main.conf [new file with mode: 0644]
src/manager.c [new file with mode: 0644]
src/manager.h [new file with mode: 0644]
src/oui.c [new file with mode: 0644]
src/oui.h [new file with mode: 0644]
src/plugin.c [new file with mode: 0644]
src/plugin.h [new file with mode: 0644]
src/ppoll.h [new file with mode: 0644]
src/rfkill.c [new file with mode: 0644]
src/sdp-xml.c [new file with mode: 0644]
src/sdp-xml.h [new file with mode: 0644]
src/sdpd-database.c [new file with mode: 0644]
src/sdpd-request.c [new file with mode: 0644]
src/sdpd-server.c [new file with mode: 0644]
src/sdpd-service.c [new file with mode: 0644]
src/sdpd.h [new file with mode: 0644]
src/storage.c [new file with mode: 0644]
src/storage.h [new file with mode: 0644]
src/textfile.c [new file with mode: 0644]
src/textfile.h [new file with mode: 0644]
src/uinput.h [new file with mode: 0644]
test/agent.c [new file with mode: 0644]
test/apitest [new file with mode: 0755]
test/attest.c [new file with mode: 0644]
test/avtest.c [new file with mode: 0644]
test/bdaddr.8 [new file with mode: 0644]
test/bdaddr.c [new file with mode: 0644]
test/btiotest.c [new file with mode: 0644]
test/dbusdef.py [new file with mode: 0644]
test/gaptest.c [new file with mode: 0644]
test/hciemu.1 [new file with mode: 0644]
test/hciemu.c [new file with mode: 0644]
test/hsmicro [new file with mode: 0755]
test/hsplay [new file with mode: 0755]
test/hstest.c [new file with mode: 0644]
test/ipctest.c [new file with mode: 0644]
test/l2test.c [new file with mode: 0644]
test/list-devices [new file with mode: 0755]
test/lmptest.c [new file with mode: 0644]
test/monitor-bluetooth [new file with mode: 0755]
test/rctest.1 [new file with mode: 0644]
test/rctest.c [new file with mode: 0644]
test/scotest.c [new file with mode: 0644]
test/sdptest.c [new file with mode: 0644]
test/service-did.xml [new file with mode: 0644]
test/service-ftp.xml [new file with mode: 0644]
test/service-opp.xml [new file with mode: 0644]
test/service-record.dtd [new file with mode: 0644]
test/service-spp.xml [new file with mode: 0644]
test/simple-agent [new file with mode: 0755]
test/simple-endpoint [new file with mode: 0644]
test/simple-service [new file with mode: 0755]
test/test-adapter [new file with mode: 0755]
test/test-attrib [new file with mode: 0644]
test/test-audio [new file with mode: 0755]
test/test-device [new file with mode: 0755]
test/test-discovery [new file with mode: 0755]
test/test-input [new file with mode: 0755]
test/test-manager [new file with mode: 0755]
test/test-network [new file with mode: 0755]
test/test-serial [new file with mode: 0755]
test/test-service [new file with mode: 0755]
test/test-telephony [new file with mode: 0755]
test/test-textfile.c [new file with mode: 0644]
test/uuidtest.c [new file with mode: 0644]
tools/avctrl.8 [new file with mode: 0644]
tools/avctrl.c [new file with mode: 0644]
tools/avinfo.c [new file with mode: 0644]
tools/bccmd.8 [new file with mode: 0644]
tools/bccmd.c [new file with mode: 0644]
tools/ciptool.1 [new file with mode: 0644]
tools/ciptool.c [new file with mode: 0644]
tools/csr.c [new file with mode: 0644]
tools/csr.h [new file with mode: 0644]
tools/csr_3wire.c [new file with mode: 0644]
tools/csr_bcsp.c [new file with mode: 0644]
tools/csr_h4.c [new file with mode: 0644]
tools/csr_hci.c [new file with mode: 0644]
tools/csr_usb.c [new file with mode: 0644]
tools/dfu.c [new file with mode: 0644]
tools/dfu.h [new file with mode: 0644]
tools/dfubabel.1 [new file with mode: 0644]
tools/dfubabel.c [new file with mode: 0644]
tools/dfutool.1 [new file with mode: 0644]
tools/dfutool.c [new file with mode: 0644]
tools/hciattach.8 [new file with mode: 0644]
tools/hciattach.c [new file with mode: 0644]
tools/hciattach.h [new file with mode: 0644]
tools/hciattach_ath3k.c [new file with mode: 0644]
tools/hciattach_qualcomm.c [new file with mode: 0644]
tools/hciattach_st.c [new file with mode: 0644]
tools/hciattach_ti.c [new file with mode: 0644]
tools/hciattach_tialt.c [new file with mode: 0644]
tools/hciconfig.8 [new file with mode: 0644]
tools/hciconfig.c [new file with mode: 0644]
tools/hcieventmask.c [new file with mode: 0644]
tools/hcisecfilter.c [new file with mode: 0644]
tools/hcitool.1 [new file with mode: 0644]
tools/hcitool.c [new file with mode: 0644]
tools/hid2hci.8 [new file with mode: 0644]
tools/hid2hci.c [new file with mode: 0644]
tools/kword.c [new file with mode: 0644]
tools/kword.h [new file with mode: 0644]
tools/l2ping.8 [new file with mode: 0644]
tools/l2ping.c [new file with mode: 0644]
tools/lexer.l [new file with mode: 0644]
tools/parser.y [new file with mode: 0644]
tools/ppporc.c [new file with mode: 0644]
tools/rfcomm.1 [new file with mode: 0644]
tools/rfcomm.c [new file with mode: 0644]
tools/rfcomm.conf [new file with mode: 0644]
tools/sdptool.1 [new file with mode: 0644]
tools/sdptool.c [new file with mode: 0644]
tools/ubcsp.c [new file with mode: 0644]
tools/ubcsp.h [new file with mode: 0644]
tracer/main.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..78aa43e
--- /dev/null
@@ -0,0 +1,105 @@
+*.o
+*.a
+*.lo
+*.la
+*.so
+.deps
+.libs
+.dirstamp
+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
+lexer.c
+parser.h
+parser.c
+
+bluez.pc
+lib/bluetooth
+src/builtin.h
+src/bluetoothd
+audio/telephony.c
+scripts/bluetooth.rules
+scripts/97-bluetooth.rules
+scripts/97-bluetooth-hid2hci.rules
+
+sbc/sbcdec
+sbc/sbcenc
+sbc/sbcinfo
+sbc/sbctester
+
+attrib/gatttool
+tracer/hcitrace
+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/rfcomm
+tools/l2ping
+tools/ppporc
+tools/sdptool
+cups/bluetooth
+test/agent
+test/bdaddr
+test/hciemu
+test/attest
+test/hstest
+test/avtest
+test/l2test
+test/rctest
+test/scotest
+test/gaptest
+test/sdptest
+test/lmptest
+test/ipctest
+test/btiotest
+test/test-textfile
+compat/dund
+compat/hidd
+compat/pand
+
+doc/*.bak
+doc/*.stamp
+doc/bluez.*
+doc/bluez-*.txt
+doc/*.sgml
+doc/version.xml
+doc/xml
+doc/html
+src/bluetoothd.8
+
+debian/*.debhelper.log
+debian/*.substvars
+debian/bluez-dbg/
+debian/*.install
+debian/bluez-gstreamer/
+debian/bluez/
+debian/files
+debian/libbluetooth-dev/
+debian/*.debhelper
+debian/libbluetooth3/
+debian/tmp/
\ No newline at end of file
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..3e4b3b9
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,52 @@
+Maxim Krasnyansky <maxk@qualcomm.com>
+Marcel Holtmann <marcel@holtmann.org>
+Stephen Crane <steve.crane@rococosoft.com>
+Jean Tourrilhes <jt@hpl.hp.com>
+Jan Beutel <j.beutel@ieee.org>
+Ilguiz Latypov <ilatypov@superbt.com>
+Thomas Moser <thomas.moser@tmoser.ch>
+Nils Faerber <nils@kernelconcepts.de>
+Martin Leopold <martin@leopold.dk>
+Wolfgang Heidrich <wolfgang.heidrich@esk.fhg.de>
+Fabrizio Gennari <fabrizio.gennari@philips.com>
+Brad Midgley <bmidgley@xmission.com>
+Henryk Ploetz <henryk@ploetzli.ch>
+Philip Blundell <pb@nexus.co.uk>
+Johan Hedberg <johan.hedberg@nokia.com>
+Claudio Takahasi <claudio.takahasi@indt.org.br>
+Eduardo Rocha <eduardo.rocha@indt.org.br>
+Denis Kenzior <denis.kenzior@trolltech.com>
+Frederic Dalleau <frederic.dalleau@access-company.com>
+Frederic Danis <frederic.danis@access-company.com>
+Luiz Augusto von Dentz <luiz.dentz@gmail.com>
+Fabien Chevalier <fabchevalier@free.fr>
+Ohad Ben-Cohen <ohad@bencohen.org>
+Daniel Gollub <dgollub@suse.de>
+Tom Patzig <tpatzig@suse.de>
+Kai Vehmanen <kai.vehmanen@nokia.com>
+Vinicius Gomes <vinicius.gomes@openbossa.org>
+Alok Barsode <alok.barsode@azingo.com>
+Bastien Nocera <hadess@hadess.net>
+Albert Huang <albert@csail.mit.edu>
+Glenn Durfee <gdurfee@google.com>
+David Woodhouse <david.woodhouse@intel.com>
+Christian Hoene <hoene@uni-tuebingen.de>
+Pekka Pessi <pekka.pessi@nokia.com>
+Siarhei Siamashka <siarhei.siamashka@nokia.com>
+Nick Pelly <npelly@google.com>
+Lennart Poettering <lennart@poettering.net>
+Gustavo F. Padovan <gustavo@las.ic.unicamp.br>
+Marc-Andre Lureau <marc-andre.lureau@nokia.com>
+Bea Lam <bea.lam@nokia.com>
+Zygo Blaxell <zygo.blaxell@xandros.com>
+Forrest Zhao <forrest.zhao@intel.com>
+Scott Talbot <psyc@stalbot.com>
+Ilya Rubtsov <lusyaru@gmail.com>
+Mario Limonciello <mario_limonciello@dell.com>
+Filippo Giunchedi <filippo@esaurito.net>
+Jaikumar Ganesh <jaikumar@google.com>
+Elvis Pfutzenreuter <epx@signove.com>
+Santiago Carot-Nemesio <scarot@libresoft.es>
+José Antonio Santos Cadenas <jcaden@libresoft.es>
+Francisco Alecrim <francisco.alecrim@openbossa.org>
+Daniel Orstadius <daniel.orstadius@gmail.com>
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..6d45519
--- /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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
index 0000000..1f7c8cc
--- /dev/null
@@ -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.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
index 0000000..1f936e7
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,1598 @@
+ver 4.90:
+       Fix issue with setting of global mode property.
+       Fix issue with handling of RequestSession responses.
+       Fix issue with TP_BNEP_CTRL_BV_01_C qualification test.
+       Fix issue with too short AVDTP request timeout.
+       Add support for SIM Access Profile manager.
+       Add support for new UUID utility functions.
+       Add support for attribute server notifications.
+       Add support for client characteristic configuration.
+       Update support for interactive GATT utility.
+
+ver 4.89:
+       Fix issue with name resolving when discovery is suspended.
+       Fix issue with parsing flags of advertising report.
+       Fix issue with SEP handling if interface is disabled.
+       Fix issue with device object creation on disconnect event.
+       Fix issue with indicators whenever the driver is initialized.
+       Fix issue with call indicator when parsing call info reply.
+       Fix issue with crash and allowed GATT MTU was too large.
+       Add support for SDP record of Primary GATT services.
+       Add support for interactive mode for GATT utility.
+
+ver 4.88:
+       Fix issue with HID channel reference count handling.
+       Fix issue with daemon exit on badly formatted AT+VTS.
+       Fix issue with crash while parsing of endpoint properties.
+       Fix issue with possible crash on AVDTP Suspend request timeout.
+       Fix issue with stopping inquiry before adapter is initialized.
+       Fix issue with creating device object when connection fails.
+       Fix issue with sending HCIDEVUP when adapter is already up.
+       Fix issue with handling bonding IO channel closing.
+       Fix agent cancellation in security mode 3 situations.
+       Update pairing code to support management interface.
+
+ver 4.87:
+       Fix issue with initialization when adapter is already up.
+       Fix issue with attribute server MTU and incoming connections.
+       Fix issue with duplicate characteristics after discovery.
+
+ver 4.86:
+       Revert wrong fix for SDP PDU size error response.
+       Fix various memory leaks in A2DP and AVDTP support.
+       Add Routing property to MediaTransport interface
+       Add proper tracking mechanism to NREC status.
+       Add READ_BLOB_REQUEST support to attribute server.
+
+ver 4.85:
+       Fix issue with event mask setting for older adapters.
+       Fix issue with device creation and pairing failures.
+       Add support for telephony support via oFono.
+       Add support for characteristic security level.
+       Update support for service registration.
+
+ver 4.84:
+       Fix issue with wrong parameters and device found signals.
+       Fix issue with leaking EIR data if RSSI does not change.
+       Fix issue with adapter initialization state.
+       Fix issue with closing of SDP server sockets.
+
+ver 4.83:
+       Fix issue with already connected HFP/HSP endpoints.
+       Fix missing reply when create device is canceled.
+       Fix memory leak within the attribute server.
+       Fix memory leak with unused extended inquiry name.
+       Fix setting paired state when device->authr is false.
+       Fix clearing authentication request for renewed keys.
+       Add support for storing link keys in runtime memory.
+       Update support for primary service discovery.
+
+ver 4.82:
+       Fix crash with mmap of files with multiples of page size.
+       Fix HFP response and hold (AT+BTRH) command response.
+       Fix device creation error response when powered off.
+       Fix device removal when connecting/browsing fails.
+       Add initial attribute permission implementation.
+       Add AVDTP SRC stream send buffer size verification.
+       Add support for setting link policy based on features.
+
+ver 4.81:
+       Fix issue with telephony driver initialization.
+       Fix issue with adapter services list initialization.
+       Fix crash after simultaneous authentication requests.
+       Add support for primary service search on device creation.
+
+ver 4.80:
+       Fix legacy link key storing for some buggy adapters.
+       Fix invalid memory access when EIR field length is zero.
+       Fix adapter initialization to wait for kernel HCI commands.
+       Fix initialization of adapters which are already up.
+       Fix possible race condition when initializing adapters.
+       Fix possible crashes when attempting to connect AVDTP.
+       Fix not aborting sink stream configuration on disconnect.
+       Fix not indicating disconnected state when connecting to AVDTP.
+       Fix not dropping AVDTP session when canceling stream setup.
+       Fix AVDTP abort not being send when the state is idle.
+       Fix regression with Low Energy and interleave discovery.
+       Add a new configuration option to disable Low Energy support.
+       Add iwmmxt optimization for SBC for ARM PXA series CPUs.
+       Update support for GATT Primary Service Discovery.
+       Update MCAP and HDP support.
+
+ver 4.79:
+       Fix issue with adapter initialization race condition.
+       Update new Bluetooth Management interface support.
+
+ver 4.78:
+       Fix various issues with AVDTP timer handling.
+       Fix various issues with handling of mode changes.
+       Fix issue with audio disconnect watch in connecting state.
+       Fix issue with handling call waiting indicators in telephony.
+       Fix issue with handling UUID parameter and RegisterEndpoint.
+       Add initial support for Bluetooth Management interface.
+       Add support for Application property to HealthChannel.
+
+ver 4.77:
+       Fix issue with device name and accessing already freed memory.
+       Fix issue with handling CHLD=0 command for handsfree.
+       Fix issue with manager properties and no adapters.
+       Fix issue with properties and broken service records.
+       Fix issue with A2DP playback and sample rate changes.
+       Update MCAP and HDP support.
+
+ver 4.76:
+       Fix issue in telephony driver with hanging up held call.
+       Fix issue in telephony driver with notifications when on hold.
+       Fix issue with blocking on setconf confirmation callback.
+       Fix issue with not always signaling new streams as sinks.
+       Fix issue with errors in case of endpoint request timeout.
+       Fix issue with HFP/HSP microphone and speaker gain values.
+       Add source if the device attempt to configure local sink stream.
+       Add PSM option for GATT/ATT over BR/EDR on gatttool.
+       Add support for GATT/ATT Attribute Write Request.
+       Update MCAP and HDP support.
+
+ver 4.75:
+       Fix use of uninitialized variable on legacy pairing.
+       Fix mismatch of attribute protocol opcode.
+
+ver 4.74:
+       Fix regression for Legacy Pairing.
+       Fix wrong PSM value for attribute protocol.
+       Fix issue with RSSI field in advertising reports.
+       Add support for Add BR/EDR and LE interleaved discovery.
+       Add support for GATT write characteristic value option.
+       Add support for specifying download address for AR300x.
+
+ver 4.73:
+       Fix problem with EIR data when setting the name.
+       Fix reading local name from command complete event.
+       Fix registering local endpoints with disabled socket interface.
+       Add support for more HCI operations using ops infrastructure.
+       Add support for GATT characteristic hierarchy.
+       Add support for GATT indications.
+
+ver 4.72:
+       Fix memory leak while connecting BTIO channels.
+       Fix crash with GStreamer plugin if SBC is not supported.
+       Fix issue with GATT server stop sending notifications.
+       Fix issue with GATT and dealing with the minimum MTU size.
+       Fix issue with file descriptor leak in GATT client.
+       Add support for UUID 128-bit handling in attribute client.
+       Add support for encoders/decoders for MTU Exchange.
+       Add support for the MTU Exchange procedure to the server.
+       Add support for a per channel MTU to the ATT server.
+       Add support for Characteristic interface.
+       Add support for new Media API and framework.
+       Add initial support for HDP plugin.
+
+ver 4.71:
+       Fix compilation when SBC support in not enabled.
+       Fix crash with RequestSession and application disconnects.
+       Fix memory leak and possible crash when removing audio device.
+       Fix issue with closing stream of locked sep when reconfiguring.
+       Fix issue where discovery could interfere with bonding.
+       Fix issue with Connected status when PS3 BD remote connects.
+       Fix issue with lifetime of fake input devices.
+       Add support for compile time option of oui.txt path.
+       Add support for printing IEEE1284 device ID for CUPS.
+       Add plugin for setting adapter class via DMI.
+       Add more features for attribute protocol and profile.
+       Add initial support for MCAP.
+
+ver 4.70:
+       Fix incoming call indication handling when in WAITING state.
+       Fix various SDP related qualification test case issues.
+       Fix logic to write EIR when SDP records are changed.
+       Fix UTF-8 validity check for remote names in EIR.
+       Add support for UUID-128 extended inquiry response.
+       Add service UUIDs from EIR to the DeviceFound signal.
+       Add fast connectable feature for Handsfree profile.
+       Add HCI command and event definitions for AMP support.
+       Add firmware download support for Qualcommh devices.
+       Add host level support for Atheros AR300x device.
+       Add initial support of ATT and GATT for basic rate.
+
+ver 4.69:
+       Fix issue with calling g_option_context_free() twice.
+       Fix inconsistencies with initial LE commands and events.
+       Add support for telephony ClearLastNumber method.
+       Add support for network server interface.
+
+ver 4.68:
+       Fix initialization of adapters in RAW mode.
+       Fix signal strength for HFP in Maemo's telephony support.
+       Add support for following the radio state via Maemo's MCE.
+       Add initial set of LE commands and events definitions.
+       Add mode option for L2CAP sockets to the BtIO API.
+
+ver 4.67:
+       Fix issue with authentication reply when bonding already completed.
+       Fix issue with not canceling authentication when bonding fails.
+       Fix issue with changed combination keys and temporary storage.
+       Fix issue with sdp_get_supp_feat library function.
+       Fix issue with missing unblock on device removal.
+       Fix issue with not waiting for mode change completion.
+       Add ARMv6 optimized version of analysis filter for SBC encoder.
+
+ver 4.66:
+       Fix regression with full debug enabling via SIGUSR2.
+       Fix redundant speaker/microphone gains being sent.
+       Fix not emitting PropertyChanged for SpeakerGain/MicrophoneGain.
+       Fix issue with storage usage when a record is not found in memory.
+       Fix issue with DiscoverServices not retrieving any records.
+       Fix audio profile disconnection order to match whitepaper.
+       Fix auto-accept confirmation when local agent has NoInputNoOutput.
+       Fix remote just-works SSP when MITM protection is required.
+       Fix performing dedicated bonding without MITM requirement.
+       Add support for storing debug link keys in runtime memory.
+
+ver 4.65:
+       Fix issues with general bonding being default setting now.
+       Fix driver removal upon device removal.
+       Add new "Blocked" property to device objects.
+       Add hciconfig support for blacklisting.
+       Add support for dynamic debug feature.
+
+ver 4.64:
+       Fix invalid memory access in headset_get_nrec function.
+       Fix issue with disconnect event on higher protocol layers.
+       Fix issue with list parsing in sdp_set_supp_features function.
+       Fix device object reference counting for SDP browse requests.
+       Add missing memory checks whenever memory is allocated for SDP.
+       Add support for exporting local services via D-Bus.
+       Add more L2CAP Enhanced Retransmission test options.
+
+ver 4.63:
+       Fix avdtp_abort not canceling pending requests.
+       Fix stale connection when abort gets rejected.
+
+ver 4.62:
+       Fix accidental symbol breakage with inquiry transmit power.
+       Fix using invalid data from previous headset connection.
+       Fix double free on AVDTP Abort response.
+       Fix possible crash while verifying AVDTP version.
+       Fix missing inuse flag when AVDTP stream is configured.
+       Add support for Bluetooth controller types.
+
+ver 4.61:
+       Fix issues with Read Inquiry Response Transmit Power Level.
+       Fix possible invalid read when removing a temporary device.
+       Fix mode restoration when remember_powered is false.
+       Fix conference call releasing in telephony-maemo.
+       Fix segmentation fault with authorization during headset disconnects.
+       Add support for handling unanswered AVDTP request on disconnect.
+       Add support for handling Inquiry Response Transmit Power Level.
+       Add support for caching of remote host features.
+       Add preliminary voice dialing support for HSP.
+
+ver 4.60:
+       Fix voice mailbox number reading from SIM.
+       Fix some races with D-Bus mainloop integration.
+       Add helpers for D-Bus signal watches.
+
+ver 4.59:
+       Add values for Bluetooth 4.0 specification.
+       Add SDP functions for HDP support.
+       Add test scripts for input and audio.
+       Fix missing close on BtIO create_io function.
+       Fix sending incorrect AVDTP commands after timeout occurs.
+       Fix timer removal when device disconnects unexpectedly.
+       Fix Extended Inquiry Response record for Device ID.
+
+ver 4.58:
+       Fix crash when adapter agent exists during authentication.
+       Fix CK-20W quirks for play and pause events.
+
+ver 4.57:
+       Fix unloading of drivers for uninitialized adapters.
+       Fix debug message to use requested and not opened SEID.
+       Fix codec selection for GStreamer plugin.
+       Fix deleting of SDP records during service updates.
+       Fix deleting of SDP records when a device is removed.
+       Fix handling when the SDP record is modified on remote device.
+       Fix potential buffer overflow by using snprintf instead of sprintf.
+       Fix const declarations for some storage function parameters.
+
+ver 4.56:
+       Add missing values from Bluetooth 3.0 specification.
+       Add proper tracking of device paired status.
+       Fix tracking of devices without permanently stored link key.
+       Fix issue with link key removal after connection failures.
+       Fix legacy pairing information based on remote host features.
+       Fix off-by-one issue with AVDTP capability parsing.
+       Fix AVRCP, AVCTP, AVDTP, A2DP and HFP version numbers.
+       Fix agent canceling before calling agent_destroy.
+       Fix service record parsing with an empty UUID list.
+       Fix various SDP related memory leaks.
+
+ver 4.55:
+       Add support for POSIX capabilities dropping.
+       Add special quirk for the Nokia CK-20W car kit.
+       Fix error code handling for AVDTP SetConfiguration response.
+       Fix updating out of range list when RSSI hasn't changed.
+       Fix various memory leaks and unnecessary error checks.
+
+ver 4.54:
+       Add introspection interface to output of introspection calls.
+       Fix stream handling when media transport disconnects prematurely.
+       Fix command timeout handling when there's no stream.
+       Fix headset_suspend_stream behavior for invalid states
+       Fix issue with AVDTP ABORTING state transition.
+       Fix issue with AVDTP suspend while closing.
+
+ver 4.53:
+       Fix issue with telephony connection state notifications.
+       Fix AVDTP stream leak for invalid media transport config.
+       Fix audio connection authorization handling with timeouts.
+       Fix race condition in authorizing audio connections.
+       Fix device authorized setting for AVRCP-only connections.
+       Fix duplicate attempts from device to connect signal channel.
+
+ver 4.52:
+       Add AVCTP support to test utility.
+       Fix AVDTP Abort when transport closes before response.
+       Fix authorization when the audio profiles are slow to connect.
+       Fix potential AVDTP reference leaks.
+
+ver 4.51:
+       Add utility for basic AVDTP testing.
+       Add support for configuring L2CAP FCS option.
+       Fix discovery mode for CUPS 1.4.x and later.
+       Fix global state tracking of audio service.
+       Fix last issues with the new build system.
+
+ver 4.50:
+       Fix issue with missing manual pages in distribution.
+       Fix issue with the configuration and state directories.
+       Fix issue with creating include directory.
+       Fix dependencies of include file generation.
+
+ver 4.49:
+       Add simple test program for basic GAP testing.
+       Add support for confirmation requests to agent example.
+       Add support for full non-recursive build.
+       Add five millisecond delay for Simple Pairing auto-accept.
+       Fix Class of Device setting when InitiallyPowered=false.
+
+ver 4.48:
+       Add library function for comparing UUID values.
+       Add support for creating all plugins as builtins.
+       Add support for async handling of service class changes.
+       Add support for source interface to audio IPC.
+       Fix device name settings when device is off or down.
+       Fix issue with enabled SCO server when not necessary.
+       Fix missing D-Bus access policy for CUPS backend.
+       Fix discovery results of CUPS backend.
+       Fix initialization handling of Maemo telephony.
+
+ver 4.47:
+       Add support for RFKILL unblock handling.
+       Add support for serial proxy configurations.
+       Add support for caching service class updates.
+       Fix issues with updating SDP service records.
+       Fix usage of limited discoverable mode.
+       Remove deprecated methods and signals for AudioSource.
+
+ver 4.46:
+       Add support for A2DP sink role.
+       Fix clearing svc_cache before the adapter is up.
+       Fix various pointer after free usages.
+       Fix various memory leaks.
+
+ver 4.45:
+       Fix UDEV_DATADIR fallback if pkg-config fails.
+       Fix adapter cleanup and setup prototypes.
+       Fix double-free with out-of-range devices.
+       Fix inband ring setting to be per-headset.
+       Fix handling of Maemo CSD startup.
+
+ver 4.44:
+       Add some missing manual pages.
+       Fix missing number prefix when installing udev rules.
+       Fix program prefix used in Bluetooth udev rules.
+       Fix three-way calling indicator order.
+       Fix downgrade/upgrade of callheld indicator.
+       Fix +CIEV sending when indicator value changes.
+       Fix signal handling for Maemo telephony driver.
+       Fix parsing issues with messages from Maemo CSD.
+       Fix issue with duplicate active calls.
+
+ver 4.43:
+       Add support for udev based on-demand startup.
+       Fix verbose error reporting of CUPS backend.
+       Fix various string length issues.
+       Fix issues with Maemo telephony driver.
+       Fix another device setup and temporary flag issue.
+       Fix and update example agent implementation.
+
+ver 4.42:
+       Add TI WL1271 to Texas Instruments chip list.
+       Add special udev mode to bluetoothd.
+       Fix regression when there is no agent registered.
+       Fix error return when bonding socket hang up.
+       Fix SCO server socket for HFP handsfree role.
+       Fix shutdown on SCO socket before closing.
+       Fix shutdown on A2DP audio stream channel before closing.
+       Fix issue with asserting on AVDTP reference count bugs.
+       Fix authorization denied issue with certain headsets.
+       Fix AVRCP UNITINFO and SUBUNIT INFO responses.
+       Fix discovery cancel issues in case SDP discovery fails.
+
+ver 4.41:
+       Fix pairing even if the ACL gets dropped before successful SDP.
+       Fix regression which caused device to be removed after pairing.
+       Fix HSP record fetching when remote device doesn't support it.
+       Fix SDP discovery canceling when clearing hs->pending.
+       Fix headset never connecting on the first attempt.
+       Fix headset state tracking if bt_search_service() fails.
+       Fix maximum headset connection count check.
+       Fix AVDTP Discover timeout handling.
+       Fix also UI_SET_KEYBIT for the new pause and play key codes.
+
+ver 4.40:
+       Add telephony driver for oFono telephony stack.
+       Add support for Dell specific HID proxy switching.
+       Add support for running hid2hci from udev.
+       Add mapping for AVRCP Play and Pause to dedicated key codes.
+       Fix AVRCP keycodes to better match existing X keymap support.
+       Fix various quoting issues within telephony support.
+       Fix memory allocation issue when generating PDUs for SDP.
+       Fix race condition on device removal.
+       Fix non-cancelable issue with CreateDevice method.
+       Fix non-working CancelDiscovery method call.
+
+ver 4.39:
+       Add workaround for dealing with unknown inquiry complete.
+       Fix discovering when using software scheduler.
+       Fix wrong NoInputNoOutput IO capability string.
+       Fix race condition with agent during pairing.
+       Fix agent cancellation for security mode 3 acceptor failure.
+       Fix temporary flag removal when device creation fails.
+       Fix hciattach to use ppoll instead of poll.
+       Fix service class update when adapter is down.
+       Fix service classes race condition during startup.
+       Fix release of audio client before freeing the device.
+
+ver 4.38:
+       Add support for builtin plugins.
+       Add framework for adapter operations.
+       Add constants for Enhanced Retransmission modes.
+       Fix HCI socket leak in device_remove_bonding.
+       Fix various format string issues.
+       Fix crashes with various free functions.
+       Fix issues with Headset and A2DP drivers to load again.
+       Fix sending AVRCP button released passthrough messages
+       Fix bug which prevent input devices to work after restart.
+       Fix issue with interpretation of UUID-128 as channel.
+
+ver 4.37:
+       Add version value for Bluetooth 3.0 devices.
+       Add additional L2CAP extended feature mask bits.
+       Add support for loading plugins in priority order.
+       Add support for more detailed usage of disconnect watches.
+       Add support for AVRCP volume control.
+       Add saturated clipping of SBC decoder output to 16-bit.
+       Fix potentially infinite recursion of adapter_up.
+       Fix SCO handling in the case of an incoming call.
+       Fix input service to use confirm callback.
+       Fix cleanup of temporary device entries from storage.
+
+ver 4.36:
+       Add proper tracking of AVCTP connect attempts.
+       Add support to channel pattern in Serial interface.
+       Fix A2DP sink crash if removing device while connecting.
+       Fix error handling if HFP indicators aren't initialized.
+       Fix segfault while handling an incoming SCO connection.
+       Fix Serial.Disconnect to abort connection attempt.
+
+ver 4.35:
+       Add support for Handsfree profile headset role.
+       Add additional checks for open SEIDs from clients.
+       Fix device removal while audio IPC client is connected.
+       Fix device removal when an authorization request is pending.
+       Fix incoming AVDTP connect while authorization in progress.
+       Fix disconnection timers for audio support.
+       Fix various potential NULL pointer deferences.
+       Fix callheld indicator value for multiple calls.
+       Fix voice number type usage.
+       Fix GDBus watch handling.
+
+ver 4.34:
+       Add support for version checks of plugins.
+       Add support for class property on adapter interface.
+       Add support for second SDP attempt after connection reset.
+       Add support for more detailed audio states.
+       Add support for HFP+A2DP auto connection feature.
+       Add support for new and improved audio IPC.
+       Add program for testing audio IPC interface.
+       Fix various AVDTP qualification related issues.
+       Fix broken SDP AttributeIdList parsing.
+       Fix invalid memory access of SDP URL handling.
+       Fix local class of device race conditions.
+       Fix issue with periodic inquiry on startup.
+       Fix missing temporary devices in some situations.
+       Fix SBC alignment issue for encoding with four subbands.
+
+ver 4.33:
+       Add Paired property to the DeviceFound signals.
+       Add support for Headset profile 1.2 version.
+       Fix broken network configuration when IPv6 is disabled.
+       Fix network regression that caused disconnection.
+       Fix SDP truncation of strings with NULL values.
+       Fix service discovery handling of CUPS helper.
+
+ver 4.32:
+       Fix broken SDP record handling.
+       Fix SDP data buffer parsing.
+       Fix more SDP memory leaks.
+       Fix read scan enable calls.
+       Fix A2DP stream handling.
+
+ver 4.31:
+       Add support for new BtIO helper library.
+       Fix AVDTP session close issue.
+       Fix SDP memory leaks.
+       Fix various uninitialized memory issues.
+       Fix duplicate signal emissions.
+       Fix property changes request handling.
+       Fix class of device storage handling.
+
+ver 4.30:
+       Add CID field to L2CAP socket address structure.
+       Fix reset of authentication requirements after bonding.
+       Fix storing of link keys when using dedicated bonding.
+       Fix storing of pre-Bluetooth 2.1 link keys.
+       Fix resetting trust settings on every reboot.
+       Fix handling of local name changes.
+       Fix memory leaks in hciconfig and hcitool
+
+ver 4.29:
+       Use AVRCP version 1.0 for now.
+       Decrease AVDTP idle timeout to one second.
+       Delay AVRCP connection when remote device connects A2DP.
+       Add workaround for AVDTP stream setup with broken headsets.
+       Add missing three-way calling feature bit for Handsfree.
+       Fix handsfree callheld indicator updating.
+       Fix parsing of all AT commands within the buffer.
+       Fix authentication replies when disconnected.
+       Fix handling of debug combination keys.
+       Fix handling of changed combination keys.
+       Fix handling of link keys when using no bonding.
+       Fix handling of invalid/unknown authentication requirements.
+       Fix closing of L2CAP raw socket used for dedicated bonding.
+
+ver 4.28:
+       Add AVDTP signal fragmentation support.
+       Add more SBC performance optimizations.
+       Add more SBC audio quality improvements.
+       Use native byte order for audio plugins.
+       Set the adapter alias only after checking the EIR data.
+       Fix auto-disconnect issue with explicit A2DP connections.
+       Fix invalid memory access of ALSA plugin.
+       Fix compilation with -Wsign-compare.
+
+ver 4.27:
+       Add more SBC optimization (MMX and ARM NEON).
+       Add BT_SECURITY and BT_DEFER_SETUP definitions.
+       Add support for deferred connection setup.
+       Add support for fragmentation of data packets.
+       Add option to trigger dedicated bonding.
+       Follow MITM requirements from remote device.
+       Require MITM for dedicated bonding if capabilities allow it.
+       Fix IO capabilities for non-pairing and pairing cases.
+       Fix no-bonding connections in non-bondable mode.
+       Fix new pairing detection with SSP.
+       Fix bonding with pre-2.1 devices and newer kernels.
+       Fix LIAC setting while toggling Pairable property.
+       Fix device creation for incoming security mode 3 connects.
+       Fix crash within A2DP with bogus pointer.
+       Fix issue with sdp_copy_record() function.
+       Fix crash with extract_des() if sdp_uuid_extract() fails.
+
+ver 4.26:
+       Use of constant shift in SBC quantization code.
+       Add possibility to analyze 4 blocks at once in encoder.
+       Fix correct handling of frame sizes in the encoder.
+       Fix for big endian problems in SBC codec.
+       Fix audio client socket to always be non-blocking.
+       Update telephony support for Maemo.
+
+ver 4.25:
+       Fix receiving data over the audio control socket.
+       Fix subbands selection for joint-stereo in SBC encoder.
+       Add new SBC analysis filter function.
+
+ver 4.24:
+       Fix signal emissions when removing adapters.
+       Fix missing adapter signals on exit.
+       Add support for bringing adapters down on exit.
+       Add support for RememberPowered option.
+       Add support for verbose compiler warnings.
+       Add more options to SBC encoder.
+
+ver 4.23:
+       Update audio IPC for better codec handling.
+       Fix bitstream optimization for SBC encoder.
+       Fix length header values of IPC messages.
+       Fix multiple coding style violations.
+       Fix FindDevice to handle temporary devices.
+       Add configuration option for DeviceID.
+       Add support for InitiallyPowered option.
+       Add missing signals for manager properties.
+       Add telephony support for Maemo.
+
+ver 4.22:
+       Add deny statements to D-Bus access policy.
+       Add support for LegacyPairing property.
+       Add support for global properties.
+       Add more commands to telephony testing script.
+       Add sender checks for serial and network interfaces.
+       Remove deprecated methods and signals from input interface.
+       Remove deprecated methods and signals from network interface.
+       Remove OffMode option and always use device down.
+
+ver 4.21:
+       Fix adapter initialization logic.
+       Fix adapter setup and start security manager early.
+       Fix usage issue with first_init variable.
+
+ver 4.20:
+       Cleanup session handling.
+       Cleanup mode setting handling.
+       Fix issue with concurrent audio clients.
+       Fix issue with HFP/HSP suspending.
+       Fix AT result code syntax handling.
+       Add Handsfree support for AT+NREC.
+       Add PairableTimeout adapter property.
+
+ver 4.19:
+       Fix installation of manual pages for old daemons.
+       Fix D-Bus signal emmissions for CreateDevice.
+       Fix issues with UUID probing.
+       Fix +BSRF syntax issue.
+       Add Pairable adapter property.
+       Add sdp_copy_record() library function.
+
+ver 4.18:
+       Fix release before close issue with RFCOMM TTYs.
+       Fix Connected property on input interface.
+       Fix DeviceFound signals during initial name resolving.
+       Fix service discovery handling.
+       Fix duplicate UUID detection.
+       Fix SBC gain mismatch and decoding handling.
+       Add more options to SBC encoder and decoder.
+       Add special any adapter object for service interface.
+       Add variable prefix to adapter and device object paths.
+
+ver 4.17:
+       Fix SBC encoder not writing last frame.
+       Fix missing timer for A2DP suspend.
+       Add more supported devices to hid2hci utility.
+       Add additional functionality to Handsfree support.
+
+ver 4.16:
+       Fix wrong parameter usage of watch callbacks.
+       Fix parameters for callback upon path removal.
+       Fix unloading of adapter drivers.
+
+ver 4.15:
+       Fix various A2DP state machine issues.
+       Fix some issues with the Handsfree error reporting.
+       Fix format string warnings with recent GCC versions.
+       Remove dependency on GModule.
+
+ver 4.14:
+       Fix types of property arrays.
+       Fix potential crash with input devices.
+       Fix PS3 BD remote input event generation.
+       Allow dynamic adapter driver registration.
+       Update udev rules.
+
+ver 4.13:
+       Fix service discovery and UUID handling.
+       Fix bonding issues with Simple Pairing.
+       Fix file descriptor misuse of SCO connections.
+       Fix various memory leaks in the device handling.
+       Fix AVCTP disconnect handling.
+       Fix GStreamer modes for MP3 encoding.
+       Add operator selection to Handsfree support.
+
+ver 4.12:
+       Fix crash with missing icon value.
+       Fix error checks of HAL plugin.
+       Fix SCO server socket cleanup on exit.
+       Fix memory leaks from DBusPendingCall.
+       Fix handling of pending authorization requests.
+       Fix missing protocol UUIDs in record pattern.
+
+ver 4.11:
+       Change SCO server socket into a generic one.
+       Add test script for dummy telephony plugin.
+       Fix uninitialized reply of multiple GetProperties methods.
+
+ver 4.10:
+       Fix memory leaks with HAL messages.
+       Add more advanced handsfree features.
+       Add properties to audio, input and network interfaces.
+       Stop device discovery timer on device removal.
+
+ver 4.9:
+       Fix signals for Powered and Discoverable properties.
+       Fix handling of Alias and Icon properties.
+       Fix duplicate entries for service UUIDs.
+
+ver 4.8:
+       Fix retrieving of formfactor value.
+       Fix retrieving of local and remote extended features.
+       Fix potential NULL pointer dereference during pairing.
+       Fix crash with browsing due to a remotely initated pairing.
+
+ver 4.7:
+       Fix pairing and service discovery logic.
+       Fix crashes during suspend and resume.
+       Fix race condition within devdown mode.
+       Add RequestSession and ReleaseSession methods.
+       Add Powered and Discoverable properties.
+       Add Devices property and deprecate ListDevices.
+       Add workaround for a broken carkit from Nokia.
+
+ver 4.6:
+       Fix Device ID record handling.
+       Fix service browsing and storage.
+       Fix authentication and encryption for input devices.
+       Fix adapter name initialization.
+
+ver 4.5:
+       Fix initialization issue with new adapters.
+       Send HID authentication request without blocking.
+       Hide the verbose SDP debug behind SDP_DEBUG.
+       Add extra UUIDs for service discovery.
+       Add SCO server socket listener.
+       Add authorization support to service plugin.
+
+ver 4.4:
+       Add temporary fix for the CUPS compile issue.
+       Add service-api.txt to distribution.
+       Mention the variable prefix of an object path
+
+ver 4.3:
+       Add dummy driver for telephony support.
+       Add support for discovery sessions.
+       Add service plugin for external services.
+       Various cleanups.
+
+ver 4.2:
+       Avoid memory copies in A2DP write routine.
+       Fix broken logic with Simple Pairing check and old kernels.
+       Allow non-bondable and outgoing SDP without agent.
+       Only remove the bonding for non-temporary devices.
+       Cleanup various unnecessary includes.
+       Make more unexported functions static.
+       Add basic infrastructure for gtk-doc support.
+
+ver 4.1:
+       Add 30 seconds timeout to BNEP connection setup phase.
+       Avoid memory copies in A2DP write routine for ALSA.
+       Make sure to include compat/sdp.h in the distribution.
+
+ver 4.0:
+       Initial public release.
+
+ver 3.36:
+       Add init routines for TI BRF chips.
+       Add extra attributes to the serial port record.
+       Add example record for headset audio gateway record.
+       Use Handsfree version 0x0105 for the gateway role.
+       Fix SDP record registration with specific record handles.
+       Fix BCSP sent/receive handling.
+       Fix various includes for cross-compilation.
+       Allow link mode settings for outgoing connections.
+       Allow bonding during periodic inquiry.
+
+ver 3.35:
+       Add two additional company identifiers.
+       Add UUID-128 support for service discovery.
+       Fix usage of friendly names for service discovery.
+       Fix authorization when experiemental is disabled.
+       Fix uninitialized variable in passkey request handling.
+       Enable output of timestamps for l2test and rctest.
+
+ver 3.34:
+       Replace various SDP functions with safe versions.
+       Add additional length validation for incoming SDP packets.
+       Use safe function versions for SDP client handling.
+       Fix issue with RemoveDevice during discovery procedure.
+       Fix collect for non-persistent service records.
+
+ver 3.33:
+       Add functions for reading and writing the link policy settings.
+       Add definition for authentication requirements.
+       Add support for handling Simple Pairing.
+       Add Simple Pairing support to Agent interface.
+       Add ReleaseMode method to Adapter interface.
+       Add DiscoverServices method to Device interface.
+       Remove obsolete code and cleanup the repository.
+       Move over to use the libgdbus API.
+       Enable PIE by default if supported.
+
+ver 3.32:
+       Add OCF constants for synchronous flow control enabling.
+       Add support for switching HID proxy devices from Dell.
+       Add more Bluetooth client/server helper functions.
+       Add support for input service idle timeout option.
+       Fix BNEP reconnection handling.
+       Fix return value for snd_pcm_hw_params() calls.
+       Use upper-case addresses for object paths.
+       Remove HAL support helpers.
+       Remove inotify support.
+       Remove service daemon activation handling.
+       Remove uneeded D-Bus API extension.
+
+ver 3.31:
+       Create device object for all pairing cases.
+       Convert authorization to internal function calls.
+       Add initial support for Headset Audio Gateway role.
+       Add generic Bluetooth helper functions for GLib.
+       Fix endiannes handling of connection handles.
+       Don't optimize when debug is enabled.
+
+ver 3.30:
+       Convert audio service into a plugin.
+       Convert input service into a plugin.
+       Convert serial service into a plugin.
+       Convert network service into a plugin.
+       Emit old device signals when a property is changed.
+       Fix missing DiscoverDevices and CancelDiscovery methods.
+       Add another company identifier.
+       Add basic support for Bluetooth sessions.
+       Add avinfo utility for AVDTP/A2DP classification.
+       Remove build option for deprecated sdpd binary.
+
+ver 3.29:
+       Introduce new D-Bus based API.
+       Add more SBC optimizations.
+       Add support for PS3 remote devices.
+       Fix alignment trap in SDP server.
+       Fix memory leak in sdp_get_uuidseq_attr function.
+
+ver 3.28:
+       Add support for MCAP UUIDs.
+       Add support for role switch for audio service.
+       Add disconnect timer for audio service.
+       Add disconnect detection to ALSA plugin.
+       Add more SBC optimizations.
+       Fix alignment issue of SDP server.
+       Remove support for SDP parsing via expat.
+
+ver 3.27:
+       Update uinput.h with extra key definitions.
+       Add support for input connect/disconnect callbacks.
+       Add ifdefs around some baud rate definitions.
+       Add another company identifier.
+       Add proper HFP service level connection handling.
+       Add basic headset automatic disconnect support.
+       Add support for new SBC API.
+       Fix SBC decoder noise at high bitpools.
+       Use 32-bit multipliers for further SBC optimization.
+       Check for RFCOMM connection state in SCO connect callback.
+       Make use of parameters selected in ALSA plugin.
+
+ver 3.26:
+       Fix compilation issues with UCHAR_MAX, USHRT_MAX and UINT_MAX.
+       Improve handling of different audio transports.
+       Enable services by default and keep old daemons disabled.
+
+ver 3.25:
+       Add limited support for Handsfree profile.
+       Add limited support for MPEG12/MP3 codec.
+       Add basic support for UNITINFO and SUBUNITINFO.
+       Add more SBC optimizations.
+       Fix external service (un)registration.
+       Allow GetInfo and GetAddress to fail.
+
+ver 3.24:
+       Add definitions for MDP.
+       Add TCP connection support for serial proxy.
+       Add fix for Logitech HID proxy switching.
+       Add missing macros, MIN, MAX, ABS and CLAMP.
+       Add more SBC encoder optimizations.
+       Add initial mechanism to handle headset commands.
+       Fix connecting to handsfree profile headsets.
+       Use proper function for checking signal name.
+
+ver 3.23:
+       Fix remote name request handling bug.
+       Fix key search function to honor the mmap area size.
+       Fix Avahi integration of network service.
+       Add new plugin communication for audio service.
+       Enable basic AVRCP support by default.
+       More optimizations to the SBC library.
+       Create common error definitions.
+
+ver 3.22:
+       Add missing include file from audio service.
+       Add SBC conformance test utility.
+       Add basic uinput support for AVRCP.
+       Fix L2CAP socket leak in audio service.
+       Fix buffer usage in GStreamer plugin.
+       Fix remote name request event handling.
+
+ver 3.21:
+       Add constant for Bluetooth socket options level.
+       Add initial AVRCP support.
+       Add A2DP sink support to GStreamer plugin.
+       Fix interoperability with A2DP suspend.
+       Fix sign error in 8-subband encoder.
+       Fix handling of service classes length size.
+       Store Extended Inquiry Response data information.
+       Publish device id information through EIR.
+       Support higher baud rates for Ericcson based chips.
+
+ver 3.20:
+       Fix GStreamer plugin file type detection.
+       Fix potential infinite loop in inotify support.
+       Fix D-Bus signatures for dict handling.
+       Fix issues with service activation.
+       Fix SDP failure handling of audio service.
+       Fix various memory leaks in input service.
+       Add secure device creation method to input service.
+       Add service information methods to serial service.
+       Add config file support to network service.
+       Add scripting capability to network service.
+       Add special on-mode handling.
+       Add optimization for SBC encoder.
+       Add tweaks for D-Bus 1.1.x libraries.
+       Add support for inquiry transmit power level.
+
+ver 3.19:
+       Limit range of bitpool announced while in ACP side.
+       Use poll instead of usleep to wait for worker thread.
+       Use default event mask from the specification.
+       Add L2CAP mode constants.
+       Add HID proxy support for Logitech diNovo Edge dongle.
+       Add refresh option to re-request device names.
+       Show correct connection link type.
+
+ver 3.18:
+       Don't allocate memory for the Bluetooth base UUID.
+       Implement proper locking for headsets.
+       Fix various A2DP SEP locking issues.
+       Fix and cleanup audio stream handling.
+       Fix stream starting if suspend request is pending.
+       Fix A2DP and AVDTP endianess problems.
+       Add network timeout and retransmission support.
+       Add more detailed decoding of EIR elements.
+
+ver 3.17:
+       Fix supported commands bit calculation.
+       Fix crashes in audio and network services.
+       Check PAN source and destination roles.
+       Only export the needed symbols for the plugins.
+
+ver 3.16:
+       Update company identifier list.
+       Add support for headsets with SCO audio over HCI.
+       Add support for auto-create through ALSA plugin.
+       Add support for ALSA plugin parameters.
+       Add GStreamer plugin with SBC decoder and encoder.
+       Fix network service NAP, GN and PANU servers.
+       Set EIR information from SDP database.
+
+ver 3.15:
+       Add A2DP support to the audio service.
+       Add proxy support to the serial service.
+       Extract main service class for later use.
+       Set service classes value from SDP database.
+
+ver 3.14:
+       Add missing signals for the adapter interface.
+       Add definitions and functions for Simple Pairing.
+       Add basic commands for Simple Pairing.
+       Add correct Simple Pairing and EIR interaction.
+       Add missing properties for remote information.
+       Add EPoX endian quirk to the input service.
+       Fix HID descriptor import and storage functions.
+       Fix handling of adapters in raw mode.
+       Fix remote device listing methods.
+
+ver 3.13:
+       Fix some issues with the headset support.
+       Fix concurrent pending connection attempts.
+       Fix usage of devname instead of netdev.
+       Add identifier for Nokia SyncML records.
+       Add command for reading the CSR chip revision.
+       Add generic CSR radio test support.
+       Update HCI command table.
+
+ver 3.12:
+       Add missing HCI command text descriptions
+       Add missing HCI commands structures.
+       Add missing HCI event structures.
+       Add common bachk() function.
+       Add support for limited discovery mode.
+       Add support for setting of event mask.
+       Add GetRemoteServiceIdentifiers method.
+       Add skeleton for local D-Bus server.
+       Add headset gain control methods.
+       Fix various headset implementation issues.
+       Fix various serial port service issues.
+       Fix various input service issues.
+       Let CUPS plugin discover printers in range.
+       Improve the BCM2035 UART init routine.
+       Ignore connection events for non-ACL links.
+
+ver 3.11:
+       Update API documentation.
+       Minimize SDP root records and browse groups.
+       Use same decoder for text and URL strings.
+       Fix URL data size handling.
+       Fix SDP pattern extraction for XML.
+       Fix network connection persistent state.
+       Add network connection helper methods.
+       Add initial version of serial port support.
+       Add class of device tracking.
+
+ver 3.10.1:
+       Add option to disable installation of manual pages.
+       Fix input service encryption setup.
+       Fix serial service methods.
+       Fix network service connection handling.
+       Provide a simple init script.
+
+ver 3.10:
+       Add initial version of network service.
+       Add initial version of serial service.
+       Add initial version of input service.
+       Add initial version of audio service.
+       Add authorization framework.
+       Add integer based SBC library.
+       Add version code for Bluetooth 2.1 specification.
+       Add ESCO_LINK connection type constant.
+       Export sdp_uuid32_to_uuid128() function.
+
+ver 3.9:
+       Add RemoteDeviceDisconnectRequested signal.
+       Add updated service framework.
+       Add embedded GLib library.
+       Add support for using system GLib library.
+       Create internal SDP server library.
+
+ver 3.8:
+       Sort discovered devices list based on their RSSI.
+       Send DiscoverableTimeoutChanged signal.
+       Fix local and remote name validity checking.
+       Add ListRemoteDevices and ListRecentRemoteDevices methods.
+       Add basic integration of confirmation concept.
+       Add support for service record description via XML.
+       Add support for external commands to the RFCOMM utility.
+       Add experimental service and authorization API.
+       Add functions for registering binary records.
+
+ver 3.7:
+       Fix class of device handling.
+       Fix error replies with pairing and security mode 3.
+       Fix disconnect method for RFCOMM connections.
+       Add match pattern for service searches.
+       Add support for prioritized watches.
+       Add additional PDU length checks.
+       Fix CSRC value for partial responses.
+
+ver 3.6.1:
+       Fix IO channel race conditions.
+       Fix pairing issues on big endian systems.
+       Fix pairing issues with page timeout errors.
+       Fix pairing state for security mode 3 requests.
+       Switch to user as default security manager mode.
+
+ver 3.6:
+       Update D-Bus based RFCOMM interface support.
+       Use L2CAP raw sockets for HCI connection creation.
+       Add periodic discovery support to the D-Bus interface.
+       Add initial support for device names via EIR.
+       Add proper UTF-8 validation of device names.
+       Add support for the J-Three keyboard.
+       Fix issues with the asynchronous API for SDP.
+
+ver 3.5:
+       Fix and cleanup watch functionality.
+       Add support for periodic inquiry mode.
+       Add support for asynchronous SDP requests.
+       Add more request owner tracking.
+       Add asynchronous API for SDP.
+       Document pageto and discovto options.
+
+ver 3.4:
+       Improve error reporting for failed HCI commands.
+       Improve handling of CancelBonding.
+       Fixed bonding reply message when disconnected.
+       Fix UUID128 string lookup handling.
+       Fix malloc() versus bt_malloc() usage.
+
+ver 3.3:
+       Don't change inquiry mode for Bluetooth 1.1 adapters.
+       Add udev rules for Bluetooth serial PCMCIA cards.
+       Add Cancel and Release methods for passkey agents.
+       Add GetRemoteClass method.
+       Convert to using ppoll() and pselect().
+       Initialize allocated memory to zero.
+       Remove bcm203x firmware loader.
+       Remove kernel specific timeouts.
+       Add additional private data field for SDP sessions.
+       Add host controller to host flow control defines.
+       Add host number of completed packets defines.
+       Initialize various memory to zero before usage.
+
+ver 3.2:
+       Only check for the low-level D-Bus library.
+       Update possible device minor classes.
+       Fix timeout for pending reply.
+       Add more Inquiry with RSSI quirks.
+       Sleep only 100 msecs for device detection.
+       Don't send BondingCreated on link key renewal.
+       Allow storing of all UTF-8 remote device names.
+       Create storage filenames with a generic function.
+       Fix handling of SDP strings.
+       Add adapter type for SDIO cards.
+       Add features bit for link supervision timeout.
+
+ver 3.1:
+       Add missing placeholders for feature bits.
+       Fix handling of raw mode devices.
+       Fix busy loop in UUID extraction routine.
+       Remove inquiry mode setting.
+       Remove auth and encrypt settings.
+
+ver 3.0:
+       Implement the new BlueZ D-Bus API.
+       Fix broken behavior with EVT_CMD_STATUS.
+       Add features bit for pause encryption.
+       Add additional EIR error code.
+       Add more company identifiers.
+       Add another Phonebook Access identifier.
+       Update sniff subrating data structures.
+
+ver 2.25:
+       Use %jx instead of %llx for uint64_t and int64_t.
+       Allow null-terminated text strings.
+       Add UUID for N-Gage games.
+       Add UUID for Apple Macintosh Attributes.
+       Add Apple attributes and iSync records.
+       Add definitions for Apple Agent.
+       Add support for the Handsfree Audio Gateway service.
+       Add support for choosing a specific record handle.
+       Add support for dialup/telephone connections.
+       Add definitions for Apple Agent.
+       Add support for record handle on service registration.
+
+ver 2.24:
+       Fix display of SDP text and data strings.
+       Add support for device scan property.
+       Add support for additional access protocols.
+       Update the D-Bus policy configuration file.
+
+ver 2.23:
+       Update the new D-Bus interface.
+       Make dfutool ready for big endian architectures.
+       Add support for AVRCP specific service records.
+       Add support for writing complex BCCMD commands.
+       Add the new BCCMD interface utility.
+       Add MicroBCSP implementation from CSR.
+       Add constants and definitions for sniff subrating.
+       Add support for allocation of binary text elements.
+       Add HCI emulation tool.
+       Add fake HID support for old EPoX presenters.
+       Reject connections from unknown HID devices.
+       Fix service discovery deadlocks with Samsung D600 phones.
+
+ver 2.22:
+       Remove D-Bus 0.23 support.
+       Add initial version of the new D-Bus interface.
+       Add support for extended inquiry response commands.
+       Add support for the Logitech diNovo Media Desktop Laser.
+       Add compile time buffer checks (FORTIFY SOURCE).
+       Decode reserved LMP feature bits.
+       Fix errno overwrite problems.
+       Fix profile descriptor problem with Samsung phones.
+
+ver 2.21:
+       Move create_dirs() and create_file() into the textfile library.
+       Let textfile_put() also replace the last key value pair.
+       Fix memory leaks with textfile_get() usage.
+       Fix infinite loops and false positive matches.
+       Don't retrieve stored link keys for RAW devices.
+       Document the putkey and delkey commands.
+       Show supported commands also in clear text.
+       Support volatile changes of the BD_ADDR for CSR chips.
+       Add support for identification of supported commands.
+       Add missing OCF declarations for the security filter.
+       Add two new company identifiers.
+
+ver 2.20:
+       Add UUIDs for video distribution profile.
+       Add UUIDs for phonebook access profile.
+       Add attribute identifier for supported repositories.
+       Add definitions for extended inquiry response.
+       Add functions for extended inquiry response.
+       Add support for extended inquiry response.
+       Add support for HotSync service record.
+       Add support for ActiveSync service record.
+       Add ActiveSync networking support.
+       Fix D-Bus crashes with new API versions.
+
+ver 2.19:
+       Fix the GCC 4.0 warnings.
+       Fix the routing for dealing with raw devices.
+       Fix off by one memory allocation error.
+       Fix security problem with escape characters in device name.
+       Add per device service record functions.
+       Send D-Bus signals for inquiry results and remote name resolves.
+       Add support for device specific SDP records.
+
+ver 2.18:
+       Support D-Bus 0.23 and 0.33 API versions.
+       Support reading of complex BCCMD values.
+       Support minimum and maximum encryption key length.
+       Add support for reading and writing the inquiry scan type.
+       Add definitions for connection accept timeout and scan enable.
+       Add support for inquiry scan type.
+       Add tool for the CSR BCCMD interface.
+       Add first draft of the Audio/Video control utility.
+       Add disconnect timer support for the A2DP ALSA plugin.
+       Make SBC parameters configurable.
+       Replace non-printable characters in device names.
+       Remove hci_vhci.h header file.
+       Remove hci_uart.h header file.
+
+ver 2.17:
+       Set the storage directory through ${localstatedir}.
+       Add the textfile library for ASCII based file access.
+       Add support for return link keys event.
+       Add support for voice setting configuration.
+       Add support for page scan timeout configuration.
+       Add support for storing and deleting of stored link keys.
+       Add support for searching for services with UUID-128.
+       Add support for retrieving all possible service records.
+       Add support for a raw mode view of service records.
+       Add support for HID information caching in hidd.
+       Add support for authentication in pand and dund.
+       Add support for changing BD_ADDR of CSR chips.
+       Add pskey utility for changing CSR persistent storage values.
+       Add the firmware upgrade utility.
+       Add connection caching for the A2DP ALSA plugin.
+       Add functions for stored link keys.
+       Add definitions for PIN type and unit key.
+       Add SDP_WAIT_ON_CLOSE flag for sdp_connect().
+       Include stdio.h in bluetooth.h header file.
+       Include sys/socket.h in the header files.
+
+ver 2.16:
+       Store link keys in ASCII based file format.
+       Support device name caching.
+       Support zero length data sizes in l2test.
+       Change default l2ping data size to 44 bytes.
+       Hide the server record and the public browse group root.
+       Read BD_ADDR if not set and if it is a raw device.
+       Add SDP language attributes.
+       Add support for browsing the L2CAP group.
+       Add support for stored pin codes for outgoing connections.
+       Add support for local commands and extended features.
+       Add support for reading CSR panic and fault codes.
+       Add config option for setting the inquiry mode.
+       Add OUI decoding support.
+       Use unlimited inquiry responses as default.
+       Use cached device names for PIN request.
+       Use the clock offset when getting the remote names.
+       Add function for reading local supported commands.
+       Add function for reading local extended features.
+       Add function for reading remote extended features.
+       Add function for getting the remote name with a clock offset.
+       Add function for extracting the OUI from a BD_ADDR.
+       Add inquiry info structure with RSSI and page scan mode.
+       Fix buffer allocation for features to string conversion.
+       Support inquiry with unlimited number of responses.
+
+ver 2.15:
+       Enable the RFCOMM service level security.
+       Add deprecated functions for reading the name.
+       Add command for reading the clock offset.
+       Add command for reading the clock.
+       Add function for reading the clock.
+       Add function for reading the local Bluetooth address.
+       Add function for reading the local supported features.
+       Don't configure raw devices.
+       Don't set inquiry scan or page scan on raw devices.
+       Don't show extended information for raw devices.
+       Support L2CAP signal sizes bigger than 2048 bytes.
+       Cleanup of the socket handling code of the test programs.
+       Use better way for unaligned access.
+       Remove sdp_internal.h and its usage.
+
+ver 2.14:
+       Make use of additional connection information.
+       Use library function for reading the RSSI.
+       Use library function for reading the link quality.
+       Use library function for reading the transmit power level.
+       Use library functions for the link supervision timeout.
+       Add tool for changing the device address.
+       Add function for reading the RSSI.
+       Add function for reading the link quality.
+       Add function for reading the transmit power level.
+       Add functions for the link supervision timeout.
+       Remove deprecated functions.
+       Update AM_PATH_BLUEZ macro.
+
+ver 2.13:
+       Use file permission 0600 for the link key file.
+       Add support for HID attribute descriptions.
+       Add support for Device ID attributes.
+       Add Device ID and HID attribute definitions.
+       Update the UUID constants and its translations.
+       Update L2CAP socket option definitions.
+       Update connection information definitions.
+       Various whitespace cleanups.
+
+ver 2.12:
+       Inherit the device specific options from the default.
+       Use --device for selecting the source device.
+       Add --nosdp option for devices with resource limitation.
+       Add support and parameter option for secure mode.
+       Add a lot of build ids and hardware revisions.
+       Add service classes and profile ids for WAP.
+       Add simple AM_PATH_BLUEZ macro.
+       Update UUID translation tables.
+       Correct kernel interface for CMTP and HIDP support.
+
+ver 2.11:
+       Initial support for the kernel security manager.
+       Various cleanups to avoid inclusion of kernel headers.
+       Fix output when the CUPS backend is called without arguments.
+       Fix problems with a 64 bit userland.
+       Use Bluetooth library functions if available.
+       Use standard numbering scheme of SDP record handles.
+       Use bit zero for vendor packets in the filter type bitmask.
+       Add SIM Access types for service discovery.
+       Add more audio/video profile translations.
+       Add another company identifier.
+       Add the missing HCI error codes.
+       Add RFCOMM socket options.
+       Add definition for the SECURE link mode.
+       Add functions for reading and writing the inquiry mode.
+       Add functions for AFH related settings and information.
+       Add version identifier for the Bluetooth 2.0 specification.
+       Add a master option to the hidd.
+       Add support for changing the link key of a connection.
+       Add support for requesting encryption on keyboards.
+       Add support for revision information of Digianswer devices.
+       Add support for the Zoom, IBM and TDK PCMCIA cards.
+       Add checks for the OpenOBEX and the ALSA libraries.
+       Add experimental mRouter support.
+
+ver 2.10:
+       Use a define for the configuration directory.
+       Fix string initialization for flags translation.
+       Fix and extend the unaligned access macros.
+       Make compiling with debug information optional.
+       Don't override CFLAGS from configure.
+       Check for usb_get_busses() and usb_interrupt_read().
+       Add optional support for compiling with PIE.
+       Make installation of the init scripts optional.
+       Make compiling with debug information optional.
+       Don't override CFLAGS from configure.
+
+ver 2.9:
+       Retry SDP connect if busy in the CUPS backend.
+       Use packet type and allow role switch in hcitool.
+       Use the functions from the USB library for hid2hci.
+       Add Broadcom firmware loader.
+       Add EPoX endian quirk for buggy keyboards.
+       Add L2CAP info type and info result definitions.
+       Add value for L2CAP_CONF_RFC_MODE.
+       Change RSSI value to signed instead of unsigned.
+       Allow UUID32 values as protocol identifiers.
+       Update the autoconf/automake scripts.
+
+ver 2.8:
+       Use LIBS and LDADD instead of LDFLAGS.
+       Use HIDP subclass field for HID boot protocol.
+       Set olen before calling getsockopt() in pand.
+       Restore signals for dev-up script.
+       Add PID file support for pand.
+       Add size parameter to expand_name() in hcid.
+       Add support for audio source and audio sink SDP records.
+       Add support for HID virtual cable unplug.
+       Add support for AmbiCom BT2000C card.
+       Add defines and UUID's for audio/video profiles.
+       Add AVDTP protocol identifier.
+       Add HIDP subclass field.
+       Add PKGConfig support.
+       Fix the event code of inquiry with RSSI.
+       Remove dummy SDP library.
+
+ver 2.7:
+       Fix display of decoded LMP features.
+       Update company identifiers.
+       Add AFH related types.
+       Add first bits from EDR prototyping specification.
+       Add support for inquiry with RSSI.
+       Add HCRP related SDP functions.
+       Add HIDP header file.
+       Add support for getting the AFH channel map.
+       Add support for AFH mode.
+       Add support for inquiry mode.
+       Add Bluetooth backend for CUPS.
+       Add the hid2hci utility.
+       Add the hidd utility.
+       Add the pand utility.
+       Add the dund utility.
+       More endian bug fixes.
+       Give udev some time to create the RFCOMM device nodes.
+       Release the TTY if no device node is found.
+       New startup script for the Bluetooth subsystem.
+       Update to the autoconf stuff.
+
+ver 2.6:
+       Change default prefix to /usr.
+       Add manpages for hcid and hcid.conf.
+       Add the sdpd server daemon.
+       Add the sdptool utility.
+       Add the ciptool utility.
+       Add new company identifiers.
+       Add BNEP and CMTP header files.
+       Add the SDP library.
+       Use R2 for default value of pscan_rep_mode.
+
+ver 2.5:
+       Add decoding of Bluetooth 1.2 features.
+       Add link manager version parameter for Bluetooth 1.2.
+       Add new company identifiers.
+       Add D-Bus support for PIN request.
+       Support for transmit power level.
+       Support for park, sniff and hold mode.
+       Support for role switch.
+       Support for reading the clock offset.
+       Support for requesting authentication.
+       Support for setting connection encryption.
+       Show revision information for Broadcom devices.
+       Replace unprintable characters in device name.
+       Use R1 for default value of pscan_rep_mode.
+       Fix some 64-bit problems.
+       Fix some endian problems.
+       Report an error on PIN helper failure.
+       Update bluepin script for GTK2.
+
+ver 2.4:
+       Increase number of inquiry responses.
+       Support for transmit power level.
+       Display all 8 bytes of the features.
+       Add support for reading and writing of IAC.
+       Correct decoding class of device.
+       Use Ericsson revision command for ST Microelectronics devices.
+       Display AVM firmware version with 'revision' command.
+       New code for CSR specific revision information.
+       Support for ST Microelectronics specific initialization.
+       Support for 3Com card version 3.0.
+       Support for TDK, IBM and Socket cards.
+       Support for initial baud rate.
+       Update man pages.
+       Fixes for some memory leaks.
+
+ver 2.3:
+       Added const qualifiers to appropriate function arguments.
+       Minor fixes.
+       CSR firmware version is now displayed by 'revision' command.
+       Voice command is working properly on big endian machines.
+       Added support for Texas Bluetooth modules.
+       Added support for high UART baud rates on Ericsson modules.
+       BCSP initialization fixes.
+       Support for role switch command (hcitool).
+       RFCOMM config file parser fixes.
+       Update man pages.
+       Removed GLib dependency.
+
+ver 2.2:
+       Updated RFCOMM header file.
+       Additional HCI command and event defines.
+       Support for voice settings (hciconfig).
+       Minor hcitool fixes.
+       Improved configure script.
+       Added Headset testing tool.
+       Updated man pages.
+       RPM package.
+
+ver 2.1.1:
+       Resurrect hci_remote_name.
+
+ver 2.1:
+       Added hci_{read, write}_class_of_dev().
+       Added hci_{read, write}_current_iac_lap().
+       Added hci_write_local_name().
+       Added RFCOMM header file.
+       Minor fixes.
+       Improved BCSP initialization (hciattach).
+       Support for displaying link quality (hcitool).
+       Support for changing link supervision timeout (hcitool).
+       New RFCOMM TTY configuration tool (rfcomm).
+       Minor fixes and updates.
+
+ver 2.0:
+       Additional company IDs.
+       BCSP initialization (hciattach).
+       Minor hciconfig fixes.
+
+ver 2.0-pr13:
+       Support for multiple pairing modes.
+       Link key database handling fixes.
+
+ver 2.0-pre12:
+       Removed max link key limit. Keys never expire.
+       Link key database is always updated. Reread PIN on SIGHUP (hcid).
+       Bluetooth script starts SDPd, if installed.
+       Other minor fixes.
+
+ver 2.0-pre11:
+       Improved link key management and more verbose logging (hcid).
+       Fixed scan command (hcitool).
+
+ver 2.0-pre10:
+       Fix hci_inquiry function to return errors and accept user buffers.
+       New functions hci_devba, hci_devid, hci_for_each_dev and hci_get_route.
+       Additional company IDs.
+       Makefile and other minor fixes.
+       Support for reading RSSI, remote name and changing
+       connection type (hcitool). 
+       Device initialization fixes (hcid).
+       Other minor fixes and improvements.
+       Build environment cleanup and fixes.
+
+ver 2.0-pre9:
+       Improved bluepin. Working X authentication.
+       Improved hcitool. New flexible cmd syntax, additional commands.
+       Human readable display of the device features.
+       LMP features to string translation support.
+       Additional HCI command and event defines.
+       Extended hci_filter API.
+
+ver 2.0-pre8:
+       Additional HCI ioctls and defines.
+       All strings and buffers are allocated dynamically.
+       ba2str, str2ba automatically swap bdaddress.
+       Additional hciconfig commands. Support for ACL and SCO MTU ioctls.
+       Support for Inventel and COM1 UART based devices.
+       Minor hcitool fixes.
+       Improved l2test. New L2CAP test modes.
+       Minor fixes and cleanup.
+
+ver 2.0-pre7:
+       Bluetooth libraries and header files is now a separate package.
+       New build environment uses automake and libtool.
+       Massive header files cleanup.
+       Bluetooth utilities is now a separate package.
+       New build environment uses automake.
+       Moved all config files and security data to /etc/bluetooth.
+       Various cleanups.
+
+ver 2.0-pre6:
+       API cleanup and additions.
+       Improved hcitool.
+       l2test minor output fixes.
+       hciattach opt to display list of supported devices.
+
+ver 2.0-pre4:
+       HCI filter enhancements.
+
+ver 2.0-pre3:
+       Cleanup.
+
+ver 2.0-pre2:
+       Additional HCI library functions.
+       Improved CSR baud rate initialization.
+       PCMCIA scripts fixes and enhancements.
+       Documentation update.
+
+ver 2.0-pre1:
+       New UART initialization utility.
+       Hot plugging support for UART based PCMCIA devices.
+       SCO testing utility.
+       New authentication utility (bluepin).
+       Minor fixes and improvements.
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..56b077d
--- /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 (file)
index 0000000..07dcd08
--- /dev/null
@@ -0,0 +1,422 @@
+
+AM_MAKEFLAGS = --no-print-directory
+
+lib_LTLIBRARIES =
+
+noinst_LTLIBRARIES =
+
+bin_PROGRAMS =
+
+sbin_PROGRAMS =
+
+noinst_PROGRAMS =
+
+dist_man_MANS =
+
+dist_noinst_MANS =
+
+CLEANFILES =
+
+EXTRA_DIST =
+
+includedir = @includedir@/bluetooth
+
+include_HEADERS =
+
+if CONFIGFILES
+dbusdir = $(sysconfdir)/dbus-1/system.d
+
+dbus_DATA = src/bluetooth.conf
+
+confdir = $(sysconfdir)/bluetooth
+
+conf_DATA =
+
+statedir = $(localstatedir)/lib/bluetooth
+
+state_DATA =
+endif
+
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES =
+
+
+lib_headers = lib/bluetooth.h lib/hci.h lib/hci_lib.h lib/mgmt.h \
+               lib/sco.h lib/l2cap.h lib/sdp.h lib/sdp_lib.h lib/uuid.h \
+               lib/rfcomm.h lib/bnep.h lib/cmtp.h lib/hidp.h
+local_headers = $(foreach file,$(lib_headers), lib/bluetooth/$(notdir $(file)))
+
+include_HEADERS += $(lib_headers)
+
+lib_LTLIBRARIES += lib/libbluetooth.la
+
+lib_libbluetooth_la_SOURCES = $(lib_headers) \
+                               lib/bluetooth.c lib/hci.c lib/sdp.c lib/uuid.c
+lib_libbluetooth_la_LDFLAGS = -version-info 14:0:11
+lib_libbluetooth_la_DEPENDENCIES = $(local_headers)
+
+CLEANFILES += $(local_headers)
+
+
+if SBC
+noinst_LTLIBRARIES += sbc/libsbc.la
+
+sbc_libsbc_la_SOURCES = sbc/sbc.h sbc/sbc.c sbc/sbc_math.h sbc/sbc_tables.h \
+                       sbc/sbc_primitives.h sbc/sbc_primitives.c \
+                       sbc/sbc_primitives_mmx.h sbc/sbc_primitives_mmx.c \
+                       sbc/sbc_primitives_iwmmxt.h sbc/sbc_primitives_iwmmxt.c \
+                       sbc/sbc_primitives_neon.h sbc/sbc_primitives_neon.c \
+                       sbc/sbc_primitives_armv6.h sbc/sbc_primitives_armv6.c
+
+sbc_libsbc_la_CFLAGS = -finline-functions -fgcse-after-reload \
+                                       -funswitch-loops -funroll-loops
+
+noinst_PROGRAMS += sbc/sbcinfo sbc/sbcdec sbc/sbcenc
+
+sbc_sbcdec_SOURCES = sbc/sbcdec.c sbc/formats.h
+sbc_sbcdec_LDADD = sbc/libsbc.la
+
+sbc_sbcenc_SOURCES = sbc/sbcenc.c sbc/formats.h
+sbc_sbcenc_LDADD = sbc/libsbc.la
+
+if SNDFILE
+noinst_PROGRAMS += sbc/sbctester
+
+sbc_sbctester_LDADD = @SNDFILE_LIBS@ -lm
+sbc_sbctest_CFLAGS = @SNDFILE_CFLAGS@
+endif
+endif
+
+attrib_sources = attrib/att.h attrib/att.c attrib/gatt.h attrib/gatt.c \
+               attrib/gattrib.h attrib/gattrib.c
+
+gdbus_sources = gdbus/gdbus.h gdbus/mainloop.c gdbus/watch.c \
+                                       gdbus/object.c gdbus/polkit.c
+
+btio_sources = btio/btio.h btio/btio.c
+
+builtin_modules =
+builtin_sources =
+builtin_nodist =
+mcap_sources =
+
+if MCAP
+mcap_sources += health/mcap_lib.h health/mcap_internal.h \
+               health/mcap.h health/mcap.c \
+               health/mcap_sync.c
+endif
+
+if PNATPLUGIN
+builtin_modules += pnat
+builtin_sources += plugins/pnat.c
+endif
+
+if ECHOPLUGIN
+builtin_modules += echo
+builtin_sources += plugins/echo.c
+endif
+
+if AUDIOPLUGIN
+builtin_modules += audio
+builtin_sources += audio/main.c \
+                       audio/manager.h audio/manager.c \
+                       audio/gateway.h audio/gateway.c \
+                       audio/headset.h audio/headset.c \
+                       audio/control.h audio/control.c \
+                       audio/avctp.h audio/avctp.c \
+                       audio/avrcp.h audio/avrcp.c \
+                       audio/device.h audio/device.c \
+                       audio/source.h audio/source.c \
+                       audio/sink.h audio/sink.c \
+                       audio/a2dp.h audio/a2dp.c \
+                       audio/avdtp.h audio/avdtp.c \
+                       audio/ipc.h audio/ipc.c \
+                       audio/unix.h audio/unix.c \
+                       audio/media.h audio/media.c \
+                       audio/transport.h audio/transport.c \
+                       audio/telephony.h audio/a2dp-codecs.h
+builtin_nodist += audio/telephony.c
+
+noinst_LIBRARIES = audio/libtelephony.a
+
+audio_libtelephony_a_SOURCES = audio/telephony.h audio/telephony-dummy.c \
+                               audio/telephony-maemo5.c audio/telephony-ofono.c \
+                               audio/telephony-maemo6.c audio/telephony-tizen.c
+
+$(audio_libtelephony_a_OBJECTS): $(local_headers)
+
+endif
+
+if SAPPLUGIN
+builtin_modules += sap
+builtin_sources += sap/main.c \
+                       sap/manager.h sap/manager.c \
+                       sap/server.h sap/server.c \
+                       sap/sap.h sap/sap-dummy.c
+endif
+
+if INPUTPLUGIN
+builtin_modules += input
+builtin_sources += input/main.c \
+                       input/manager.h input/manager.c \
+                       input/server.h input/server.c \
+                       input/device.h input/device.c \
+                       input/fakehid.c input/fakehid.h
+endif
+
+if SERIALPLUGIN
+builtin_modules += serial
+builtin_sources += serial/main.c \
+                       serial/manager.h serial/manager.c \
+                       serial/proxy.h serial/proxy.c \
+                       serial/port.h serial/port.c
+endif
+
+if NETWORKPLUGIN
+builtin_modules += network
+builtin_sources += network/main.c \
+                       network/manager.h network/manager.c \
+                       network/common.h network/common.c \
+                       network/server.h network/server.c \
+                       network/connection.h network/connection.c
+endif
+
+if SERVICEPLUGIN
+builtin_modules += service
+builtin_sources += plugins/service.c
+endif
+
+if ATTRIBPLUGIN
+
+if READLINE
+bin_PROGRAMS += attrib/gatttool
+
+attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \
+                               attrib/gattrib.c btio/btio.c \
+                               src/glib-helper.h src/glib-helper.c \
+                               attrib/gatttool.h attrib/interactive.c \
+                               attrib/utils.c
+attrib_gatttool_LDADD = lib/libbluetooth.la @GLIB_LIBS@ @READLINE_LIBS@
+endif
+
+builtin_modules += attrib
+builtin_sources += attrib/main.c \
+               attrib/manager.h attrib/manager.c \
+               attrib/client.h attrib/client.c \
+               attrib/example.h attrib/example.c
+endif
+
+if HEALTHPLUGIN
+builtin_modules += health
+builtin_sources += health/hdp_main.c health/hdp_types.h \
+                       health/hdp_manager.h health/hdp_manager.c \
+                       health/hdp.h health/hdp.c \
+                       health/hdp_util.h health/hdp_util.c
+endif
+
+builtin_modules += hciops mgmtops
+builtin_sources += plugins/hciops.c plugins/mgmtops.c
+
+if HAL
+builtin_modules += hal
+builtin_sources += plugins/hal.c
+else
+builtin_modules += formfactor
+builtin_sources += plugins/formfactor.c
+endif
+
+EXTRA_DIST += plugins/hal.c plugins/formfactor.c
+
+builtin_modules += storage
+builtin_sources += plugins/storage.c
+
+if MAEMO6PLUGIN
+builtin_modules += maemo6
+builtin_sources += plugins/maemo6.c
+endif
+
+sbin_PROGRAMS += src/bluetoothd
+
+src_bluetoothd_SOURCES = $(gdbus_sources) $(builtin_sources) \
+                       $(attrib_sources) $(btio_sources) \
+                       $(mcap_sources) src/bluetooth.ver \
+                       src/main.c src/log.h src/log.c \
+                       src/rfkill.c src/hcid.h src/sdpd.h \
+                       src/sdpd-server.c src/sdpd-request.c \
+                       src/sdpd-service.c src/sdpd-database.c \
+                       src/attrib-server.h src/attrib-server.c \
+                       src/sdp-xml.h src/sdp-xml.c \
+                       src/textfile.h src/textfile.c \
+                       src/glib-helper.h src/glib-helper.c \
+                       src/oui.h src/oui.c src/uinput.h src/ppoll.h \
+                       src/plugin.h src/plugin.c \
+                       src/storage.h src/storage.c \
+                       src/agent.h src/agent.c \
+                       src/error.h src/error.c \
+                       src/manager.h src/manager.c \
+                       src/adapter.h src/adapter.c \
+                       src/device.h src/device.c \
+                       src/dbus-common.c src/dbus-common.h \
+                       src/event.h src/event.c
+src_bluetoothd_LDADD = lib/libbluetooth.la @GLIB_LIBS@ @DBUS_LIBS@ \
+                                                       @CAPNG_LIBS@ -ldl -lrt
+src_bluetoothd_LDFLAGS = -Wl,--export-dynamic \
+                               -Wl,--version-script=$(srcdir)/src/bluetooth.ver
+
+src_bluetoothd_DEPENDENCIES = lib/libbluetooth.la
+
+builtin_files = src/builtin.h $(builtin_nodist)
+
+nodist_src_bluetoothd_SOURCES = $(builtin_files)
+
+CLEANFILES += $(builtin_files)
+
+man_MANS = src/bluetoothd.8
+
+if CONFIGFILES
+conf_DATA += src/main.conf
+endif
+
+EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
+                       src/main.conf network/network.conf \
+                       input/input.conf serial/serial.conf \
+                       audio/audio.conf audio/telephony-dummy.c \
+                       audio/telephony-maemo5.c audio/telephony-ofono.c \
+                       audio/telephony-maemo6.c audio/telephony-tizen.c
+
+
+if ALSA
+alsadir = $(libdir)/alsa-lib
+
+alsa_LTLIBRARIES = audio/libasound_module_pcm_bluetooth.la \
+                               audio/libasound_module_ctl_bluetooth.la
+
+audio_libasound_module_pcm_bluetooth_la_SOURCES = audio/pcm_bluetooth.c \
+                                       audio/rtp.h audio/ipc.h audio/ipc.c
+audio_libasound_module_pcm_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_pcm_.*
+audio_libasound_module_pcm_bluetooth_la_LIBADD = sbc/libsbc.la \
+                                               lib/libbluetooth.la @ALSA_LIBS@
+audio_libasound_module_pcm_bluetooth_la_CFLAGS = @ALSA_CFLAGS@
+
+audio_libasound_module_ctl_bluetooth_la_SOURCES = audio/ctl_bluetooth.c \
+                                       audio/rtp.h audio/ipc.h audio/ipc.c
+audio_libasound_module_ctl_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_ctl_.*
+audio_libasound_module_ctl_bluetooth_la_LIBADD = lib/libbluetooth.la @ALSA_LIBS@
+audio_libasound_module_ctl_bluetooth_la_CFLAGS = @ALSA_CFLAGS@
+
+if CONFIGFILES
+alsaconfdir = $(datadir)/alsa
+
+alsaconf_DATA = audio/bluetooth.conf
+endif
+endif
+
+if AUDIOPLUGIN
+if GSTREAMER
+gstreamerdir = $(libdir)/gstreamer-0.10
+
+gstreamer_LTLIBRARIES = audio/libgstbluetooth.la
+
+audio_libgstbluetooth_la_SOURCES = audio/gstbluetooth.c audio/gstpragma.h \
+                               audio/gstsbcenc.h audio/gstsbcenc.c \
+                               audio/gstsbcdec.h audio/gstsbcdec.c \
+                               audio/gstsbcparse.h audio/gstsbcparse.c \
+                               audio/gstavdtpsink.h audio/gstavdtpsink.c \
+                               audio/gsta2dpsink.h audio/gsta2dpsink.c \
+                               audio/gstsbcutil.h audio/gstsbcutil.c \
+                               audio/gstrtpsbcpay.h audio/gstrtpsbcpay.c \
+                               audio/rtp.h audio/ipc.h audio/ipc.c
+audio_libgstbluetooth_la_LDFLAGS = -module -avoid-version
+audio_libgstbluetooth_la_LIBADD = sbc/libsbc.la lib/libbluetooth.la \
+                               @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10
+$(audio_libgstbluetooth_la_OBJECTS): $(local_headers)
+audio_libgstbluetooth_la_CFLAGS = -fvisibility=hidden -fno-strict-aliasing \
+                                               $(AM_CFLAGS) @GSTREAMER_CFLAGS@
+endif
+endif
+
+EXTRA_DIST += audio/bluetooth.conf
+
+
+include Makefile.tools
+
+if UDEVRULES
+rulesdir = @UDEV_DATADIR@
+
+udev_files = scripts/bluetooth.rules
+
+if HID2HCI
+udev_files += scripts/bluetooth-hid2hci.rules
+endif
+
+if PCMCIA
+udev_files += scripts/bluetooth-serial.rules
+endif
+
+rules_DATA = $(foreach file,$(udev_files), scripts/97-$(notdir $(file)))
+endif
+
+CLEANFILES += $(rules_DATA)
+
+EXTRA_DIST += scripts/bluetooth.rules \
+               scripts/bluetooth-hid2hci.rules scripts/bluetooth-serial.rules
+
+if PCMCIA
+udevdir = $(libexecdir)/udev
+
+dist_udev_SCRIPTS = scripts/bluetooth_serial
+endif
+
+EXTRA_DIST += doc/manager-api.txt \
+               doc/adapter-api.txt doc/device-api.txt \
+               doc/service-api.txt doc/agent-api.txt doc/attribute-api.txt \
+               doc/serial-api.txt doc/network-api.txt \
+               doc/input-api.txt doc/audio-api.txt doc/control-api.txt \
+               doc/hfp-api.txt doc/health-api.txt doc/sap-api.txt \
+               doc/media-api.txt doc/assigned-numbers.txt
+
+AM_YFLAGS = -d
+
+AM_CFLAGS = @DBUS_CFLAGS@ @GLIB_CFLAGS@ @CAPNG_CFLAGS@ \
+               -DBLUETOOTH_PLUGIN_BUILTIN -DPLUGINDIR=\""$(plugindir)"\"
+
+INCLUDES = -I$(builddir)/lib -I$(builddir)/src -I$(srcdir)/src \
+                       -I$(srcdir)/audio -I$(srcdir)/sbc -I$(srcdir)/gdbus \
+                       -I$(srcdir)/attrib -I$(srcdir)/btio
+
+if MCAP
+INCLUDES += -I$(builddir)/health
+endif
+
+pkgconfigdir = $(libdir)/pkgconfig
+
+pkgconfig_DATA = bluez.pc
+
+DISTCHECK_CONFIGURE_FLAGS = --disable-udevrules --enable-attrib
+
+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
+
+src/plugin.$(OBJEXT): src/builtin.h
+
+src/builtin.h: src/genbuiltin $(builtin_sources)
+       $(AM_V_GEN)$(srcdir)/src/genbuiltin $(builtin_modules) > $@
+
+audio/telephony.c: audio/@TELEPHONY_DRIVER@
+       $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@
+
+scripts/%.rules:
+       $(AM_V_GEN)cp $(subst 97-,,$@) $@
+
+$(lib_libbluetooth_la_OBJECTS): $(local_headers)
+
+lib/bluetooth/%.h: lib/%.h
+       $(AM_V_at)$(MKDIR_P) lib/bluetooth
+       $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@
+
+clean-local:
+       $(RM) -r lib/bluetooth
diff --git a/Makefile.tools b/Makefile.tools
new file mode 100644 (file)
index 0000000..1a1f5a1
--- /dev/null
@@ -0,0 +1,238 @@
+
+if TOOLS
+if CONFIGFILES
+conf_DATA += tools/rfcomm.conf
+endif
+
+bin_PROGRAMS += tools/rfcomm tools/l2ping \
+                               tools/hcitool tools/sdptool tools/ciptool
+
+sbin_PROGRAMS += tools/hciattach tools/hciconfig
+
+noinst_PROGRAMS += tools/avinfo tools/ppporc \
+                               tools/hcieventmask tools/hcisecfilter
+
+tools/kword.c: tools/parser.h
+
+tools_rfcomm_SOURCES = tools/rfcomm.c tools/parser.y tools/lexer.l \
+                                       tools/kword.h tools/kword.c
+EXTRA_tools_rfcomm_SOURCES = tools/parser.h tools/parser.c \
+                                                       tools/lexer.c
+tools_rfcomm_LDADD = lib/libbluetooth.la
+
+tools_l2ping_LDADD = lib/libbluetooth.la
+
+tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \
+                                               tools/hciattach_st.c \
+                                               tools/hciattach_ti.c \
+                                               tools/hciattach_tialt.c \
+                                               tools/hciattach_ath3k.c \
+                                               tools/hciattach_qualcomm.c
+tools_hciattach_LDADD = lib/libbluetooth.la
+
+tools_hciconfig_SOURCES = tools/hciconfig.c tools/csr.h tools/csr.c \
+                                               src/textfile.h src/textfile.c
+tools_hciconfig_LDADD = lib/libbluetooth.la
+
+tools_hcitool_SOURCES = tools/hcitool.c src/oui.h src/oui.c \
+                                               src/textfile.h src/textfile.c
+tools_hcitool_LDADD = lib/libbluetooth.la
+
+tools_sdptool_SOURCES = tools/sdptool.c src/sdp-xml.h src/sdp-xml.c
+tools_sdptool_LDADD = lib/libbluetooth.la
+
+tools_ciptool_LDADD = lib/libbluetooth.la
+
+tools_avinfo_LDADD = lib/libbluetooth.la
+
+tools_ppporc_LDADD = lib/libbluetooth.la
+
+tools_hcieventmask_LDADD = lib/libbluetooth.la
+
+dist_man_MANS += tools/rfcomm.1 tools/l2ping.8 \
+                       tools/hciattach.8 tools/hciconfig.8 \
+                       tools/hcitool.1 tools/sdptool.1 tools/ciptool.1
+else
+EXTRA_DIST += tools/rfcomm.1 tools/l2ping.8 \
+                       tools/hciattach.8 tools/hciconfig.8 \
+                       tools/hcitool.1 tools/sdptool.1 tools/ciptool.1
+endif
+
+CLEANFILES += tools/lexer.c tools/parser.c tools/parser.h
+
+EXTRA_DIST += tools/rfcomm.conf
+
+if TRACER
+sbin_PROGRAMS += tracer/hcitrace
+
+tracer_hcitrace_SOURCES = tracer/main.c
+tracer_hcitrace_LDADD = lib/libbluetooth.la \
+                               @GLIB_LIBS@ @DBUS_LIBS@ @CAPNG_LIBS@
+tracer_hcitrace_DEPENDENCIES = lib/libbluetooth.la
+endif
+
+if BCCMD
+sbin_PROGRAMS += tools/bccmd
+
+tools_bccmd_SOURCES = tools/bccmd.c tools/csr.h tools/csr.c \
+                       tools/csr_hci.c tools/csr_h4.c tools/csr_3wire.c \
+                       tools/csr_bcsp.c tools/ubcsp.h tools/ubcsp.c
+tools_bccmd_LDADD = lib/libbluetooth.la
+
+if USB
+tools_bccmd_SOURCES += tools/csr_usb.c
+tools_bccmd_LDADD += @USB_LIBS@
+endif
+
+dist_man_MANS += tools/bccmd.8
+else
+EXTRA_DIST += tools/bccmd.8
+endif
+
+if HID2HCI
+sbin_PROGRAMS += tools/hid2hci
+
+tools_hid2hci_LDADD = @USB_LIBS@
+
+dist_man_MANS += tools/hid2hci.8
+else
+EXTRA_DIST += tools/hid2hci.8
+endif
+
+if DFUTOOL
+bin_PROGRAMS += tools/dfutool
+
+tools_dfutool_SOURCES = tools/dfutool.c tools/dfu.h tools/dfu.c
+tools_dfutool_LDADD = @USB_LIBS@
+
+dist_man_MANS += tools/dfutool.1
+else
+EXTRA_DIST += tools/dfutool.1
+endif
+
+
+if USB
+noinst_PROGRAMS += tools/dfubabel tools/avctrl
+
+tools_dfubabel_LDADD = @USB_LIBS@
+
+tools_avctrl_LDADD = @USB_LIBS@
+endif
+
+EXTRA_DIST += tools/dfubabel.1 tools/avctrl.8
+
+
+if CUPS
+cupsdir = $(libdir)/cups/backend
+
+cups_PROGRAMS = cups/bluetooth
+
+cups_bluetooth_SOURCES = $(gdbus_sources) cups/main.c cups/cups.h \
+                                       cups/sdp.c cups/spp.c cups/hcrp.c
+
+cups_bluetooth_LDADD = @GLIB_LIBS@ @DBUS_LIBS@ lib/libbluetooth.la
+endif
+
+
+if TEST
+sbin_PROGRAMS += test/hciemu
+
+bin_PROGRAMS += test/l2test test/rctest
+
+noinst_PROGRAMS += test/gaptest test/sdptest test/scotest \
+                       test/attest test/hstest test/avtest test/ipctest \
+                                       test/lmptest test/bdaddr test/agent \
+                                       test/btiotest test/test-textfile \
+                                       test/uuidtest
+
+test_hciemu_LDADD = @GLIB_LIBS@ lib/libbluetooth.la
+
+test_l2test_LDADD = lib/libbluetooth.la
+
+test_rctest_LDADD = lib/libbluetooth.la
+
+test_gaptest_LDADD = @DBUS_LIBS@
+
+test_sdptest_LDADD = lib/libbluetooth.la
+
+test_scotest_LDADD = lib/libbluetooth.la
+
+test_attest_LDADD = lib/libbluetooth.la
+
+test_hstest_LDADD = lib/libbluetooth.la
+
+test_avtest_LDADD = lib/libbluetooth.la
+
+test_lmptest_LDADD = lib/libbluetooth.la
+
+test_ipctest_SOURCES = test/ipctest.c audio/ipc.h audio/ipc.c
+test_ipctest_LDADD= @GLIB_LIBS@ sbc/libsbc.la
+
+test_bdaddr_SOURCES = test/bdaddr.c src/oui.h src/oui.c
+test_bdaddr_LDADD = lib/libbluetooth.la
+
+test_agent_LDADD = @DBUS_LIBS@
+
+test_btiotest_SOURCES = test/btiotest.c btio/btio.h btio/btio.c
+test_btiotest_LDADD = @GLIB_LIBS@ lib/libbluetooth.la
+
+test_uuidtest_SOURCES = test/uuidtest.c
+test_uuidtest_LDADD = lib/libbluetooth.la
+
+test_test_textfile_SOURCES = test/test-textfile.c src/textfile.h src/textfile.c
+
+dist_man_MANS += test/rctest.1 test/hciemu.1
+
+EXTRA_DIST += test/bdaddr.8
+else
+EXTRA_DIST += test/rctest.1 test/hciemu.1 test/bdaddr.8
+endif
+
+EXTRA_DIST += test/apitest test/hsplay test/hsmicro test/dbusdef.py \
+               test/monitor-bluetooth test/list-devices test/test-discovery \
+               test/test-manager test/test-adapter test/test-device \
+               test/test-service test/test-serial test/test-telephony \
+               test/test-network test/simple-agent test/simple-service \
+               test/simple-endpoint test/test-audio test/test-input \
+               test/test-attrib test/service-record.dtd test/service-did.xml \
+               test/service-spp.xml test/service-opp.xml test/service-ftp.xml
+
+
+if HIDD
+bin_PROGRAMS += compat/hidd
+
+compat_hidd_SOURCES = compat/hidd.c compat/hidd.h src/uinput.h \
+                               compat/sdp.h compat/sdp.c compat/fakehid.c \
+                                               src/textfile.h src/textfile.c
+compat_hidd_LDADD = -lm lib/libbluetooth.la
+
+dist_man_MANS += compat/hidd.1
+else
+EXTRA_DIST += compat/hidd.1
+endif
+
+if PAND
+bin_PROGRAMS += compat/pand
+
+compat_pand_SOURCES = compat/pand.c compat/pand.h \
+                               compat/bnep.c compat/sdp.h compat/sdp.c \
+                                               src/textfile.h src/textfile.c
+compat_pand_LDADD = lib/libbluetooth.la
+
+dist_man_MANS += compat/pand.1
+else
+EXTRA_DIST += compat/pand.1
+endif
+
+if DUND
+bin_PROGRAMS += compat/dund
+
+compat_dund_SOURCES = compat/dund.c compat/dund.h compat/lib.h \
+                       compat/sdp.h compat/sdp.c compat/dun.c compat/msdun.c \
+                                               src/textfile.h src/textfile.c
+compat_dund_LDADD = lib/libbluetooth.la
+
+dist_man_MANS += compat/dund.1
+else
+EXTRA_DIST += compat/dund.1
+endif
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..77778e0
--- /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 <maxk@qualcomm.com>
+Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+
+
+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/TODO b/TODO
new file mode 100644 (file)
index 0000000..1120ad9
--- /dev/null
+++ b/TODO
@@ -0,0 +1,254 @@
+Background
+==========
+
+- Priority scale: High, Medium and Low
+
+- Complexity scale: C1, C2, C4 and C8.  The complexity scale is exponential,
+  with complexity 1 being the lowest complexity.  Complexity is a function
+  of both task 'complexity' and task 'scope'.
+
+  The general rule of thumb is that a complexity 1 task should take 1-2 weeks
+  for a person very familiar with BlueZ codebase.  Higher complexity tasks
+  require more time and have higher uncertainty.
+
+  Higher complexity tasks should be refined into several lower complexity tasks
+  once the task is better understood.
+
+General
+==========
+
+- UUID128 handling: Create new functions to handle UUIDs on host order.
+  Functions should start with prefix "bt_uuid". In the first phase, attribute
+  server/client and gatttool code should be changed to use these new functions.
+  The idea is to keep the consistency for UUID-16, UUID-32 and UUID-128. SDP
+  functions store UUID-16 and UUID-32 on host order, however UUID-128 is stored
+  on network order/big endian. Attribute Protocol uses little endian, while
+  SDP uses big endian. The idea is always store the UUID values on host order
+  and use utility functions to convert to the proper byte order depending on
+  the protocol: ATT or SDP.
+
+  Priority: high
+  Complexity: C1
+
+- Rename glib-helper file to a more convenient name. The ideia is try to keep
+  only sdp helpers functions. bt_* prefix shall be also changed.
+
+  Priority: Low
+  Complexity: C1
+
+Low Energy
+==========
+
+- Advertising management. Adapter interface needs to be changed to manage
+  connection modes, adapter type and advertising policy. See Volume 3,
+  Part C, section 9.3. If Attribute Server is enabled the LE capable
+  adapter shall to start advertising. Further investigation is necessary
+  to define which connectable mode needs to be supported: Non-connectable,
+  directed connectable and undirected connectable. Basically, two connectable
+  scenarios shall be addressed:
+  1. GATT client is disconnected, but intends to become a Peripheral to
+     receive indications/notifications.
+  2. GATT server intends to accept connections.
+
+  Priority: Medium
+  Complexity: C2
+
+- Define Auto Connection Establishment Procedure. Some profiles such as
+  Proximity requires an active link to identify path lost situation. It is
+  necessary to define how to manage connections, it seems that White List
+  is appropriated to address auto connections, however is not clear if the
+  this procedure shall be a profile specific detail or if the remote device
+  object can expose a property "WhiteList", maybe "Trusted" property can be
+  also used for this purpose. Another alternative is to define a method to
+  allow application to request/register the wanted scanning/connection
+  parameters. Before start this task, a RFC/PATCH shall be sent to the ML.
+  See Volume 3, Part C, section 9.3.5 for more information.
+
+  Priority: Medium
+  Complexity: C2
+
+- Implement a tool(or extend hciconfig) to setup the advertising parameters
+  and data. Extend hciconfig passing extra arguments when enabling the
+  advertises is not the right approach, it will be almost impossible to
+  address all arguments needed in an acceptable way. For testing, we need
+  a tool to change easily the AD Flags, the UUIDs and other data that can be
+  exported through the advertising data field. Suggestions: 1) extend hciconfig
+  passing a config file when enabling advertises; 2) write a ncurses based tool
+
+  Priority: Medium
+  Complexity: C2
+
+- Add new property in the DeviceFound signal to report the device type:
+  BR/EDR, single mode or dual-mode.
+
+  Priority: Medium
+  Complexity: C1
+
+- Privacy: When privacy is enabled in the adapter, LE scanning/connection
+  should use a private address. StartDiscovery method shall be changed and
+  new adapter property shall be added.
+
+  Priority: Medium
+  Complexity: C1
+
+- Static random address setup and storage. Once this address is written
+  in the a given remote, the address can not be changed anymore.
+
+  Priority: Low
+  Complexity: C1
+
+- Reconnection address: Reconnection address is a non resolvable private
+  address that the central writes in the peripheral. BlueZ will support
+  multiple profiles, it is not clear how it needs to be implemented.
+  Further discussion is necessary.
+
+  Priority: Low
+  Complexity: C2
+
+- Device Name Characteristic is a GAP characteristic for Low Energy. This
+  characteristic shall be integrated/used in the discovery procedure. The
+  ideia is to report the value of this characteristic using DeviceFound signals.
+  Discussion with the community is needed before to start this task. Other GAP
+  characteristics for LE needs to follow a similar approach. It is not clear
+  if all GAP characteristics can be exposed using properties instead of a primary
+  service characteristics.
+  See Volume 3, Part C, section 12.1 for more information.
+
+  Priority: Low
+  Complexity: C2
+
+ATT/GATT
+========
+
+- For BR/EDR, primary services can be registered based on the information
+  extracted from the service records. UUIDs, start and end handles information
+  are available in the record, Discover All Primary Services procedure is not
+  necessary. If a GATT service doesn't export a service record means that
+  it should not be used over BR/EDR. Don't start this task before to move the
+  attribute client code to the bluetoothd core.
+
+  Priority: Medium
+  Complexity: C1
+
+- ATT/GATT parsing to hcidump. Partially implemented, missing to fix
+  multiple advertises in the same event and RSSI.
+
+  Priority: Medium
+  Complexity: C2
+
+- GATT server: fix MTU exchange
+
+  Priority: Medium
+  Complexity: C2
+
+- Implement ATT PDU validation. Malformed PDUs can cause division by zero
+  when decoding PDUs. A proper error PDU should be returned for this case.
+  See decoding function in att.c file.
+
+  Priority: Medium
+  Complexity: C1
+
+- Refactor read_by_group() and read_by_type() in src/attrib-server.c
+  (they've grown simply too big). First step could be to move out the
+  long for-loops to new functions called e.g. get_groups() and get_types().
+
+  Priority: Low
+  Complexity: C1
+
+- Agent for characteristics: Agent interface should be extended to support
+  authorization per characteristic if the remote is not in the trusted list.
+
+  Priority: Low
+  Complexity: C1
+
+- gatttool should have the ability to wait for req responses before
+  quitting (some servers require a small sleep even with cmd's). Maybe a
+  --delay-exit or --timeout command line switch.
+
+  Priority: Low
+  Complexity: C1
+
+- Refactoring of gatt.c functions. Currently, the callbacks of the services
+  and characteristics discovery functions return the ATT PDU and the caller
+  needs to call again the same function to fetch the remaining data when
+  necessary. Investigate if all results can be returned in the callback
+  result to avoid repeated code. Before change the code, please analyze
+  if this change will not break the GATT/ATT qualification tests. Maybe
+  an interactive fetch/query is necessary to pass the tests.
+
+  Priority: Low
+  Complexity: C1
+
+- Client needs to export a property in the Device Characteristic hierarchy
+  to manage characteristic value changes reports in the remote device.
+  Currently, Client Characteristic Configuration attribute is not exposed
+  as an object. The user needs to use gatttool to change the value of the
+  this attribute to receive notification/indications. Export this attribute
+  as a property is a proposal that needs further discussion.
+
+  Priority: Low
+  Complexity: C1
+
+- Attribute server should process queued GATT/ATT commands if the
+  client disconnects. The client can simply send a command and quit,
+  without wait for a response(ex: Write Command). For this scenario
+  that the client disconnects the link quickly the queued received
+  command is ignored.
+
+  Priority: Low
+  Complecity: C1
+
+- Add sdp discovery support to gattool with BR (--sdp, default is 0x1f)
+
+  Priority: Low
+  Complexity: C1
+
+- Implement Server characteristic Configuration support in the attribute
+  server to manage characteristic value broadcasting. There is a single
+  instance of the Server Characteristic Configuration for all clients.
+  See Volume 3, Part G, section 3.3.3.4 for more information.
+
+  Priority: Low
+  Complexity: C1
+
+- Long write is not implemented. Attribute server, client and command line
+  tool shall be changed to support this feature.
+
+  Priority: Low
+  Complexity: C2
+
+- Define attribute server API. External applications needs to register,
+  change attributes and to be notified about changes. Example: Proximity,
+  Time and Alert Profiles. "Local Service hierarchy" in the attribute-api
+  needs to be proposed and a RFC shall be sent to the ML.
+
+  Priority: Low
+  Complexity: C2
+
+Management Interface
+====================
+
+- Device discovery support (both for BR/EDR & LE)
+
+  Priority: High
+  Complexity: C3
+
+- EIR generation support
+
+  Priority: High
+  Complexity: C2
+
+- Blacklist support
+
+  Priority: Medium
+  Complexity: C1
+
+- mgmt_set_fast_connectable
+
+  Priority: Medium
+  Complexity: C1
+
+- Whitelist support (initially only for LE)
+
+  Priority: Medium
+  Complexity: C2
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644 (file)
index 0000000..5c884d9
--- /dev/null
@@ -0,0 +1,395 @@
+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="$CFLAGS -Werror -Wextra"
+               CFLAGS="$CFLAGS -Wno-unused-parameter"
+               CFLAGS="$CFLAGS -Wno-missing-field-initializers"
+               CFLAGS="$CFLAGS -Wdeclaration-after-statement"
+               CFLAGS="$CFLAGS -Wmissing-declarations"
+               CFLAGS="$CFLAGS -Wredundant-decls"
+               CFLAGS="$CFLAGS -Wcast-align"
+               CFLAGS="$CFLAGS -DG_DISABLE_DEPRECATED"
+       fi
+])
+
+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_CHECK_LIB(dbus-1, dbus_connection_can_send_type, dummy=yes,
+               AC_DEFINE(NEED_DBUS_CONNECTION_CAN_SEND_TYPE, 1,
+                       [Define to 1 if you need the dbus_connection_can_send_type() function.]
+))
+       AC_SUBST(DBUS_CFLAGS)
+       AC_SUBST(DBUS_LIBS)
+])
+
+AC_DEFUN([AC_PATH_GLIB], [
+       PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16, dummy=yes,
+                               AC_MSG_ERROR(GLib library version 2.16 or later is required))
+       AC_CHECK_LIB(glib-2.0, g_slist_free_full, dummy=yes,
+               AC_DEFINE(NEED_G_SLIST_FREE_FULL, 1,
+                       [Define to 1 if you need g_slist_free_full() function.]))
+       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_SNDFILE], [
+       PKG_CHECK_MODULES(SNDFILE, sndfile, sndfile_found=yes, sndfile_found=no)
+       AC_SUBST(SNDFILE_CFLAGS)
+       AC_SUBST(SNDFILE_LIBS)
+])
+
+AC_DEFUN([AC_PATH_READLINE], [
+       AC_CHECK_HEADER(readline/readline.h,
+               AC_CHECK_LIB(readline, main,
+                       [ readline_found=yes
+                       AC_SUBST(READLINE_LIBS, "-lreadline")
+                       ], readline_found=no),
+               [])
+])
+
+AC_DEFUN([AC_PATH_OUI], [
+       AC_ARG_WITH(ouifile,
+                   AS_HELP_STRING([--with-ouifile=PATH],[Path to the oui.txt file @<:@auto@:>@]),
+                   [ac_with_ouifile=$withval],
+                   [ac_with_ouifile="/var/lib/misc/oui.txt"])
+       AC_DEFINE_UNQUOTED(OUIFILE, ["$ac_with_ouifile"], [Define the OUI file path])
+])
+
+AC_DEFUN([AC_ARG_BLUEZ], [
+       debug_enable=no
+       optimization_enable=yes
+       fortify_enable=yes
+       pie_enable=yes
+       sndfile_enable=${sndfile_found}
+       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
+       sap_enable=no
+       service_enable=yes
+       health_enable=yes
+       pnat_enable=no
+       attrib_enable=no
+       tracer_enable=no
+       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
+       udevrules_enable=yes
+       configfiles_enable=yes
+       telephony_driver=dummy
+       maemo6_enable=no
+
+       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(sap, AC_HELP_STRING([--enable-sap], [enable sap plugin]), [
+               sap_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(health, AC_HELP_STRING([--enable-health], [enable health plugin]), [
+               health_enable=${enableval}
+       ])
+
+       AC_ARG_ENABLE(pnat, AC_HELP_STRING([--enable-pnat], [enable pnat plugin]), [
+               pnat_enable=${enableval}
+       ])
+
+       AC_ARG_ENABLE(attrib, AC_HELP_STRING([--enable-attrib], [enable attrib plugin]), [
+               attrib_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(tracer, AC_HELP_STRING([--enable-tracer], [install Tracing daemon]), [
+               tracer_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(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])
+
+       AC_ARG_ENABLE(maemo6, AC_HELP_STRING([--enable-maemo6], [compile with maemo6 plugin]), [
+               maemo6_enable=${enableval}
+       ])
+
+       AC_ARG_ENABLE(hal, AC_HELP_STRING([--enable-hal], [Use HAL to determine adapter class]), [
+               hal_enable=${enableval}
+       ])
+
+       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
+
+       AM_CONDITIONAL(SNDFILE, test "${sndfile_enable}" = "yes" && test "${sndfile_found}" = "yes")
+       AM_CONDITIONAL(USB, test "${usb_enable}" = "yes" && test "${usb_found}" = "yes")
+       AM_CONDITIONAL(SBC, test "${alsa_enable}" = "yes" || test "${gstreamer_enable}" = "yes" ||
+                                                                       test "${test_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(SAPPLUGIN, test "${sap_enable}" = "yes")
+       AM_CONDITIONAL(SERVICEPLUGIN, test "${service_enable}" = "yes")
+       AM_CONDITIONAL(HEALTHPLUGIN, test "${health_enable}" = "yes")
+       AM_CONDITIONAL(MCAP, test "${health_enable}" = "yes")
+       AM_CONDITIONAL(HAL, test "${hal_enable}" = "yes")
+       AM_CONDITIONAL(READLINE, test "${readline_found}" = "yes")
+       AM_CONDITIONAL(ATTRIBPLUGIN, test "${attrib_enable}" = "yes")
+       AM_CONDITIONAL(ECHOPLUGIN, test "no" = "yes")
+       AM_CONDITIONAL(PNATPLUGIN, test "${pnat_enable}" = "yes")
+       AM_CONDITIONAL(TRACER, test "${tracer_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(UDEVRULES, test "${udevrules_enable}" = "yes")
+       AM_CONDITIONAL(CONFIGFILES, test "${configfiles_enable}" = "yes")
+       AM_CONDITIONAL(MAEMO6PLUGIN, test "${maemo6_enable}" = "yes")
+])
diff --git a/attrib/att.c b/attrib/att.c
new file mode 100644 (file)
index 0000000..08000e0
--- /dev/null
@@ -0,0 +1,968 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+#include <glib.h>
+
+#include "att.h"
+
+const char *att_ecode2str(uint8_t status)
+{
+       switch (status)  {
+       case ATT_ECODE_INVALID_HANDLE:
+               return "Invalid handle";
+       case ATT_ECODE_READ_NOT_PERM:
+               return "Atribute can't be read";
+       case ATT_ECODE_WRITE_NOT_PERM:
+               return "Attribute can't be written";
+       case ATT_ECODE_INVALID_PDU:
+               return "Attribute PDU was invalid";
+       case ATT_ECODE_INSUFF_AUTHEN:
+               return "Attribute requires authentication before read/write";
+       case ATT_ECODE_REQ_NOT_SUPP:
+               return "Server doesn't support the request received";
+       case ATT_ECODE_INVALID_OFFSET:
+               return "Offset past the end of the attribute";
+       case ATT_ECODE_INSUFF_AUTHO:
+               return "Attribute requires authorization before read/write";
+       case ATT_ECODE_PREP_QUEUE_FULL:
+               return "Too many prepare writes have been queued";
+       case ATT_ECODE_ATTR_NOT_FOUND:
+               return "No attribute found within the given range";
+       case ATT_ECODE_ATTR_NOT_LONG:
+               return "Attribute can't be read/written using Read Blob Req";
+       case ATT_ECODE_INSUFF_ENCR_KEY_SIZE:
+               return "Encryption Key Size is insufficient";
+       case ATT_ECODE_INVAL_ATTR_VALUE_LEN:
+               return "Attribute value length is invalid";
+       case ATT_ECODE_UNLIKELY:
+               return "Request attribute has encountered an unlikely error";
+       case ATT_ECODE_INSUFF_ENC:
+               return "Encryption required before read/write";
+       case ATT_ECODE_UNSUPP_GRP_TYPE:
+               return "Attribute type is not a supported grouping attribute";
+       case ATT_ECODE_INSUFF_RESOURCES:
+               return "Insufficient Resources to complete the request";
+       case ATT_ECODE_IO:
+               return "Internal application error: I/O";
+       default:
+               return "Unexpected error code";
+       }
+}
+
+void att_data_list_free(struct att_data_list *list)
+{
+       if (list == NULL)
+               return;
+
+       if (list->data) {
+               int i;
+               for (i = 0; i < list->num; i++)
+                       g_free(list->data[i]);
+       }
+
+       g_free(list->data);
+       g_free(list);
+}
+
+struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len)
+{
+       struct att_data_list *list;
+       int i;
+
+       list = g_new0(struct att_data_list, 1);
+       list->len = len;
+       list->num = num;
+
+       list->data = g_malloc0(sizeof(uint8_t *) * num);
+
+       for (i = 0; i < num; i++)
+               list->data[i] = g_malloc0(sizeof(uint8_t) * len);
+
+       return list;
+}
+
+uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+                                                       uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+       uint16_t length;
+
+       if (!uuid)
+               return 0;
+
+       if (uuid->type == BT_UUID16)
+               length = 2;
+       else if (uuid->type == BT_UUID128)
+               length = 16;
+       else
+               return 0;
+
+       if (len < min_len + length)
+               return 0;
+
+       pdu[0] = ATT_OP_READ_BY_GROUP_REQ;
+       att_put_u16(start, &pdu[1]);
+       att_put_u16(end, &pdu[3]);
+
+       att_put_uuid(*uuid, &pdu[5]);
+
+       return min_len + length;
+}
+
+uint16_t dec_read_by_grp_req(const uint8_t *pdu, int len, uint16_t *start,
+                                               uint16_t *end, bt_uuid_t *uuid)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (start == NULL || end == NULL || uuid == NULL)
+               return 0;
+
+       if (pdu[0] != ATT_OP_READ_BY_GROUP_REQ)
+               return 0;
+
+       if (len < min_len + 2)
+               return 0;
+
+       *start = att_get_u16(&pdu[1]);
+       *end = att_get_u16(&pdu[3]);
+       if (len == min_len + 2)
+               *uuid = att_get_uuid16(&pdu[5]);
+       else
+               *uuid = att_get_uuid128(&pdu[5]);
+
+       return len;
+}
+
+uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu,
+                                                               int len)
+{
+       int i;
+       uint16_t w;
+       uint8_t *ptr;
+
+       if (list == NULL)
+               return 0;
+
+       if (len < list->len + 2)
+               return 0;
+
+       pdu[0] = ATT_OP_READ_BY_GROUP_RESP;
+       pdu[1] = list->len;
+
+       ptr = &pdu[2];
+
+       for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) {
+               memcpy(ptr, list->data[i], list->len);
+               ptr += list->len;
+               w += list->len;
+       }
+
+       return w;
+}
+
+struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, int len)
+{
+       struct att_data_list *list;
+       const uint8_t *ptr;
+       uint16_t elen, num;
+       int i;
+
+       if (pdu[0] != ATT_OP_READ_BY_GROUP_RESP)
+               return NULL;
+
+       elen = pdu[1];
+       num = (len - 2) / elen;
+       list = att_data_list_alloc(num, elen);
+
+       ptr = &pdu[2];
+
+       for (i = 0; i < num; i++) {
+               memcpy(list->data[i], ptr, list->len);
+               ptr += list->len;
+       }
+
+       return list;
+}
+
+uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+                       const uint8_t *value, int vlen, uint8_t *pdu, int len)
+{
+       uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end) +
+                                                       sizeof(uint16_t);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (!uuid)
+               return 0;
+
+       if (uuid->type != BT_UUID16)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (vlen > len - min_len)
+               vlen = len - min_len;
+
+       pdu[0] = ATT_OP_FIND_BY_TYPE_REQ;
+       att_put_u16(start, &pdu[1]);
+       att_put_u16(end, &pdu[3]);
+       att_put_uuid16(*uuid, &pdu[5]);
+
+       if (vlen > 0) {
+               memcpy(&pdu[7], value, vlen);
+               return min_len + vlen;
+       }
+
+       return min_len;
+}
+
+uint16_t dec_find_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+               uint16_t *end, bt_uuid_t *uuid, uint8_t *value, int *vlen)
+{
+       int valuelen;
+       uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) +
+                                               sizeof(*end) + sizeof(uint16_t);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (pdu[0] != ATT_OP_FIND_BY_TYPE_REQ)
+               return 0;
+
+       /* First requested handle number */
+       if (start)
+               *start = att_get_u16(&pdu[1]);
+
+       /* Last requested handle number */
+       if (end)
+               *end = att_get_u16(&pdu[3]);
+
+       /* Always UUID16 */
+       if (uuid)
+               *uuid = att_get_uuid16(&pdu[5]);
+
+       valuelen = len - min_len;
+
+       /* Attribute value to find */
+       if (valuelen > 0 && value)
+               memcpy(value, pdu + min_len, valuelen);
+
+       if (vlen)
+               *vlen = valuelen;
+
+       return len;
+}
+
+uint16_t enc_find_by_type_resp(GSList *matches, uint8_t *pdu, int len)
+{
+       GSList *l;
+       uint16_t offset;
+
+       if (pdu == NULL || len < 5)
+               return 0;
+
+       pdu[0] = ATT_OP_FIND_BY_TYPE_RESP;
+
+       for (l = matches, offset = 1; l && len >= (offset + 4);
+                                       l = l->next, offset += 4) {
+               struct att_range *range = l->data;
+
+               att_put_u16(range->start, &pdu[offset]);
+               att_put_u16(range->end, &pdu[offset + 2]);
+       }
+
+       return offset;
+}
+
+GSList *dec_find_by_type_resp(const uint8_t *pdu, int len)
+{
+       struct att_range *range;
+       GSList *matches;
+       int offset;
+
+       if (pdu == NULL || len < 5)
+               return NULL;
+
+       if (pdu[0] != ATT_OP_FIND_BY_TYPE_RESP)
+               return NULL;
+
+       for (offset = 1, matches = NULL; len >= (offset + 4); offset += 4) {
+               range = g_new0(struct att_range, 1);
+               range->start = att_get_u16(&pdu[offset]);
+               range->end = att_get_u16(&pdu[offset + 2]);
+
+               matches = g_slist_append(matches, range);
+       }
+
+       return matches;
+}
+
+uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+                                                       uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+       uint16_t length;
+
+       if (!uuid)
+               return 0;
+
+       if (uuid->type == BT_UUID16)
+               length = 2;
+       else if (uuid->type == BT_UUID128)
+               length = 16;
+       else
+               return 0;
+
+       if (len < min_len + length)
+               return 0;
+
+       pdu[0] = ATT_OP_READ_BY_TYPE_REQ;
+       att_put_u16(start, &pdu[1]);
+       att_put_u16(end, &pdu[3]);
+
+       att_put_uuid(*uuid, &pdu[5]);
+
+       return min_len + length;
+}
+
+uint16_t dec_read_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+                                               uint16_t *end, bt_uuid_t *uuid)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (start == NULL || end == NULL || uuid == NULL)
+               return 0;
+
+       if (len < min_len + 2)
+               return 0;
+
+       if (pdu[0] != ATT_OP_READ_BY_TYPE_REQ)
+               return 0;
+
+       *start = att_get_u16(&pdu[1]);
+       *end = att_get_u16(&pdu[3]);
+
+       if (len == min_len + 2)
+               *uuid = att_get_uuid16(&pdu[5]);
+       else
+               *uuid = att_get_uuid128(&pdu[5]);
+
+       return len;
+}
+
+uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, int len)
+{
+       uint8_t *ptr;
+       int i, w, l;
+
+       if (list == NULL)
+               return 0;
+
+       if (pdu == NULL)
+               return 0;
+
+       l = MIN(len - 2, list->len);
+
+       pdu[0] = ATT_OP_READ_BY_TYPE_RESP;
+       pdu[1] = l;
+       ptr = &pdu[2];
+
+       for (i = 0, w = 2; i < list->num && w + l <= len; i++) {
+               memcpy(ptr, list->data[i], l);
+               ptr += l;
+               w += l;
+       }
+
+       return w;
+}
+
+struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, int len)
+{
+       struct att_data_list *list;
+       const uint8_t *ptr;
+       uint16_t elen, num;
+       int i;
+
+       if (pdu[0] != ATT_OP_READ_BY_TYPE_RESP)
+               return NULL;
+
+       elen = pdu[1];
+       num = (len - 2) / elen;
+       list = att_data_list_alloc(num, elen);
+
+       ptr = &pdu[2];
+
+       for (i = 0; i < num; i++) {
+               memcpy(list->data[i], ptr, list->len);
+               ptr += list->len;
+       }
+
+       return list;
+}
+
+uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, int vlen,
+                                                       uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (vlen > len - min_len)
+               vlen = len - min_len;
+
+       pdu[0] = ATT_OP_WRITE_CMD;
+       att_put_u16(handle, &pdu[1]);
+
+       if (vlen > 0) {
+               memcpy(&pdu[3], value, vlen);
+               return min_len + vlen;
+       }
+
+       return min_len;
+}
+
+uint16_t dec_write_cmd(const uint8_t *pdu, int len, uint16_t *handle,
+                                               uint8_t *value, int *vlen)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (value == NULL || vlen == NULL || handle == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (pdu[0] != ATT_OP_WRITE_CMD)
+               return 0;
+
+       *handle = att_get_u16(&pdu[1]);
+       memcpy(value, pdu + min_len, len - min_len);
+       *vlen = len - min_len;
+
+       return len;
+}
+
+uint16_t enc_write_req(uint16_t handle, const uint8_t *value, int vlen,
+                                                       uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (vlen > len - min_len)
+               vlen = len - min_len;
+
+       pdu[0] = ATT_OP_WRITE_REQ;
+       att_put_u16(handle, &pdu[1]);
+
+       if (vlen > 0) {
+               memcpy(&pdu[3], value, vlen);
+               return min_len + vlen;
+       }
+
+       return min_len;
+}
+
+uint16_t dec_write_req(const uint8_t *pdu, int len, uint16_t *handle,
+                                               uint8_t *value, int *vlen)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (value == NULL || vlen == NULL || handle == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (pdu[0] != ATT_OP_WRITE_REQ)
+               return 0;
+
+       *handle = att_get_u16(&pdu[1]);
+       *vlen = len - min_len;
+       if (*vlen > 0)
+               memcpy(value, pdu + min_len, *vlen);
+
+       return len;
+}
+
+uint16_t enc_write_resp(uint8_t *pdu, int len)
+{
+       if (pdu == NULL)
+               return 0;
+
+       pdu[0] = ATT_OP_WRITE_RESP;
+
+       return sizeof(pdu[0]);
+}
+
+uint16_t dec_write_resp(const uint8_t *pdu, int len)
+{
+       if (pdu == NULL)
+               return 0;
+
+       if (pdu[0] != ATT_OP_WRITE_RESP)
+               return 0;
+
+       return len;
+}
+
+uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       pdu[0] = ATT_OP_READ_REQ;
+       att_put_u16(handle, &pdu[1]);
+
+       return min_len;
+}
+
+uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu,
+                                                                       int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) +
+                                                       sizeof(offset);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       pdu[0] = ATT_OP_READ_BLOB_REQ;
+       att_put_u16(handle, &pdu[1]);
+       att_put_u16(offset, &pdu[3]);
+
+       return min_len;
+}
+
+uint16_t dec_read_req(const uint8_t *pdu, int len, uint16_t *handle)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (handle == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (pdu[0] != ATT_OP_READ_REQ)
+               return 0;
+
+       *handle = att_get_u16(&pdu[1]);
+
+       return min_len;
+}
+
+uint16_t dec_read_blob_req(const uint8_t *pdu, int len, uint16_t *handle,
+                                                       uint16_t *offset)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) +
+                                                       sizeof(*offset);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (handle == NULL)
+               return 0;
+
+       if (offset == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (pdu[0] != ATT_OP_READ_BLOB_REQ)
+               return 0;
+
+       *handle = att_get_u16(&pdu[1]);
+       *offset = att_get_u16(&pdu[3]);
+
+       return min_len;
+}
+
+uint16_t enc_read_resp(uint8_t *value, int vlen, uint8_t *pdu, int len)
+{
+       if (pdu == NULL)
+               return 0;
+
+       /* If the attribute value length is longer than the allowed PDU size,
+        * send only the octets that fit on the PDU. The remaining octets can
+        * be requested using the Read Blob Request. */
+       if (vlen > len - 1)
+               vlen = len - 1;
+
+       pdu[0] = ATT_OP_READ_RESP;
+
+       memcpy(pdu + 1, value, vlen);
+
+       return vlen + 1;
+}
+
+uint16_t enc_read_blob_resp(uint8_t *value, int vlen, uint16_t offset,
+                                                       uint8_t *pdu, int len)
+{
+       if (pdu == NULL)
+               return 0;
+
+       vlen -= offset;
+       if (vlen > len - 1)
+               vlen = len - 1;
+
+       pdu[0] = ATT_OP_READ_BLOB_RESP;
+
+       memcpy(pdu + 1, &value[offset], vlen);
+
+       return vlen + 1;
+}
+
+uint16_t dec_read_resp(const uint8_t *pdu, int len, uint8_t *value, int *vlen)
+{
+       if (pdu == NULL)
+               return 0;
+
+       if (value == NULL || vlen == NULL)
+               return 0;
+
+       if (pdu[0] != ATT_OP_READ_RESP)
+               return 0;
+
+       memcpy(value, pdu + 1, len - 1);
+
+       *vlen = len - 1;
+
+       return len;
+}
+
+uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status,
+                                                       uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(opcode) +
+                                               sizeof(handle) + sizeof(status);
+       uint16_t u16;
+
+       if (len < min_len)
+               return 0;
+
+       u16 = htobs(handle);
+       pdu[0] = ATT_OP_ERROR;
+       pdu[1] = opcode;
+       memcpy(&pdu[2], &u16, sizeof(u16));
+       pdu[4] = status;
+
+       return min_len;
+}
+
+uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       pdu[0] = ATT_OP_FIND_INFO_REQ;
+       att_put_u16(start, &pdu[1]);
+       att_put_u16(end, &pdu[3]);
+
+       return min_len;
+}
+
+uint16_t dec_find_info_req(const uint8_t *pdu, int len, uint16_t *start,
+                                                               uint16_t *end)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (start == NULL || end == NULL)
+               return 0;
+
+       if (pdu[0] != ATT_OP_FIND_INFO_REQ)
+               return 0;
+
+       *start = att_get_u16(&pdu[1]);
+       *end = att_get_u16(&pdu[3]);
+
+       return min_len;
+}
+
+uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list,
+                                                       uint8_t *pdu, int len)
+{
+       uint8_t *ptr;
+       int i, w;
+
+       if (pdu == NULL)
+               return 0;
+
+       if (list == NULL)
+               return 0;
+
+       if (len < list->len + 2)
+               return 0;
+
+       pdu[0] = ATT_OP_FIND_INFO_RESP;
+       pdu[1] = format;
+       ptr = (void *) &pdu[2];
+
+       for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) {
+               memcpy(ptr, list->data[i], list->len);
+               ptr += list->len;
+               w += list->len;
+       }
+
+       return w;
+}
+
+struct att_data_list *dec_find_info_resp(const uint8_t *pdu, int len,
+                                                       uint8_t *format)
+{
+       struct att_data_list *list;
+       uint8_t *ptr;
+       uint16_t elen, num;
+       int i;
+
+       if (pdu == NULL)
+               return 0;
+
+       if (format == NULL)
+               return 0;
+
+       if (pdu[0] != ATT_OP_FIND_INFO_RESP)
+               return 0;
+
+       *format = pdu[1];
+       elen = sizeof(pdu[0]) + sizeof(*format);
+       if (*format == 0x01)
+               elen += 2;
+       else if (*format == 0x02)
+               elen += 16;
+
+       num = (len - 2) / elen;
+
+       ptr = (void *) &pdu[2];
+
+       list = att_data_list_alloc(num, elen);
+
+       for (i = 0; i < num; i++) {
+               memcpy(list->data[i], ptr, list->len);
+               ptr += list->len;
+       }
+
+       return list;
+}
+
+uint16_t enc_notification(struct attribute *a, uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < (a->len + min_len))
+               return 0;
+
+       pdu[0] = ATT_OP_HANDLE_NOTIFY;
+       att_put_u16(a->handle, &pdu[1]);
+       memcpy(&pdu[3], a->data, a->len);
+
+       return a->len + min_len;
+}
+
+uint16_t enc_indication(struct attribute *a, uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < (a->len + min_len))
+               return 0;
+
+       pdu[0] = ATT_OP_HANDLE_IND;
+       att_put_u16(a->handle, &pdu[1]);
+       memcpy(&pdu[3], a->data, a->len);
+
+       return a->len + min_len;
+}
+
+struct attribute *dec_indication(const uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+       struct attribute *a;
+
+       if (pdu == NULL)
+               return NULL;
+
+       if (pdu[0] != ATT_OP_HANDLE_IND)
+               return NULL;
+
+       if (len < min_len)
+               return NULL;
+
+       a = g_malloc0(sizeof(struct attribute) + len - min_len);
+       a->len = len - min_len;
+
+       a->handle = att_get_u16(&pdu[1]);
+       memcpy(a->data, &pdu[3], a->len);
+
+       return a;
+}
+
+uint16_t enc_confirmation(uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       pdu[0] = ATT_OP_HANDLE_CNF;
+
+       return min_len;
+}
+
+uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(mtu);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       pdu[0] = ATT_OP_MTU_REQ;
+       att_put_u16(mtu, &pdu[1]);
+
+       return min_len;
+}
+
+uint16_t dec_mtu_req(const uint8_t *pdu, int len, uint16_t *mtu)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (mtu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (pdu[0] != ATT_OP_MTU_REQ)
+               return 0;
+
+       *mtu = att_get_u16(&pdu[1]);
+
+       return min_len;
+}
+
+uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, int len)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(mtu);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       pdu[0] = ATT_OP_MTU_RESP;
+       att_put_u16(mtu, &pdu[1]);
+
+       return min_len;
+}
+
+uint16_t dec_mtu_resp(const uint8_t *pdu, int len, uint16_t *mtu)
+{
+       const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu);
+
+       if (pdu == NULL)
+               return 0;
+
+       if (mtu == NULL)
+               return 0;
+
+       if (len < min_len)
+               return 0;
+
+       if (pdu[0] != ATT_OP_MTU_RESP)
+               return 0;
+
+       *mtu = att_get_u16(&pdu[1]);
+
+       return min_len;
+}
diff --git a/attrib/att.h b/attrib/att.h
new file mode 100644 (file)
index 0000000..7a83bfa
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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
+ *
+ */
+
+/* GATT Profile Attribute types */
+#define GATT_PRIM_SVC_UUID             0x2800
+#define GATT_SND_SVC_UUID              0x2801
+#define GATT_INCLUDE_UUID              0x2802
+#define GATT_CHARAC_UUID               0x2803
+
+/* GATT Characteristic Types */
+#define GATT_CHARAC_DEVICE_NAME                        0x2A00
+#define GATT_CHARAC_APPEARANCE                 0x2A01
+#define GATT_CHARAC_PERIPHERAL_PRIV_FLAG       0x2A02
+#define GATT_CHARAC_RECONNECTION_ADDRESS       0x2A03
+#define GATT_CHARAC_PERIPHERAL_PREF_CONN       0x2A04
+#define GATT_CHARAC_SERVICE_CHANGED            0x2A05
+
+/* GATT Characteristic Descriptors */
+#define GATT_CHARAC_EXT_PROPER_UUID    0x2900
+#define GATT_CHARAC_USER_DESC_UUID     0x2901
+#define GATT_CLIENT_CHARAC_CFG_UUID    0x2902
+#define GATT_SERVER_CHARAC_CFG_UUID    0x2903
+#define GATT_CHARAC_FMT_UUID           0x2904
+#define GATT_CHARAC_AGREG_FMT_UUID     0x2905
+
+/* Attribute Protocol Opcodes */
+#define ATT_OP_ERROR                   0x01
+#define ATT_OP_MTU_REQ                 0x02
+#define ATT_OP_MTU_RESP                        0x03
+#define ATT_OP_FIND_INFO_REQ           0x04
+#define ATT_OP_FIND_INFO_RESP          0x05
+#define ATT_OP_FIND_BY_TYPE_REQ                0x06
+#define ATT_OP_FIND_BY_TYPE_RESP       0x07
+#define ATT_OP_READ_BY_TYPE_REQ                0x08
+#define ATT_OP_READ_BY_TYPE_RESP       0x09
+#define ATT_OP_READ_REQ                        0x0A
+#define ATT_OP_READ_RESP               0x0B
+#define ATT_OP_READ_BLOB_REQ           0x0C
+#define ATT_OP_READ_BLOB_RESP          0x0D
+#define ATT_OP_READ_MULTI_REQ          0x0E
+#define ATT_OP_READ_MULTI_RESP         0x0F
+#define ATT_OP_READ_BY_GROUP_REQ       0x10
+#define ATT_OP_READ_BY_GROUP_RESP      0x11
+#define ATT_OP_WRITE_REQ               0x12
+#define ATT_OP_WRITE_RESP              0x13
+#define ATT_OP_WRITE_CMD               0x52
+#define ATT_OP_PREP_WRITE_REQ          0x16
+#define ATT_OP_PREP_WRITE_RESP         0x17
+#define ATT_OP_EXEC_WRITE_REQ          0x18
+#define ATT_OP_EXEC_WRITE_RESP         0x19
+#define ATT_OP_HANDLE_NOTIFY           0x1B
+#define ATT_OP_HANDLE_IND              0x1D
+#define ATT_OP_HANDLE_CNF              0x1E
+#define ATT_OP_SIGNED_WRITE_CMD                0xD2
+
+/* Error codes for Error response PDU */
+#define ATT_ECODE_INVALID_HANDLE               0x01
+#define ATT_ECODE_READ_NOT_PERM                        0x02
+#define ATT_ECODE_WRITE_NOT_PERM               0x03
+#define ATT_ECODE_INVALID_PDU                  0x04
+#define ATT_ECODE_INSUFF_AUTHEN                        0x05
+#define ATT_ECODE_REQ_NOT_SUPP                 0x06
+#define ATT_ECODE_INVALID_OFFSET               0x07
+#define ATT_ECODE_INSUFF_AUTHO                 0x08
+#define ATT_ECODE_PREP_QUEUE_FULL              0x09
+#define ATT_ECODE_ATTR_NOT_FOUND               0x0A
+#define ATT_ECODE_ATTR_NOT_LONG                        0x0B
+#define ATT_ECODE_INSUFF_ENCR_KEY_SIZE         0x0C
+#define ATT_ECODE_INVAL_ATTR_VALUE_LEN         0x0D
+#define ATT_ECODE_UNLIKELY                     0x0E
+#define ATT_ECODE_INSUFF_ENC                   0x0F
+#define ATT_ECODE_UNSUPP_GRP_TYPE              0x10
+#define ATT_ECODE_INSUFF_RESOURCES             0x11
+/* Application error */
+#define ATT_ECODE_IO                           0xFF
+
+/* Characteristic Property bit field */
+#define ATT_CHAR_PROPER_BROADCAST              0x01
+#define ATT_CHAR_PROPER_READ                   0x02
+#define ATT_CHAR_PROPER_WRITE_WITHOUT_RESP     0x04
+#define ATT_CHAR_PROPER_WRITE                  0x08
+#define ATT_CHAR_PROPER_NOTIFY                 0x10
+#define ATT_CHAR_PROPER_INDICATE               0x20
+#define ATT_CHAR_PROPER_AUTH                   0x40
+#define ATT_CHAR_PROPER_EXT_PROPER             0x80
+
+
+#define ATT_MAX_MTU                            256
+#define ATT_DEFAULT_L2CAP_MTU                  48
+#define ATT_DEFAULT_LE_MTU                     23
+
+/* Requirements for read/write operations */
+enum {
+       ATT_NONE,               /* No restrictions */
+       ATT_AUTHENTICATION,     /* Authentication required */
+       ATT_AUTHORIZATION,      /* Authorization required */
+       ATT_NOT_PERMITTED,      /* Operation not permitted */
+};
+
+struct attribute {
+       uint16_t handle;
+       bt_uuid_t uuid;
+       int read_reqs;
+       int write_reqs;
+       uint8_t (*read_cb)(struct attribute *a, gpointer user_data);
+       uint8_t (*write_cb)(struct attribute *a, gpointer user_data);
+       gpointer cb_user_data;
+       int len;
+       uint8_t data[0];
+};
+
+struct att_data_list {
+       uint16_t num;
+       uint16_t len;
+       uint8_t **data;
+};
+
+struct att_range {
+       uint16_t start;
+       uint16_t end;
+};
+
+struct att_primary {
+       char uuid[MAX_LEN_UUID_STR + 1];
+       uint16_t start;
+       uint16_t end;
+};
+
+struct att_char {
+       char uuid[MAX_LEN_UUID_STR + 1];
+       uint16_t handle;
+       uint8_t properties;
+       uint16_t value_handle;
+};
+
+/* These functions do byte conversion */
+static inline uint8_t att_get_u8(const void *ptr)
+{
+       const uint8_t *u8_ptr = ptr;
+       return bt_get_unaligned(u8_ptr);
+}
+
+static inline uint16_t att_get_u16(const void *ptr)
+{
+       const uint16_t *u16_ptr = ptr;
+       return btohs(bt_get_unaligned(u16_ptr));
+}
+
+static inline uint32_t att_get_u32(const void *ptr)
+{
+       const uint32_t *u32_ptr = ptr;
+       return btohl(bt_get_unaligned(u32_ptr));
+}
+
+static inline uint128_t att_get_u128(const void *ptr)
+{
+       const uint128_t *u128_ptr = ptr;
+       uint128_t dst;
+
+       btoh128(u128_ptr, &dst);
+
+       return dst;
+}
+
+static inline void att_put_u8(uint8_t src, void *dst)
+{
+       bt_put_unaligned(src, (uint8_t *) dst);
+}
+
+static inline void att_put_u16(uint16_t src, void *dst)
+{
+       bt_put_unaligned(htobs(src), (uint16_t *) dst);
+}
+
+static inline void att_put_u32(uint32_t src, void *dst)
+{
+       bt_put_unaligned(htobl(src), (uint32_t *) dst);
+}
+
+static inline void att_put_u128(uint128_t src, void *dst)
+{
+       uint128_t *d128 = dst;
+
+       htob128(&src, d128);
+}
+
+static inline void att_put_uuid16(bt_uuid_t src, void *dst)
+{
+       att_put_u16(src.value.u16, dst);
+}
+
+static inline void att_put_uuid128(bt_uuid_t src, void *dst)
+{
+       att_put_u128(src.value.u128, dst);
+}
+
+static inline void att_put_uuid(bt_uuid_t src, void *dst)
+{
+       if (src.type == BT_UUID16)
+               att_put_uuid16(src, dst);
+       else
+               att_put_uuid128(src, dst);
+}
+
+static inline bt_uuid_t att_get_uuid16(const void *ptr)
+{
+       bt_uuid_t uuid;
+
+       bt_uuid16_create(&uuid, att_get_u16(ptr));
+
+       return uuid;
+}
+
+static inline bt_uuid_t att_get_uuid128(const void *ptr)
+{
+       bt_uuid_t uuid;
+       uint128_t value;
+
+       value  = att_get_u128(ptr);
+       bt_uuid128_create(&uuid, value);
+
+       return uuid;
+}
+
+struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len);
+void att_data_list_free(struct att_data_list *list);
+
+const char *att_ecode2str(uint8_t status);
+uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+                                                       uint8_t *pdu, int len);
+uint16_t dec_read_by_grp_req(const uint8_t *pdu, int len, uint16_t *start,
+                                               uint16_t *end, bt_uuid_t *uuid);
+uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, int len);
+uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+                       const uint8_t *value, int vlen, uint8_t *pdu, int len);
+uint16_t dec_find_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+               uint16_t *end, bt_uuid_t *uuid, uint8_t *value, int *vlen);
+uint16_t enc_find_by_type_resp(GSList *ranges, uint8_t *pdu, int len);
+GSList *dec_find_by_type_resp(const uint8_t *pdu, int len);
+struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, int len);
+uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+                                                       uint8_t *pdu, int len);
+uint16_t dec_read_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+                                               uint16_t *end, bt_uuid_t *uuid);
+uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu,
+                                                               int len);
+uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, int vlen,
+                                                       uint8_t *pdu, int len);
+uint16_t dec_write_cmd(const uint8_t *pdu, int len, uint16_t *handle,
+                                               uint8_t *value, int *vlen);
+struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, int len);
+uint16_t enc_write_req(uint16_t handle, const uint8_t *value, int vlen,
+                                                       uint8_t *pdu, int len);
+uint16_t dec_write_req(const uint8_t *pdu, int len, uint16_t *handle,
+                                               uint8_t *value, int *vlen);
+uint16_t enc_write_resp(uint8_t *pdu, int len);
+uint16_t dec_write_resp(const uint8_t *pdu, int len);
+uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, int len);
+uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu,
+                                                               int len);
+uint16_t dec_read_req(const uint8_t *pdu, int len, uint16_t *handle);
+uint16_t dec_read_blob_req(const uint8_t *pdu, int len, uint16_t *handle,
+                                                       uint16_t *offset);
+uint16_t enc_read_resp(uint8_t *value, int vlen, uint8_t *pdu, int len);
+uint16_t enc_read_blob_resp(uint8_t *value, int vlen, uint16_t offset,
+                                                       uint8_t *pdu, int len);
+uint16_t dec_read_resp(const uint8_t *pdu, int len, uint8_t *value, int *vlen);
+uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status,
+                                                       uint8_t *pdu, int len);
+uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, int len);
+uint16_t dec_find_info_req(const uint8_t *pdu, int len, uint16_t *start,
+                                                               uint16_t *end);
+uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list,
+                                                       uint8_t *pdu, int len);
+struct att_data_list *dec_find_info_resp(const uint8_t *pdu, int len,
+                                                       uint8_t *format);
+uint16_t enc_notification(struct attribute *a, uint8_t *pdu, int len);
+uint16_t enc_indication(struct attribute *a, uint8_t *pdu, int len);
+struct attribute *dec_indication(const uint8_t *pdu, int len);
+uint16_t enc_confirmation(uint8_t *pdu, int len);
+
+uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, int len);
+uint16_t dec_mtu_req(const uint8_t *pdu, int len, uint16_t *mtu);
+uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, int len);
+uint16_t dec_mtu_resp(const uint8_t *pdu, int len, uint16_t *mtu);
diff --git a/attrib/client.c b/attrib/client.c
new file mode 100644 (file)
index 0000000..3237a6b
--- /dev/null
@@ -0,0 +1,1116 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+#include "adapter.h"
+#include "device.h"
+#include "log.h"
+#include "gdbus.h"
+#include "error.h"
+#include "dbus-common.h"
+#include "btio.h"
+#include "storage.h"
+
+#include "att.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "client.h"
+
+#define CHAR_INTERFACE "org.bluez.Characteristic"
+
+struct gatt_service {
+       struct btd_device *dev;
+       bdaddr_t sba;
+       bdaddr_t dba;
+       char *path;
+       GSList *primary;
+       GAttrib *attrib;
+       DBusMessage *msg;
+       int psm;
+       gboolean listen;
+};
+
+struct format {
+       guint8 format;
+       guint8 exponent;
+       guint16 unit;
+       guint8 namespace;
+       guint16 desc;
+} __attribute__ ((packed));
+
+struct primary {
+       struct gatt_service *gatt;
+       struct att_primary *att;
+       char *path;
+       GSList *chars;
+       GSList *watchers;
+};
+
+struct characteristic {
+       struct primary *prim;
+       char *path;
+       uint16_t handle;
+       uint16_t end;
+       uint8_t perm;
+       char type[MAX_LEN_UUID_STR + 1];
+       char *name;
+       char *desc;
+       struct format *format;
+       uint8_t *value;
+       size_t vlen;
+};
+
+struct query_data {
+       struct primary *prim;
+       struct characteristic *chr;
+       DBusMessage *msg;
+       uint16_t handle;
+};
+
+struct watcher {
+       guint id;
+       char *name;
+       char *path;
+       struct primary *prim;
+};
+
+static GSList *gatt_services = NULL;
+
+static DBusConnection *connection;
+
+static void characteristic_free(void *user_data)
+{
+       struct characteristic *chr = user_data;
+
+       g_free(chr->path);
+       g_free(chr->desc);
+       g_free(chr->format);
+       g_free(chr->value);
+       g_free(chr->name);
+       g_free(chr);
+}
+
+static void watcher_free(void *user_data)
+{
+       struct watcher *watcher = user_data;
+
+       g_free(watcher->path);
+       g_free(watcher->name);
+       g_free(watcher);
+}
+
+static void primary_free(void *user_data)
+{
+       struct primary *prim = user_data;
+       GSList *l;
+
+       for (l = prim->watchers; l; l = l->next) {
+               struct watcher *watcher = l->data;
+               g_dbus_remove_watch(connection, watcher->id);
+       }
+
+       g_slist_foreach(prim->chars, (GFunc) characteristic_free, NULL);
+       g_slist_free(prim->chars);
+       g_free(prim->path);
+       g_free(prim);
+}
+
+static void gatt_service_free(void *user_data)
+{
+       struct gatt_service *gatt = user_data;
+
+       g_slist_foreach(gatt->primary, (GFunc) primary_free, NULL);
+       g_slist_free(gatt->primary);
+       g_attrib_unref(gatt->attrib);
+       g_free(gatt->path);
+       btd_device_unref(gatt->dev);
+       g_free(gatt);
+}
+
+static int gatt_dev_cmp(gconstpointer a, gconstpointer b)
+{
+       const struct gatt_service *gatt = a;
+       const struct btd_device *dev = b;
+
+       return gatt->dev != dev;
+}
+
+static int characteristic_handle_cmp(gconstpointer a, gconstpointer b)
+{
+       const struct characteristic *chr = a;
+       uint16_t handle = GPOINTER_TO_UINT(b);
+
+       return chr->handle - handle;
+}
+
+static int watcher_cmp(gconstpointer a, gconstpointer b)
+{
+       const struct watcher *watcher = a;
+       const struct watcher *match = b;
+       int ret;
+
+       ret = g_strcmp0(watcher->name, match->name);
+       if (ret != 0)
+               return ret;
+
+       return g_strcmp0(watcher->path, match->path);
+}
+
+static void append_char_dict(DBusMessageIter *iter, struct characteristic *chr)
+{
+       DBusMessageIter dict;
+       const char *name = "";
+       char *uuid;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                       DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+       uuid = g_strdup(chr->type);
+       dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+       g_free(uuid);
+
+       /* FIXME: Translate UUID to name. */
+       dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &name);
+
+       if (chr->desc)
+               dict_append_entry(&dict, "Description", DBUS_TYPE_STRING,
+                                                               &chr->desc);
+
+       if (chr->value)
+               dict_append_array(&dict, "Value", DBUS_TYPE_BYTE, &chr->value,
+                                                               chr->vlen);
+
+       /* FIXME: Missing Format, Value and Representation */
+
+       dbus_message_iter_close_container(iter, &dict);
+}
+
+static void watcher_exit(DBusConnection *conn, void *user_data)
+{
+       struct watcher *watcher = user_data;
+       struct primary *prim = watcher->prim;
+       struct gatt_service *gatt = prim->gatt;
+
+       DBG("%s watcher %s exited", prim->path, watcher->name);
+
+       prim->watchers = g_slist_remove(prim->watchers, watcher);
+
+       g_attrib_unref(gatt->attrib);
+}
+
+static int characteristic_set_value(struct characteristic *chr,
+                                       const uint8_t *value, size_t vlen)
+{
+       chr->value = g_try_realloc(chr->value, vlen);
+       if (chr->value == NULL)
+               return -ENOMEM;
+
+       memcpy(chr->value, value, vlen);
+       chr->vlen = vlen;
+
+       return 0;
+}
+
+static void update_watchers(gpointer data, gpointer user_data)
+{
+       struct watcher *w = data;
+       struct characteristic *chr = user_data;
+       DBusMessage *msg;
+
+       msg = dbus_message_new_method_call(w->name, w->path,
+                               "org.bluez.Watcher", "ValueChanged");
+       if (msg == NULL)
+               return;
+
+       dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &chr->path,
+                       DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+                       &chr->value, chr->vlen, DBUS_TYPE_INVALID);
+
+       dbus_message_set_no_reply(msg, TRUE);
+       g_dbus_send_message(connection, msg);
+}
+
+static void events_handler(const uint8_t *pdu, uint16_t len,
+                                                       gpointer user_data)
+{
+       struct gatt_service *gatt = user_data;
+       struct characteristic *chr;
+       struct primary *prim;
+       GSList *lprim, *lchr;
+       uint8_t opdu[ATT_MAX_MTU];
+       guint handle;
+       uint16_t olen;
+
+       if (len < 3) {
+               DBG("Malformed notification/indication packet (opcode 0x%02x)",
+                                                                       pdu[0]);
+               return;
+       }
+
+       handle = att_get_u16(&pdu[1]);
+
+       for (lprim = gatt->primary, prim = NULL, chr = NULL; lprim;
+                                               lprim = lprim->next) {
+               prim = lprim->data;
+
+               lchr = g_slist_find_custom(prim->chars,
+                       GUINT_TO_POINTER(handle), characteristic_handle_cmp);
+               if (lchr) {
+                       chr = lchr->data;
+                       break;
+               }
+       }
+
+       if (chr == NULL) {
+               DBG("Attribute handle 0x%02x not found", handle);
+               return;
+       }
+
+       switch (pdu[0]) {
+       case ATT_OP_HANDLE_IND:
+               olen = enc_confirmation(opdu, sizeof(opdu));
+               g_attrib_send(gatt->attrib, 0, opdu[0], opdu, olen,
+                                               NULL, NULL, NULL);
+       case ATT_OP_HANDLE_NOTIFY:
+               if (characteristic_set_value(chr, &pdu[3], len - 3) < 0)
+                       DBG("Can't change Characteristic 0x%02x", handle);
+
+               g_slist_foreach(prim->watchers, update_watchers, chr);
+               break;
+       }
+}
+
+static void attrib_destroy(gpointer user_data)
+{
+       struct gatt_service *gatt = user_data;
+
+       gatt->attrib = NULL;
+}
+
+static void attrib_disconnect(gpointer user_data)
+{
+       struct gatt_service *gatt = user_data;
+
+       /* Remote initiated disconnection only */
+       g_attrib_unref(gatt->attrib);
+}
+
+static void connect_cb(GIOChannel *chan, GError *gerr, gpointer user_data)
+{
+       struct gatt_service *gatt = user_data;
+
+       if (gerr) {
+               if (gatt->msg) {
+                       DBusMessage *reply = btd_error_failed(gatt->msg,
+                                                       gerr->message);
+                       g_dbus_send_message(connection, reply);
+               }
+
+               error("%s", gerr->message);
+               goto fail;
+       }
+
+       if (gatt->attrib == NULL)
+               return;
+
+       /* Listen mode: used for notification and indication */
+       if (gatt->listen == TRUE) {
+               g_attrib_register(gatt->attrib,
+                                       ATT_OP_HANDLE_NOTIFY,
+                                       events_handler, gatt, NULL);
+               g_attrib_register(gatt->attrib,
+                                       ATT_OP_HANDLE_IND,
+                                       events_handler, gatt, NULL);
+               return;
+       }
+
+       return;
+fail:
+       g_attrib_unref(gatt->attrib);
+}
+
+static int l2cap_connect(struct gatt_service *gatt, GError **gerr,
+                                                               gboolean listen)
+{
+       GIOChannel *io;
+
+       if (gatt->attrib != NULL) {
+               gatt->attrib = g_attrib_ref(gatt->attrib);
+               gatt->listen = listen;
+               return 0;
+       }
+
+       /*
+        * FIXME: If the service doesn't support Client Characteristic
+        * Configuration it is necessary to poll the server from time
+        * to time checking for modifications.
+        */
+       if (gatt->psm < 0)
+               io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr,
+                       BT_IO_OPT_SOURCE_BDADDR, &gatt->sba,
+                       BT_IO_OPT_DEST_BDADDR, &gatt->dba,
+                       BT_IO_OPT_CID, GATT_CID,
+                       BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+                       BT_IO_OPT_INVALID);
+       else
+               io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr,
+                       BT_IO_OPT_SOURCE_BDADDR, &gatt->sba,
+                       BT_IO_OPT_DEST_BDADDR, &gatt->dba,
+                       BT_IO_OPT_PSM, gatt->psm,
+                       BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+                       BT_IO_OPT_INVALID);
+       if (!io)
+               return -1;
+
+       gatt->attrib = g_attrib_new(io);
+       g_io_channel_unref(io);
+       gatt->listen = listen;
+
+       g_attrib_set_destroy_function(gatt->attrib, attrib_destroy, gatt);
+       g_attrib_set_disconnect_function(gatt->attrib, attrib_disconnect,
+                                                                       gatt);
+
+       return 0;
+}
+
+static DBusMessage *register_watcher(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       const char *sender = dbus_message_get_sender(msg);
+       struct primary *prim = data;
+       struct watcher *watcher;
+       GError *gerr = NULL;
+       char *path;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                                       DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       if (l2cap_connect(prim->gatt, &gerr, TRUE) < 0) {
+               DBusMessage *reply = btd_error_failed(msg, gerr->message);
+               g_error_free(gerr);
+               return reply;
+       }
+
+       watcher = g_new0(struct watcher, 1);
+       watcher->name = g_strdup(sender);
+       watcher->prim = prim;
+       watcher->path = g_strdup(path);
+       watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit,
+                                                       watcher, watcher_free);
+
+       prim->watchers = g_slist_append(prim->watchers, watcher);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_watcher(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       const char *sender = dbus_message_get_sender(msg);
+       struct primary *prim = data;
+       struct watcher *watcher, *match;
+       GSList *l;
+       char *path;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                                       DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       match = g_new0(struct watcher, 1);
+       match->name = g_strdup(sender);
+       match->path = g_strdup(path);
+       l = g_slist_find_custom(prim->watchers, match, watcher_cmp);
+       watcher_free(match);
+       if (!l)
+               return btd_error_not_authorized(msg);
+
+       watcher = l->data;
+       g_dbus_remove_watch(conn, watcher->id);
+       prim->watchers = g_slist_remove(prim->watchers, watcher);
+       watcher_free(watcher);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_value(DBusConnection *conn, DBusMessage *msg,
+                       DBusMessageIter *iter, struct characteristic *chr)
+{
+       struct gatt_service *gatt = chr->prim->gatt;
+       DBusMessageIter sub;
+       GError *gerr = NULL;
+       uint8_t *value;
+       int len;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY ||
+                       dbus_message_iter_get_element_type(iter) != DBUS_TYPE_BYTE)
+               return btd_error_invalid_args(msg);
+
+       dbus_message_iter_recurse(iter, &sub);
+
+       dbus_message_iter_get_fixed_array(&sub, &value, &len);
+
+       if (l2cap_connect(gatt, &gerr, FALSE) < 0) {
+               DBusMessage *reply = btd_error_failed(msg, gerr->message);
+               g_error_free(gerr);
+               return reply;
+       }
+
+       gatt_write_cmd(gatt->attrib, chr->handle, value, len, NULL, NULL);
+
+       characteristic_set_value(chr, value, len);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct characteristic *chr = data;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       dbus_message_iter_init_append(reply, &iter);
+
+       append_char_dict(&iter, chr);
+
+       return reply;
+}
+
+static DBusMessage *set_property(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct characteristic *chr = data;
+       DBusMessageIter iter;
+       DBusMessageIter sub;
+       const char *property;
+
+       if (!dbus_message_iter_init(msg, &iter))
+               return btd_error_invalid_args(msg);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+               return btd_error_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 btd_error_invalid_args(msg);
+
+       dbus_message_iter_recurse(&iter, &sub);
+
+       if (g_str_equal("Value", property))
+               return set_value(conn, msg, &sub, chr);
+
+       return btd_error_invalid_args(msg);
+}
+
+static GDBusMethodTable char_methods[] = {
+       { "GetProperties",      "",     "a{sv}", get_properties },
+       { "SetProperty",        "sv",   "",     set_property,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { }
+};
+
+static char *characteristic_list_to_string(GSList *chars)
+{
+       GString *characteristics;
+       GSList *l;
+
+       characteristics = g_string_new(NULL);
+
+       for (l = chars; l; l = l->next) {
+               struct characteristic *chr = l->data;
+               char chr_str[64];
+
+               memset(chr_str, 0, sizeof(chr_str));
+
+               snprintf(chr_str, sizeof(chr_str), "%04X#%02X#%04X#%s ",
+                               chr->handle, chr->perm, chr->end, chr->type);
+
+               characteristics = g_string_append(characteristics, chr_str);
+       }
+
+       return g_string_free(characteristics, FALSE);
+}
+
+static void store_characteristics(struct gatt_service *gatt,
+                                                       struct primary *prim)
+{
+       char *characteristics;
+       struct att_primary *att = prim->att;
+
+       characteristics = characteristic_list_to_string(prim->chars);
+
+       write_device_characteristics(&gatt->sba, &gatt->dba, att->start,
+                                                       characteristics);
+
+       g_free(characteristics);
+}
+
+static void register_characteristics(struct primary *prim)
+{
+       GSList *lc;
+
+       for (lc = prim->chars; lc; lc = lc->next) {
+               struct characteristic *chr = lc->data;
+               g_dbus_register_interface(connection, chr->path,
+                               CHAR_INTERFACE, char_methods,
+                               NULL, NULL, chr, NULL);
+               DBG("Registered: %s", chr->path);
+       }
+}
+
+static GSList *string_to_characteristic_list(struct primary *prim,
+                                                       const char *str)
+{
+       GSList *l = NULL;
+       char **chars;
+       int i;
+
+       if (str == NULL)
+               return NULL;
+
+       chars = g_strsplit(str, " ", 0);
+       if (chars == NULL)
+               return NULL;
+
+       for (i = 0; chars[i]; i++) {
+               struct characteristic *chr;
+               int ret;
+
+               chr = g_new0(struct characteristic, 1);
+
+               ret = sscanf(chars[i], "%04hX#%02hhX#%04hX#%s", &chr->handle,
+                               &chr->perm, &chr->end, chr->type);
+               if (ret < 4) {
+                       g_free(chr);
+                       continue;
+               }
+
+               chr->prim = prim;
+               chr->path = g_strdup_printf("%s/characteristic%04x",
+                                               prim->path, chr->handle);
+
+               l = g_slist_append(l, chr);
+       }
+
+       g_strfreev(chars);
+
+       return l;
+}
+
+static void load_characteristics(gpointer data, gpointer user_data)
+{
+       struct primary *prim = data;
+       struct att_primary *att = prim->att;
+       struct gatt_service *gatt = user_data;
+       GSList *chrs_list;
+       char *str;
+
+       if (prim->chars) {
+               DBG("Characteristics already loaded");
+               return;
+       }
+
+       str = read_device_characteristics(&gatt->sba, &gatt->dba, att->start);
+       if (str == NULL)
+               return;
+
+       chrs_list = string_to_characteristic_list(prim, str);
+
+       free(str);
+
+       if (chrs_list == NULL)
+               return;
+
+       prim->chars = chrs_list;
+       register_characteristics(prim);
+
+       return;
+}
+
+static void store_attribute(struct gatt_service *gatt, uint16_t handle,
+                               uint16_t type, uint8_t *value, gsize len)
+{
+       bt_uuid_t uuid;
+       char *str, *tmp;
+       guint i;
+
+       str = g_malloc0(MAX_LEN_UUID_STR + len * 2 + 1);
+
+       bt_uuid16_create(&uuid, type);
+       bt_uuid_to_string(&uuid, str, MAX_LEN_UUID_STR);
+
+       str[MAX_LEN_UUID_STR - 1] = '#';
+
+       for (i = 0, tmp = str + MAX_LEN_UUID_STR; i < len; i++, tmp += 2)
+               sprintf(tmp, "%02X", value[i]);
+
+       write_device_attribute(&gatt->sba, &gatt->dba, handle, str);
+       g_free(str);
+}
+
+static void update_char_desc(guint8 status, const guint8 *pdu, guint16 len,
+                                                       gpointer user_data)
+{
+       struct query_data *current = user_data;
+       struct gatt_service *gatt = current->prim->gatt;
+       struct characteristic *chr = current->chr;
+
+       if (status == 0) {
+
+               g_free(chr->desc);
+
+               chr->desc = g_malloc(len);
+               memcpy(chr->desc, pdu + 1, len - 1);
+               chr->desc[len - 1] = '\0';
+
+               store_attribute(gatt, current->handle,
+                               GATT_CHARAC_USER_DESC_UUID,
+                               (void *) chr->desc, len);
+       } else if (status == ATT_ECODE_INSUFF_ENC) {
+               GIOChannel *io = g_attrib_get_channel(gatt->attrib);
+
+               if (bt_io_set(io, BT_IO_L2CAP, NULL,
+                               BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH,
+                               BT_IO_OPT_INVALID)) {
+                       gatt_read_char(gatt->attrib, current->handle, 0,
+                                       update_char_desc, current);
+                       return;
+               }
+       }
+
+       g_attrib_unref(gatt->attrib);
+       g_free(current);
+}
+
+static void update_char_format(guint8 status, const guint8 *pdu, guint16 len,
+                                                               gpointer user_data)
+{
+       struct query_data *current = user_data;
+       struct gatt_service *gatt = current->prim->gatt;
+       struct characteristic *chr = current->chr;
+
+       if (status != 0)
+               goto done;
+
+       if (len < 8)
+               goto done;
+
+       g_free(chr->format);
+
+       chr->format = g_new0(struct format, 1);
+       memcpy(chr->format, pdu + 1, 7);
+
+       store_attribute(gatt, current->handle, GATT_CHARAC_FMT_UUID,
+                               (void *) chr->format, sizeof(*chr->format));
+
+done:
+       g_attrib_unref(gatt->attrib);
+       g_free(current);
+}
+
+static void update_char_value(guint8 status, const guint8 *pdu,
+                                       guint16 len, gpointer user_data)
+{
+       struct query_data *current = user_data;
+       struct gatt_service *gatt = current->prim->gatt;
+       struct characteristic *chr = current->chr;
+
+       if (status == 0)
+               characteristic_set_value(chr, pdu + 1, len - 1);
+       else if (status == ATT_ECODE_INSUFF_ENC) {
+               GIOChannel *io = g_attrib_get_channel(gatt->attrib);
+
+               if (bt_io_set(io, BT_IO_L2CAP, NULL,
+                               BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH,
+                               BT_IO_OPT_INVALID)) {
+                       gatt_read_char(gatt->attrib, chr->handle, 0,
+                                       update_char_value, current);
+                       return;
+               }
+       }
+
+       g_attrib_unref(gatt->attrib);
+       g_free(current);
+}
+
+static int uuid_desc16_cmp(bt_uuid_t *uuid, guint16 desc)
+{
+       bt_uuid_t u16;
+
+       bt_uuid16_create(&u16, desc);
+
+       return bt_uuid_cmp(uuid, &u16);
+}
+
+static void descriptor_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                                       gpointer user_data)
+{
+       struct query_data *current = user_data;
+       struct gatt_service *gatt = current->prim->gatt;
+       struct att_data_list *list;
+       guint8 format;
+       int i;
+
+       if (status != 0)
+               goto done;
+
+       DBG("Find Information Response received");
+
+       list = dec_find_info_resp(pdu, plen, &format);
+       if (list == NULL)
+               goto done;
+
+       for (i = 0; i < list->num; i++) {
+               guint16 handle;
+               bt_uuid_t uuid;
+               uint8_t *info = list->data[i];
+               struct query_data *qfmt;
+
+               handle = att_get_u16(info);
+
+               if (format == 0x01) {
+                       uuid = att_get_uuid16(&info[2]);
+               } else {
+                       /* Currently, only "user description" and "presentation
+                        * format" descriptors are used, and both have 16-bit
+                        * UUIDs. Therefore there is no need to support format
+                        * 0x02 yet. */
+                       continue;
+               }
+               qfmt = g_new0(struct query_data, 1);
+               qfmt->prim = current->prim;
+               qfmt->chr = current->chr;
+               qfmt->handle = handle;
+
+               if (uuid_desc16_cmp(&uuid, GATT_CHARAC_USER_DESC_UUID) == 0) {
+                       gatt->attrib = g_attrib_ref(gatt->attrib);
+                       gatt_read_char(gatt->attrib, handle, 0, update_char_desc,
+                                                                       qfmt);
+               } else if (uuid_desc16_cmp(&uuid, GATT_CHARAC_FMT_UUID) == 0) {
+                       gatt->attrib = g_attrib_ref(gatt->attrib);
+                       gatt_read_char(gatt->attrib, handle, 0,
+                                               update_char_format, qfmt);
+               } else
+                       g_free(qfmt);
+       }
+
+       att_data_list_free(list);
+done:
+       g_attrib_unref(gatt->attrib);
+       g_free(current);
+}
+
+static void update_all_chars(gpointer data, gpointer user_data)
+{
+       struct query_data *qdesc, *qvalue;
+       struct characteristic *chr = data;
+       struct primary *prim = user_data;
+       struct gatt_service *gatt = prim->gatt;
+
+       qdesc = g_new0(struct query_data, 1);
+       qdesc->prim = prim;
+       qdesc->chr = chr;
+
+       gatt->attrib = g_attrib_ref(gatt->attrib);
+       gatt_find_info(gatt->attrib, chr->handle + 1, chr->end, descriptor_cb,
+                                                                       qdesc);
+
+       qvalue = g_new0(struct query_data, 1);
+       qvalue->prim = prim;
+       qvalue->chr = chr;
+
+       gatt->attrib = g_attrib_ref(gatt->attrib);
+       gatt_read_char(gatt->attrib, chr->handle, 0, update_char_value, qvalue);
+}
+
+static void char_discovered_cb(GSList *characteristics, guint8 status,
+                                                       gpointer user_data)
+{
+       DBusMessage *reply;
+       DBusMessageIter iter, array_iter;
+       struct query_data *current = user_data;
+       struct primary *prim = current->prim;
+       struct att_primary *att = prim->att;
+       struct gatt_service *gatt = prim->gatt;
+       uint16_t *previous_end = NULL;
+       GSList *l;
+
+       if (status != 0) {
+               const char *str = att_ecode2str(status);
+
+               DBG("Discover all characteristics failed: %s", str);
+               reply = btd_error_failed(current->msg, str);
+               goto fail;
+       }
+
+       for (l = characteristics; l; l = l->next) {
+               struct att_char *current_chr = l->data;
+               struct characteristic *chr;
+               guint handle = current_chr->value_handle;
+               GSList *lchr;
+
+               lchr = g_slist_find_custom(prim->chars,
+                       GUINT_TO_POINTER(handle), characteristic_handle_cmp);
+               if (lchr)
+                       continue;
+
+               chr = g_new0(struct characteristic, 1);
+               chr->prim = prim;
+               chr->perm = current_chr->properties;
+               chr->handle = current_chr->value_handle;
+               chr->path = g_strdup_printf("%s/characteristic%04x",
+                                               prim->path, chr->handle);
+               strncpy(chr->type, current_chr->uuid, sizeof(chr->type));
+
+               if (previous_end)
+                       *previous_end = current_chr->handle;
+
+               previous_end = &chr->end;
+
+               prim->chars = g_slist_append(prim->chars, chr);
+       }
+
+       if (previous_end)
+               *previous_end = att->end;
+
+       store_characteristics(gatt, prim);
+       register_characteristics(prim);
+
+       reply = dbus_message_new_method_return(current->msg);
+
+       dbus_message_iter_init_append(reply, &iter);
+
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+                               DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter);
+
+       for (l = prim->chars; l; l = l->next) {
+               struct characteristic *chr = l->data;
+
+               dbus_message_iter_append_basic(&array_iter,
+                                       DBUS_TYPE_OBJECT_PATH, &chr->path);
+       }
+
+       dbus_message_iter_close_container(&iter, &array_iter);
+
+       g_slist_foreach(prim->chars, update_all_chars, prim);
+
+fail:
+       g_dbus_send_message(connection, reply);
+       g_attrib_unref(gatt->attrib);
+       g_free(current);
+}
+
+static DBusMessage *discover_char(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct primary *prim = data;
+       struct att_primary *att = prim->att;
+       struct gatt_service *gatt = prim->gatt;
+       struct query_data *qchr;
+       GError *gerr = NULL;
+
+       if (l2cap_connect(prim->gatt, &gerr, FALSE) < 0) {
+               DBusMessage *reply = btd_error_failed(msg, gerr->message);
+               g_error_free(gerr);
+               return reply;
+       }
+
+       qchr = g_new0(struct query_data, 1);
+       qchr->prim = prim;
+       qchr->msg = dbus_message_ref(msg);
+
+       gatt_discover_char(gatt->attrib, att->start, att->end,
+                                               char_discovered_cb, qchr);
+
+       return NULL;
+}
+
+static DBusMessage *prim_get_properties(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct primary *prim = data;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       DBusMessageIter dict;
+       GSList *l;
+       char **chars;
+       const char *uuid;
+       int i;
+
+       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);
+
+       chars = g_new0(char *, g_slist_length(prim->chars) + 1);
+
+       for (i = 0, l = prim->chars; l; l = l->next, i++) {
+               struct characteristic *chr = l->data;
+               chars[i] = chr->path;
+       }
+
+       dict_append_array(&dict, "Characteristics", DBUS_TYPE_OBJECT_PATH,
+                                                               &chars, i);
+       uuid = prim->att->uuid;
+       dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+
+       g_free(chars);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static GDBusMethodTable prim_methods[] = {
+       { "DiscoverCharacteristics",    "",     "ao",   discover_char,
+                                       G_DBUS_METHOD_FLAG_ASYNC        },
+       { "RegisterCharacteristicsWatcher",     "o", "",
+                                               register_watcher        },
+       { "UnregisterCharacteristicsWatcher",   "o", "",
+                                               unregister_watcher      },
+       { "GetProperties",      "",     "a{sv}",prim_get_properties     },
+       { }
+};
+
+static void register_primaries(struct gatt_service *gatt, GSList *primaries)
+{
+       GSList *l;
+
+       for (l = primaries; l; l = l->next) {
+               struct att_primary *att = l->data;
+               struct primary *prim;
+
+               prim = g_new0(struct primary, 1);
+               prim->att = att;
+               prim->gatt = gatt;
+               prim->path = g_strdup_printf("%s/service%04x", gatt->path,
+                                                               att->start);
+
+               g_dbus_register_interface(connection, prim->path,
+                               CHAR_INTERFACE, prim_methods,
+                               NULL, NULL, prim, NULL);
+               DBG("Registered: %s", prim->path);
+
+               gatt->primary = g_slist_append(gatt->primary, prim);
+               btd_device_add_service(gatt->dev, prim->path);
+               load_characteristics(prim, gatt);
+       }
+}
+
+int attrib_client_register(struct btd_device *device, int psm)
+{
+       struct btd_adapter *adapter = device_get_adapter(device);
+       const char *path = device_get_path(device);
+       struct gatt_service *gatt;
+       GSList *primaries = btd_device_get_primaries(device);
+       bdaddr_t sba, dba;
+
+       adapter_get_address(adapter, &sba);
+       device_get_address(device, &dba);
+
+       gatt = g_new0(struct gatt_service, 1);
+       gatt->dev = btd_device_ref(device);
+       gatt->listen = FALSE;
+       gatt->path = g_strdup(path);
+       bacpy(&gatt->sba, &sba);
+       bacpy(&gatt->dba, &dba);
+       gatt->psm = psm;
+
+       register_primaries(gatt, primaries);
+
+       gatt_services = g_slist_append(gatt_services, gatt);
+
+       return 0;
+}
+
+void attrib_client_unregister(struct btd_device *device)
+{
+       struct gatt_service *gatt;
+       GSList *l, *lp, *lc;
+
+       l = g_slist_find_custom(gatt_services, device, gatt_dev_cmp);
+       if (!l)
+               return;
+
+       gatt = l->data;
+       gatt_services = g_slist_remove(gatt_services, gatt);
+
+       for (lp = gatt->primary; lp; lp = lp->next) {
+               struct primary *prim = lp->data;
+               for (lc = prim->chars; lc; lc = lc->next) {
+                       struct characteristic *chr = lc->data;
+                       g_dbus_unregister_interface(connection, chr->path,
+                                                               CHAR_INTERFACE);
+               }
+               g_dbus_unregister_interface(connection, prim->path,
+                                                               CHAR_INTERFACE);
+       }
+
+       gatt_service_free(gatt);
+}
+
+int attrib_client_init(DBusConnection *conn)
+{
+
+       connection = dbus_connection_ref(conn);
+
+       /*
+        * FIXME: if the adapter supports BLE start scanning. Temporary
+        * solution, this approach doesn't allow to control scanning based
+        * on the discoverable property.
+        */
+
+       return 0;
+}
+
+void attrib_client_exit(void)
+{
+       dbus_connection_unref(connection);
+}
diff --git a/attrib/client.h b/attrib/client.h
new file mode 100644 (file)
index 0000000..50e2b5f
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 attrib_client_init(DBusConnection *conn);
+void attrib_client_exit(void);
+int attrib_client_register(struct btd_device *device, int psm);
+void attrib_client_unregister(struct btd_device *device);
diff --git a/attrib/example.c b/attrib/example.c
new file mode 100644 (file)
index 0000000..fae288c
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <arpa/inet.h>
+
+#include <bluetooth/uuid.h>
+
+#include <glib.h>
+
+#include "log.h"
+#include "attrib-server.h"
+
+#include "att.h"
+#include "example.h"
+
+/* FIXME: Not defined by SIG? UUID128? */
+#define OPCODES_SUPPORTED_UUID          0xA001
+#define BATTERY_STATE_SVC_UUID         0xA002
+#define BATTERY_STATE_UUID             0xA003
+#define THERM_HUMIDITY_SVC_UUID                0xA004
+#define MANUFACTURER_SVC_UUID          0xA005
+#define TEMPERATURE_UUID               0xA006
+#define FMT_CELSIUS_UUID               0xA007
+#define FMT_OUTSIDE_UUID               0xA008
+#define RELATIVE_HUMIDITY_UUID         0xA009
+#define FMT_PERCENT_UUID               0xA00A
+#define BLUETOOTH_SIG_UUID             0xA00B
+#define MANUFACTURER_NAME_UUID         0xA00C
+#define MANUFACTURER_SERIAL_UUID       0xA00D
+#define VENDOR_SPECIFIC_SVC_UUID       0xA00E
+#define VENDOR_SPECIFIC_TYPE_UUID      0xA00F
+#define FMT_KILOGRAM_UUID              0xA010
+#define FMT_HANGING_UUID               0xA011
+
+static GSList *sdp_handles = NULL;
+
+static int register_attributes(void)
+{
+       const char *desc_out_temp = "Outside Temperature";
+       const char *desc_out_hum = "Outside Relative Humidity";
+       const char *desc_weight = "Rucksack Weight";
+       const char *manufacturer_name1 = "ACME Temperature Sensor";
+       const char *manufacturer_name2 = "ACME Weighing Scales";
+       const char *serial1 = "237495-3282-A";
+       const char *serial2 = "11267-2327A00239";
+
+       const uint128_t char_weight_uuid_btorder = {
+               .data = { 0x80, 0x88, 0xF2, 0x18, 0x90, 0x2C, 0x45, 0x0B,
+                         0xB6, 0xC4, 0x62, 0x89, 0x1E, 0x8C, 0x25, 0xE9 } };
+       const uint128_t prim_weight_uuid_btorder = {
+               .data = { 0x4F, 0x0A, 0xC0, 0x96, 0x35, 0xD4, 0x49, 0x11,
+                         0x96, 0x31, 0xDE, 0xA8, 0xDC, 0x74, 0xEE, 0xFE } };
+
+       uint128_t char_weight_uuid;
+       uint8_t atval[256];
+       uint32_t handle;
+       bt_uuid_t uuid;
+       int len;
+
+       btoh128(&char_weight_uuid_btorder, &char_weight_uuid);
+
+       /* Battery state service: primary service definition */
+       bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+       att_put_u16(BATTERY_STATE_SVC_UUID, &atval[0]);
+       attrib_db_add(0x0100, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+       /* Battery: battery state characteristic */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0110, &atval[1]);
+       att_put_u16(BATTERY_STATE_UUID, &atval[3]);
+       attrib_db_add(0x0106, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* Battery: battery state attribute */
+       bt_uuid16_create(&uuid, BATTERY_STATE_UUID);
+       atval[0] = 0x04;
+       attrib_db_add(0x0110, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1);
+
+       /* Battery: Client Characteristic Configuration */
+       bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
+       atval[0] = 0x00;
+       atval[1] = 0x00;
+       attrib_db_add(0x0111, &uuid, ATT_NONE, ATT_AUTHENTICATION, atval, 2);
+
+       /* Add an SDP record for the above service */
+       handle = attrib_create_sdp(0x0100, "Battery State Service");
+       if (handle)
+               sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+       /* Thermometer: primary service definition */
+       bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+       att_put_u16(THERM_HUMIDITY_SVC_UUID, &atval[0]);
+       attrib_db_add(0x0200, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+       /* Thermometer: Include */
+       bt_uuid16_create(&uuid, GATT_INCLUDE_UUID);
+       att_put_u16(0x0500, &atval[0]);
+       att_put_u16(0x0504, &atval[2]);
+       att_put_u16(MANUFACTURER_SVC_UUID, &atval[4]);
+       attrib_db_add(0x0201, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+       /* Thermometer: Include */
+       att_put_u16(0x0550, &atval[0]);
+       att_put_u16(0x0568, &atval[2]);
+       att_put_u16(VENDOR_SPECIFIC_SVC_UUID, &atval[4]);
+       attrib_db_add(0x0202, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+       /* Thermometer: temperature characteristic */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0204, &atval[1]);
+       att_put_u16(TEMPERATURE_UUID, &atval[3]);
+       attrib_db_add(0x0203, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* Thermometer: temperature characteristic value */
+       bt_uuid16_create(&uuid, TEMPERATURE_UUID);
+       atval[0] = 0x8A;
+       atval[1] = 0x02;
+       attrib_db_add(0x0204, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+       /* Thermometer: temperature characteristic format */
+       bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+       atval[0] = 0x0E;
+       atval[1] = 0xFE;
+       att_put_u16(FMT_CELSIUS_UUID, &atval[2]);
+       atval[4] = 0x01;
+       att_put_u16(FMT_OUTSIDE_UUID, &atval[5]);
+       attrib_db_add(0x0205, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 7);
+
+       /* Thermometer: characteristic user description */
+       bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+       len = strlen(desc_out_temp);
+       strncpy((char *) atval, desc_out_temp, len);
+       attrib_db_add(0x0206, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+       /* Thermometer: relative humidity characteristic */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0212, &atval[1]);
+       att_put_u16(RELATIVE_HUMIDITY_UUID, &atval[3]);
+       attrib_db_add(0x0210, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* Thermometer: relative humidity value */
+       bt_uuid16_create(&uuid, RELATIVE_HUMIDITY_UUID);
+       atval[0] = 0x27;
+       attrib_db_add(0x0212, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1);
+
+       /* Thermometer: relative humidity characteristic format */
+       bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+       atval[0] = 0x04;
+       atval[1] = 0x00;
+       att_put_u16(FMT_PERCENT_UUID, &atval[2]);
+       att_put_u16(BLUETOOTH_SIG_UUID, &atval[4]);
+       att_put_u16(FMT_OUTSIDE_UUID, &atval[6]);
+       attrib_db_add(0x0213, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 8);
+
+       /* Thermometer: characteristic user description */
+       bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+       len = strlen(desc_out_hum);
+       strncpy((char *) atval, desc_out_hum, len);
+       attrib_db_add(0x0214, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+       /* Add an SDP record for the above service */
+       handle = attrib_create_sdp(0x0200, "Thermometer");
+       if (handle)
+               sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+       /* Secondary Service: Manufacturer Service */
+       bt_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+       att_put_u16(MANUFACTURER_SVC_UUID, &atval[0]);
+       attrib_db_add(0x0500, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+       /* Manufacturer name characteristic definition */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0502, &atval[1]);
+       att_put_u16(MANUFACTURER_NAME_UUID, &atval[3]);
+       attrib_db_add(0x0501, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* Manufacturer name characteristic value */
+       bt_uuid16_create(&uuid, MANUFACTURER_NAME_UUID);
+       len = strlen(manufacturer_name1);
+       strncpy((char *) atval, manufacturer_name1, len);
+       attrib_db_add(0x0502, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+       /* Manufacturer serial number characteristic */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0504, &atval[1]);
+       att_put_u16(MANUFACTURER_SERIAL_UUID, &atval[3]);
+       attrib_db_add(0x0503, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* Manufacturer serial number characteristic value */
+       bt_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID);
+       len = strlen(serial1);
+       strncpy((char *) atval, serial1, len);
+       attrib_db_add(0x0504, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+       /* Secondary Service: Manufacturer Service */
+       bt_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+       att_put_u16(MANUFACTURER_SVC_UUID, &atval[0]);
+       attrib_db_add(0x0505, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+       /* Manufacturer name characteristic definition */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0507, &atval[1]);
+       att_put_u16(MANUFACTURER_NAME_UUID, &atval[3]);
+       attrib_db_add(0x0506, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* Secondary Service: Vendor Specific Service */
+       bt_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+       att_put_u16(VENDOR_SPECIFIC_SVC_UUID, &atval[0]);
+       attrib_db_add(0x0550, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+       /* Vendor Specific Type characteristic definition */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0568, &atval[1]);
+       att_put_u16(VENDOR_SPECIFIC_TYPE_UUID, &atval[3]);
+       attrib_db_add(0x0560, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* Vendor Specific Type characteristic value */
+       bt_uuid16_create(&uuid, VENDOR_SPECIFIC_TYPE_UUID);
+       atval[0] = 0x56;
+       atval[1] = 0x65;
+       atval[2] = 0x6E;
+       atval[3] = 0x64;
+       atval[4] = 0x6F;
+       atval[5] = 0x72;
+       attrib_db_add(0x0568, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+       /* Manufacturer name attribute */
+       bt_uuid16_create(&uuid, MANUFACTURER_NAME_UUID);
+       len = strlen(manufacturer_name2);
+       strncpy((char *) atval, manufacturer_name2, len);
+       attrib_db_add(0x0507, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+       /* Characteristic: serial number */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0509, &atval[1]);
+       att_put_u16(MANUFACTURER_SERIAL_UUID, &atval[3]);
+       attrib_db_add(0x0508, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* Serial number characteristic value */
+       bt_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID);
+       len = strlen(serial2);
+       strncpy((char *) atval, serial2, len);
+       attrib_db_add(0x0509, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+       /* Weight service: primary service definition */
+       bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+       memcpy(atval, &prim_weight_uuid_btorder, 16);
+       attrib_db_add(0x0680, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 16);
+
+       /* Weight: include */
+       bt_uuid16_create(&uuid, GATT_INCLUDE_UUID);
+       att_put_u16(0x0505, &atval[0]);
+       att_put_u16(0x0509, &atval[2]);
+       att_put_u16(MANUFACTURER_SVC_UUID, &atval[4]);
+       attrib_db_add(0x0681, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+       /* Weight: characteristic */
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(0x0683, &atval[1]);
+       memcpy(&atval[3], &char_weight_uuid_btorder, 16);
+       attrib_db_add(0x0682, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 19);
+
+       /* Weight: characteristic value */
+       bt_uuid128_create(&uuid, char_weight_uuid);
+       atval[0] = 0x82;
+       atval[1] = 0x55;
+       atval[2] = 0x00;
+       atval[3] = 0x00;
+       attrib_db_add(0x0683, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 4);
+
+       /* Weight: characteristic format */
+       bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+       atval[0] = 0x08;
+       atval[1] = 0xFD;
+       att_put_u16(FMT_KILOGRAM_UUID, &atval[2]);
+       att_put_u16(BLUETOOTH_SIG_UUID, &atval[4]);
+       att_put_u16(FMT_HANGING_UUID, &atval[6]);
+       attrib_db_add(0x0684, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 8);
+
+       /* Weight: characteristic user description */
+       bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+       len = strlen(desc_weight);
+       strncpy((char *) atval, desc_weight, len);
+       attrib_db_add(0x0685, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+       /* Add an SDP record for the above service */
+       handle = attrib_create_sdp(0x0680, "Weight Service");
+       if (handle)
+               sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+       return 0;
+}
+
+int server_example_init(void)
+{
+       return register_attributes();
+}
+
+void server_example_exit(void)
+{
+       while (sdp_handles) {
+               uint32_t handle = GPOINTER_TO_UINT(sdp_handles->data);
+
+               attrib_free_sdp(handle);
+               sdp_handles = g_slist_remove(sdp_handles, sdp_handles->data);
+       }
+}
diff --git a/attrib/example.h b/attrib/example.h
new file mode 100644 (file)
index 0000000..a2b07fe
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 server_example_init(void);
+void server_example_exit(void);
diff --git a/attrib/gatt.c b/attrib/gatt.c
new file mode 100644 (file)
index 0000000..32bd4a0
--- /dev/null
@@ -0,0 +1,539 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <stdint.h>
+#include <glib.h>
+#include <bluetooth/uuid.h>
+
+#include "att.h"
+#include "gattrib.h"
+#include "gatt.h"
+
+struct discover_primary {
+       GAttrib *attrib;
+       bt_uuid_t uuid;
+       GSList *primaries;
+       gatt_cb_t cb;
+       void *user_data;
+};
+
+struct discover_char {
+       GAttrib *attrib;
+       bt_uuid_t uuid;
+       uint16_t end;
+       GSList *characteristics;
+       gatt_cb_t cb;
+       void *user_data;
+};
+
+static void discover_primary_free(struct discover_primary *dp)
+{
+       g_slist_free(dp->primaries);
+       g_attrib_unref(dp->attrib);
+       g_free(dp);
+}
+
+static void discover_char_free(struct discover_char *dc)
+{
+       g_slist_foreach(dc->characteristics, (GFunc) g_free, NULL);
+       g_slist_free(dc->characteristics);
+       g_attrib_unref(dc->attrib);
+       g_free(dc);
+}
+
+static guint16 encode_discover_primary(uint16_t start, uint16_t end,
+                               bt_uuid_t *uuid, uint8_t *pdu, size_t len)
+{
+       bt_uuid_t prim;
+       guint16 plen;
+       uint8_t op;
+
+       bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID);
+
+       if (uuid == NULL) {
+               /* Discover all primary services */
+               op = ATT_OP_READ_BY_GROUP_REQ;
+               plen = enc_read_by_grp_req(start, end, &prim, pdu, len);
+       } else {
+               uint16_t u16;
+               uint128_t u128;
+               const void *value;
+               int vlen;
+
+               /* Discover primary service by service UUID */
+               op = ATT_OP_FIND_BY_TYPE_REQ;
+
+               if (uuid->type == BT_UUID16) {
+                       u16 = htobs(uuid->value.u16);
+                       value = &u16;
+                       vlen = sizeof(u16);
+               } else {
+                       htob128(&uuid->value.u128, &u128);
+                       value = &u128;
+                       vlen = sizeof(u128);
+               }
+
+               plen = enc_find_by_type_req(start, end, &prim, value, vlen,
+                                                               pdu, len);
+       }
+
+       return plen;
+}
+
+static void primary_by_uuid_cb(guint8 status, const guint8 *ipdu,
+                                       guint16 iplen, gpointer user_data)
+
+{
+       struct discover_primary *dp = user_data;
+       GSList *ranges, *last;
+       struct att_range *range;
+       uint8_t opdu[ATT_DEFAULT_LE_MTU];
+       guint16 oplen;
+       int err = 0;
+
+       if (status) {
+               err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+               goto done;
+       }
+
+       ranges = dec_find_by_type_resp(ipdu, iplen);
+       if (ranges == NULL)
+               goto done;
+
+       dp->primaries = g_slist_concat(dp->primaries, ranges);
+
+       last = g_slist_last(ranges);
+       range = last->data;
+
+       if (range->end == 0xffff)
+               goto done;
+
+       oplen = encode_discover_primary(range->end + 1, 0xffff, &dp->uuid,
+                                                       opdu, sizeof(opdu));
+
+       if (oplen == 0)
+               goto done;
+
+       g_attrib_send(dp->attrib, 0, opdu[0], opdu, oplen, primary_by_uuid_cb,
+                                                               dp, NULL);
+       return;
+
+done:
+       dp->cb(dp->primaries, err, dp->user_data);
+       discover_primary_free(dp);
+}
+
+static void primary_all_cb(guint8 status, const guint8 *ipdu, guint16 iplen,
+                                                       gpointer user_data)
+{
+       struct discover_primary *dp = user_data;
+       struct att_data_list *list;
+       unsigned int i, err;
+       uint16_t start, end;
+
+       if (status) {
+               err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+               goto done;
+       }
+
+       list = dec_read_by_grp_resp(ipdu, iplen);
+       if (list == NULL) {
+               err = ATT_ECODE_IO;
+               goto done;
+       }
+
+       for (i = 0, end = 0; i < list->num; i++) {
+               const uint8_t *data = list->data[i];
+               struct att_primary *primary;
+               bt_uuid_t uuid;
+
+               start = att_get_u16(&data[0]);
+               end = att_get_u16(&data[2]);
+
+               if (list->len == 6) {
+                       bt_uuid_t uuid16 = att_get_uuid16(&data[4]);
+                       bt_uuid_to_uuid128(&uuid16, &uuid);
+               } else if (list->len == 20) {
+                       uuid = att_get_uuid128(&data[4]);
+               } else {
+                       /* Skipping invalid data */
+                       continue;
+               }
+
+               primary = g_try_new0(struct att_primary, 1);
+               if (!primary) {
+                       err = ATT_ECODE_INSUFF_RESOURCES;
+                       goto done;
+               }
+               primary->start = start;
+               primary->end = end;
+               bt_uuid_to_string(&uuid, primary->uuid, sizeof(primary->uuid));
+               dp->primaries = g_slist_append(dp->primaries, primary);
+       }
+
+       att_data_list_free(list);
+       err = 0;
+
+       if (end != 0xffff) {
+               uint8_t opdu[ATT_DEFAULT_LE_MTU];
+               guint16 oplen = encode_discover_primary(end + 1, 0xffff, NULL,
+                                                       opdu, sizeof(opdu));
+
+               g_attrib_send(dp->attrib, 0, opdu[0], opdu, oplen,
+                                               primary_all_cb, dp, NULL);
+
+               return;
+       }
+
+done:
+       dp->cb(dp->primaries, err, dp->user_data);
+       discover_primary_free(dp);
+}
+
+guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func,
+                                                       gpointer user_data)
+{
+       struct discover_primary *dp;
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       GAttribResultFunc cb;
+       guint16 plen;
+
+       plen = encode_discover_primary(0x0001, 0xffff, uuid, pdu, sizeof(pdu));
+       if (plen == 0)
+               return 0;
+
+       dp = g_try_new0(struct discover_primary, 1);
+       if (dp == NULL)
+               return 0;
+
+       dp->attrib = g_attrib_ref(attrib);
+       dp->cb = func;
+       dp->user_data = user_data;
+
+       if (uuid) {
+               memcpy(&dp->uuid, uuid, sizeof(bt_uuid_t));
+               cb = primary_by_uuid_cb;
+       } else
+               cb = primary_all_cb;
+
+       return g_attrib_send(attrib, 0, pdu[0], pdu, plen, cb, dp, NULL);
+}
+
+static void char_discovered_cb(guint8 status, const guint8 *ipdu, guint16 iplen,
+                                                       gpointer user_data)
+{
+       struct discover_char *dc = user_data;
+       struct att_data_list *list;
+       unsigned int i, err;
+       uint8_t opdu[ATT_DEFAULT_LE_MTU];
+       guint16 oplen;
+       bt_uuid_t uuid;
+       uint16_t last = 0;
+
+       if (status) {
+               err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+               goto done;
+       }
+
+       list = dec_read_by_type_resp(ipdu, iplen);
+       if (list == NULL) {
+               err = ATT_ECODE_IO;
+               goto done;
+       }
+
+       for (i = 0; i < list->num; i++) {
+               uint8_t *value = list->data[i];
+               struct att_char *chars;
+               bt_uuid_t uuid;
+
+               last = att_get_u16(value);
+
+               if (list->len == 7) {
+                       bt_uuid_t uuid16 = att_get_uuid16(&value[5]);
+                       bt_uuid_to_uuid128(&uuid16, &uuid);
+               } else
+                       uuid = att_get_uuid128(&value[5]);
+
+               chars = g_try_new0(struct att_char, 1);
+               if (!chars) {
+                       err = ATT_ECODE_INSUFF_RESOURCES;
+                       goto done;
+               }
+
+               chars->handle = last;
+               chars->properties = value[2];
+               chars->value_handle = att_get_u16(&value[3]);
+               bt_uuid_to_string(&uuid, chars->uuid, sizeof(chars->uuid));
+               dc->characteristics = g_slist_append(dc->characteristics,
+                                                                       chars);
+       }
+
+       att_data_list_free(list);
+       err = 0;
+
+       if (last != 0) {
+               bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+
+               oplen = enc_read_by_type_req(last + 1, dc->end, &uuid, opdu,
+                                                               sizeof(opdu));
+
+               if (oplen == 0)
+                       return;
+
+               g_attrib_send(dc->attrib, 0, opdu[0], opdu, oplen,
+                                               char_discovered_cb, dc, NULL);
+
+               return;
+       }
+
+done:
+       dc->cb(dc->characteristics, err, dc->user_data);
+       discover_char_free(dc);
+}
+
+guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end,
+                                       gatt_cb_t func, gpointer user_data)
+{
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       struct discover_char *dc;
+       guint16 plen;
+       bt_uuid_t uuid;
+
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+
+       plen = enc_read_by_type_req(start, end, &uuid, pdu, sizeof(pdu));
+       if (plen == 0)
+               return 0;
+
+       dc = g_try_new0(struct discover_char, 1);
+       if (dc == NULL)
+               return 0;
+
+       dc->attrib = g_attrib_ref(attrib);
+       dc->cb = func;
+       dc->user_data = user_data;
+       dc->end = end;
+
+       return g_attrib_send(attrib, 0, pdu[0], pdu, plen, char_discovered_cb,
+                                                               dc, NULL);
+}
+
+guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end,
+                                       bt_uuid_t *uuid, GAttribResultFunc func,
+                                       gpointer user_data)
+{
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       guint16 plen;
+
+       plen = enc_read_by_type_req(start, end, uuid, pdu, sizeof(pdu));
+       if (plen == 0)
+               return 0;
+
+       return g_attrib_send(attrib, 0, ATT_OP_READ_BY_TYPE_REQ,
+                                       pdu, plen, func, user_data, NULL);
+}
+
+struct read_long_data {
+       GAttrib *attrib;
+       GAttribResultFunc func;
+       gpointer user_data;
+       guint8 *buffer;
+       guint16 size;
+       guint16 handle;
+       guint id;
+       gint ref;
+};
+
+static void read_long_destroy(gpointer user_data)
+{
+       struct read_long_data *long_read = user_data;
+
+       if (g_atomic_int_dec_and_test(&long_read->ref) == FALSE)
+               return;
+
+       if (long_read->buffer != NULL)
+               g_free(long_read->buffer);
+
+       g_free(long_read);
+}
+
+static void read_blob_helper(guint8 status, const guint8 *rpdu, guint16 rlen,
+                                                       gpointer user_data)
+{
+       struct read_long_data *long_read = user_data;
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       guint8 *tmp;
+       guint16 plen;
+       guint id;
+
+       if (status != 0 || rlen == 1) {
+               status = 0;
+               goto done;
+       }
+
+       tmp = g_try_realloc(long_read->buffer, long_read->size + rlen - 1);
+
+       if (tmp == NULL) {
+               status = ATT_ECODE_INSUFF_RESOURCES;
+               goto done;
+       }
+
+       memcpy(&tmp[long_read->size], &rpdu[1], rlen - 1);
+       long_read->buffer = tmp;
+       long_read->size += rlen - 1;
+
+       if (rlen < ATT_DEFAULT_LE_MTU)
+               goto done;
+
+       plen = enc_read_blob_req(long_read->handle, long_read->size - 1,
+                                                       pdu, sizeof(pdu));
+       id = g_attrib_send(long_read->attrib, long_read->id,
+                               ATT_OP_READ_BLOB_REQ, pdu, plen,
+                               read_blob_helper, long_read, read_long_destroy);
+
+       if (id != 0) {
+               g_atomic_int_inc(&long_read->ref);
+               return;
+       }
+
+       status = ATT_ECODE_IO;
+
+done:
+       long_read->func(status, long_read->buffer, long_read->size,
+                                                       long_read->user_data);
+}
+
+static void read_char_helper(guint8 status, const guint8 *rpdu,
+                                       guint16 rlen, gpointer user_data)
+{
+       struct read_long_data *long_read = user_data;
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       guint16 plen;
+       guint id;
+
+       if (status != 0 || rlen < ATT_DEFAULT_LE_MTU)
+               goto done;
+
+       long_read->buffer = g_malloc(rlen);
+
+       if (long_read->buffer == NULL)
+               goto done;
+
+       memcpy(long_read->buffer, rpdu, rlen);
+       long_read->size = rlen;
+
+       plen = enc_read_blob_req(long_read->handle, rlen - 1, pdu, sizeof(pdu));
+       id = g_attrib_send(long_read->attrib, long_read->id,
+                       ATT_OP_READ_BLOB_REQ, pdu, plen, read_blob_helper,
+                       long_read, read_long_destroy);
+
+       if (id != 0) {
+               g_atomic_int_inc(&long_read->ref);
+               return;
+       }
+
+       status = ATT_ECODE_IO;
+
+done:
+       long_read->func(status, rpdu, rlen, long_read->user_data);
+}
+
+guint gatt_read_char(GAttrib *attrib, uint16_t handle, uint16_t offset,
+                               GAttribResultFunc func, gpointer user_data)
+{
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       guint16 plen;
+       guint id;
+       struct read_long_data *long_read;
+
+       long_read = g_try_new0(struct read_long_data, 1);
+
+       if (long_read == NULL)
+               return 0;
+
+       long_read->attrib = attrib;
+       long_read->func = func;
+       long_read->user_data = user_data;
+       long_read->handle = handle;
+
+       if (offset > 0) {
+               plen = enc_read_blob_req(long_read->handle, offset, pdu,
+                                                               sizeof(pdu));
+               id = g_attrib_send(attrib, 0, ATT_OP_READ_BLOB_REQ, pdu, plen,
+                               read_blob_helper, long_read, read_long_destroy);
+       } else {
+               plen = enc_read_req(handle, pdu, sizeof(pdu));
+               id = g_attrib_send(attrib, 0, ATT_OP_READ_REQ, pdu, plen,
+                               read_char_helper, long_read, read_long_destroy);
+       }
+
+       if (id == 0)
+               g_free(long_read);
+       else {
+               g_atomic_int_inc(&long_read->ref);
+               long_read->id = id;
+       }
+
+       return id;
+}
+
+guint gatt_write_char(GAttrib *attrib, uint16_t handle, uint8_t *value,
+                       int vlen, GAttribResultFunc func, gpointer user_data)
+{
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       guint16 plen;
+
+       if (func)
+               plen = enc_write_req(handle, value, vlen, pdu, sizeof(pdu));
+       else
+               plen = enc_write_cmd(handle, value, vlen, pdu, sizeof(pdu));
+
+       return g_attrib_send(attrib, 0, pdu[0], pdu, plen, func,
+                                                       user_data, NULL);
+}
+
+guint gatt_find_info(GAttrib *attrib, uint16_t start, uint16_t end,
+                               GAttribResultFunc func, gpointer user_data)
+{
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       guint16 plen;
+
+       plen = enc_find_info_req(start, end, pdu, sizeof(pdu));
+       if (plen == 0)
+               return 0;
+
+       return g_attrib_send(attrib, 0, ATT_OP_FIND_INFO_REQ, pdu, plen, func,
+                                                       user_data, NULL);
+}
+
+guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, uint8_t *value, int vlen,
+                               GDestroyNotify notify, gpointer user_data)
+{
+       uint8_t pdu[ATT_DEFAULT_LE_MTU];
+       guint16 plen;
+
+       plen = enc_write_cmd(handle, value, vlen, pdu, sizeof(pdu));
+       return g_attrib_send(attrib, 0, ATT_OP_WRITE_CMD, pdu, plen, NULL,
+                                                       user_data, notify);
+}
diff --git a/attrib/gatt.h b/attrib/gatt.h
new file mode 100644 (file)
index 0000000..730de7e
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 GATT_CID 4
+
+typedef void (*gatt_cb_t) (GSList *l, guint8 status, gpointer user_data);
+
+guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func,
+                                                       gpointer user_data);
+
+guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end,
+                                       gatt_cb_t func, gpointer user_data);
+
+guint gatt_read_char(GAttrib *attrib, uint16_t handle, uint16_t offset,
+                               GAttribResultFunc func, gpointer user_data);
+
+guint gatt_write_char(GAttrib *attrib, uint16_t handle, uint8_t *value,
+                       int vlen, GAttribResultFunc func, gpointer user_data);
+
+guint gatt_find_info(GAttrib *attrib, uint16_t start, uint16_t end,
+                               GAttribResultFunc func, gpointer user_data);
+
+guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, uint8_t *value, int vlen,
+                               GDestroyNotify notify, gpointer user_data);
+
+guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end,
+                               bt_uuid_t *uuid, GAttribResultFunc func,
+                               gpointer user_data);
diff --git a/attrib/gattrib.c b/attrib/gattrib.c
new file mode 100644 (file)
index 0000000..07e56de
--- /dev/null
@@ -0,0 +1,590 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <stdint.h>
+#include <string.h>
+#include <glib.h>
+
+#include <stdio.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+
+#define GATT_TIMEOUT 30
+
+struct _GAttrib {
+       GIOChannel *io;
+       gint refs;
+       guint read_watch;
+       guint write_watch;
+       guint timeout_watch;
+       GQueue *queue;
+       GSList *events;
+       guint next_cmd_id;
+       guint next_evt_id;
+       GDestroyNotify destroy;
+       GAttribDisconnectFunc disconnect;
+       gpointer destroy_user_data;
+       gpointer disc_user_data;
+};
+
+struct command {
+       guint id;
+       guint8 opcode;
+       guint8 *pdu;
+       guint16 len;
+       guint8 expected;
+       gboolean sent;
+       GAttribResultFunc func;
+       gpointer user_data;
+       GDestroyNotify notify;
+};
+
+struct event {
+       guint id;
+       guint8 expected;
+       GAttribNotifyFunc func;
+       gpointer user_data;
+       GDestroyNotify notify;
+};
+
+static guint8 opcode2expected(guint8 opcode)
+{
+       switch (opcode) {
+       case ATT_OP_MTU_REQ:
+               return ATT_OP_MTU_RESP;
+
+       case ATT_OP_FIND_INFO_REQ:
+               return ATT_OP_FIND_INFO_RESP;
+
+       case ATT_OP_FIND_BY_TYPE_REQ:
+               return ATT_OP_FIND_BY_TYPE_RESP;
+
+       case ATT_OP_READ_BY_TYPE_REQ:
+               return ATT_OP_READ_BY_TYPE_RESP;
+
+       case ATT_OP_READ_REQ:
+               return ATT_OP_READ_RESP;
+
+       case ATT_OP_READ_BLOB_REQ:
+               return ATT_OP_READ_BLOB_RESP;
+
+       case ATT_OP_READ_MULTI_REQ:
+               return ATT_OP_READ_MULTI_RESP;
+
+       case ATT_OP_READ_BY_GROUP_REQ:
+               return ATT_OP_READ_BY_GROUP_RESP;
+
+       case ATT_OP_WRITE_REQ:
+               return ATT_OP_WRITE_RESP;
+
+       case ATT_OP_PREP_WRITE_REQ:
+               return ATT_OP_PREP_WRITE_RESP;
+
+       case ATT_OP_EXEC_WRITE_REQ:
+               return ATT_OP_EXEC_WRITE_RESP;
+
+       case ATT_OP_HANDLE_IND:
+               return ATT_OP_HANDLE_CNF;
+       }
+
+       return 0;
+}
+
+static gboolean is_response(guint8 opcode)
+{
+       switch (opcode) {
+       case ATT_OP_ERROR:
+       case ATT_OP_MTU_RESP:
+       case ATT_OP_FIND_INFO_RESP:
+       case ATT_OP_FIND_BY_TYPE_RESP:
+       case ATT_OP_READ_BY_TYPE_RESP:
+       case ATT_OP_READ_RESP:
+       case ATT_OP_READ_BLOB_RESP:
+       case ATT_OP_READ_MULTI_RESP:
+       case ATT_OP_READ_BY_GROUP_RESP:
+       case ATT_OP_WRITE_RESP:
+       case ATT_OP_PREP_WRITE_RESP:
+       case ATT_OP_EXEC_WRITE_RESP:
+       case ATT_OP_HANDLE_CNF:
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+GAttrib *g_attrib_ref(GAttrib *attrib)
+{
+       if (!attrib)
+               return NULL;
+
+       g_atomic_int_inc(&attrib->refs);
+
+       return attrib;
+}
+
+static void command_destroy(struct command *cmd)
+{
+       if (cmd->notify)
+               cmd->notify(cmd->user_data);
+
+       g_free(cmd->pdu);
+       g_free(cmd);
+}
+
+static void event_destroy(struct event *evt)
+{
+       if (evt->notify)
+               evt->notify(evt->user_data);
+
+       g_free(evt);
+}
+
+static void attrib_destroy(GAttrib *attrib)
+{
+       GSList *l;
+       struct command *c;
+
+       while ((c = g_queue_pop_head(attrib->queue)))
+               command_destroy(c);
+
+       g_queue_free(attrib->queue);
+       attrib->queue = NULL;
+
+       for (l = attrib->events; l; l = l->next)
+               event_destroy(l->data);
+
+       g_slist_free(attrib->events);
+       attrib->events = NULL;
+
+       if (attrib->timeout_watch > 0)
+               g_source_remove(attrib->timeout_watch);
+
+       if (attrib->write_watch > 0)
+               g_source_remove(attrib->write_watch);
+
+       if (attrib->read_watch > 0) {
+               g_source_remove(attrib->read_watch);
+               g_io_channel_unref(attrib->io);
+       }
+
+       if (attrib->destroy)
+               attrib->destroy(attrib->destroy_user_data);
+
+       g_free(attrib);
+}
+
+void g_attrib_unref(GAttrib *attrib)
+{
+       if (!attrib)
+               return;
+
+       if (g_atomic_int_dec_and_test(&attrib->refs) == FALSE)
+               return;
+
+       attrib_destroy(attrib);
+}
+
+GIOChannel *g_attrib_get_channel(GAttrib *attrib)
+{
+       if (!attrib)
+               return NULL;
+
+       return attrib->io;
+}
+
+gboolean g_attrib_set_disconnect_function(GAttrib *attrib,
+               GAttribDisconnectFunc disconnect, gpointer user_data)
+{
+       if (attrib == NULL)
+               return FALSE;
+
+       attrib->disconnect = disconnect;
+       attrib->disc_user_data = user_data;
+
+       return TRUE;
+}
+
+gboolean g_attrib_set_destroy_function(GAttrib *attrib,
+               GDestroyNotify destroy, gpointer user_data)
+{
+       if (attrib == NULL)
+               return FALSE;
+
+       attrib->destroy = destroy;
+       attrib->destroy_user_data = user_data;
+
+       return TRUE;
+}
+
+static gboolean disconnect_timeout(gpointer data)
+{
+       struct _GAttrib *attrib = data;
+
+       attrib_destroy(attrib);
+
+       return FALSE;
+}
+
+static gboolean can_write_data(GIOChannel *io, GIOCondition cond,
+                                                               gpointer data)
+{
+       struct _GAttrib *attrib = data;
+       struct command *cmd;
+       GError *gerr = NULL;
+       gsize len;
+       GIOStatus iostat;
+
+       if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+               if (attrib->disconnect)
+                       attrib->disconnect(attrib->disc_user_data);
+
+               return FALSE;
+       }
+
+       cmd = g_queue_peek_head(attrib->queue);
+       if (cmd == NULL)
+               return FALSE;
+
+       iostat = g_io_channel_write_chars(io, (gchar *) cmd->pdu, cmd->len,
+                                                               &len, &gerr);
+       if (iostat != G_IO_STATUS_NORMAL)
+               return FALSE;
+
+       if (cmd->expected == 0) {
+               g_queue_pop_head(attrib->queue);
+               command_destroy(cmd);
+
+               return TRUE;
+       }
+
+       cmd->sent = TRUE;
+
+       if (attrib->timeout_watch == 0)
+               attrib->timeout_watch = g_timeout_add_seconds(GATT_TIMEOUT,
+                                               disconnect_timeout, attrib);
+
+       return FALSE;
+}
+
+static void destroy_sender(gpointer data)
+{
+       struct _GAttrib *attrib = data;
+
+       attrib->write_watch = 0;
+}
+
+static void wake_up_sender(struct _GAttrib *attrib)
+{
+       if (attrib->write_watch == 0)
+               attrib->write_watch = g_io_add_watch_full(attrib->io,
+                       G_PRIORITY_DEFAULT, G_IO_OUT, can_write_data,
+                       attrib, destroy_sender);
+}
+
+static gboolean received_data(GIOChannel *io, GIOCondition cond, gpointer data)
+{
+       struct _GAttrib *attrib = data;
+       struct command *cmd = NULL;
+       GSList *l;
+       uint8_t buf[512], status;
+       gsize len;
+       GIOStatus iostat;
+       gboolean qempty;
+
+       if (attrib->timeout_watch > 0) {
+               g_source_remove(attrib->timeout_watch);
+               attrib->timeout_watch = 0;
+       }
+
+       if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+               attrib->read_watch = 0;
+               if (attrib->disconnect)
+                       attrib->disconnect(attrib->disc_user_data);
+               return FALSE;
+       }
+
+       memset(buf, 0, sizeof(buf));
+
+       iostat = g_io_channel_read_chars(io, (gchar *) buf, sizeof(buf),
+                                                               &len, NULL);
+       if (iostat != G_IO_STATUS_NORMAL) {
+               status = ATT_ECODE_IO;
+               goto done;
+       }
+
+       for (l = attrib->events; l; l = l->next) {
+               struct event *evt = l->data;
+
+               if (evt->expected == buf[0] ||
+                                       evt->expected == GATTRIB_ALL_EVENTS)
+                       evt->func(buf, len, evt->user_data);
+       }
+
+       if (is_response(buf[0]) == FALSE)
+               return TRUE;
+
+       cmd = g_queue_pop_head(attrib->queue);
+       if (cmd == NULL) {
+               /* Keep the watch if we have events to report */
+               return attrib->events != NULL;
+       }
+
+       if (buf[0] == ATT_OP_ERROR) {
+               status = buf[4];
+               goto done;
+       }
+
+       if (cmd->expected != buf[0]) {
+               status = ATT_ECODE_IO;
+               goto done;
+       }
+
+       status = 0;
+
+done:
+       qempty = attrib->queue == NULL || g_queue_is_empty(attrib->queue);
+
+       if (cmd) {
+               if (cmd->func)
+                       cmd->func(status, buf, len, cmd->user_data);
+
+               command_destroy(cmd);
+       }
+
+       if (!qempty)
+               wake_up_sender(attrib);
+
+       return TRUE;
+}
+
+GAttrib *g_attrib_new(GIOChannel *io)
+{
+       struct _GAttrib *attrib;
+
+       g_io_channel_set_encoding(io, NULL, NULL);
+       g_io_channel_set_buffered(io, FALSE);
+
+       attrib = g_try_new0(struct _GAttrib, 1);
+       if (attrib == NULL)
+               return NULL;
+
+       attrib->io = g_io_channel_ref(io);
+       attrib->queue = g_queue_new();
+
+       attrib->read_watch = g_io_add_watch(attrib->io,
+                       G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                       received_data, attrib);
+
+       return g_attrib_ref(attrib);
+}
+
+guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode,
+                       const guint8 *pdu, guint16 len, GAttribResultFunc func,
+                       gpointer user_data, GDestroyNotify notify)
+{
+       struct command *c;
+
+       c = g_try_new0(struct command, 1);
+       if (c == NULL)
+               return 0;
+
+       c->opcode = opcode;
+       c->expected = opcode2expected(opcode);
+       c->pdu = g_malloc(len);
+       memcpy(c->pdu, pdu, len);
+       c->len = len;
+       c->func = func;
+       c->user_data = user_data;
+       c->notify = notify;
+
+       if (id) {
+               c->id = id;
+               g_queue_push_head(attrib->queue, c);
+       } else {
+               c->id = ++attrib->next_cmd_id;
+               g_queue_push_tail(attrib->queue, c);
+       }
+
+       if (g_queue_get_length(attrib->queue) == 1)
+               wake_up_sender(attrib);
+
+       return c->id;
+}
+
+static gint command_cmp_by_id(gconstpointer a, gconstpointer b)
+{
+       const struct command *cmd = a;
+       guint id = GPOINTER_TO_UINT(b);
+
+       return cmd->id - id;
+}
+
+gboolean g_attrib_cancel(GAttrib *attrib, guint id)
+{
+       GList *l;
+       struct command *cmd;
+
+       if (attrib == NULL || attrib->queue == NULL)
+               return FALSE;
+
+       l = g_queue_find_custom(attrib->queue, GUINT_TO_POINTER(id),
+                                                       command_cmp_by_id);
+       if (l == NULL)
+               return FALSE;
+
+       cmd = l->data;
+
+       if (cmd == g_queue_peek_head(attrib->queue) && cmd->sent)
+               cmd->func = NULL;
+       else {
+               g_queue_remove(attrib->queue, cmd);
+               command_destroy(cmd);
+       }
+
+       return TRUE;
+}
+
+gboolean g_attrib_cancel_all(GAttrib *attrib)
+{
+       struct command *c, *head = NULL;
+       gboolean first = TRUE;
+
+       if (attrib == NULL || attrib->queue == NULL)
+               return FALSE;
+
+       while ((c = g_queue_pop_head(attrib->queue))) {
+               if (first && c->sent) {
+                       /* If the command was sent ignore its callback ... */
+                       c->func = NULL;
+                       head = c;
+                       continue;
+               }
+
+               first = FALSE;
+               command_destroy(c);
+       }
+
+       if (head) {
+               /* ... and put it back in the queue */
+               g_queue_push_head(attrib->queue, head);
+       }
+
+       return TRUE;
+}
+
+gboolean g_attrib_set_debug(GAttrib *attrib,
+               GAttribDebugFunc func, gpointer user_data)
+{
+       return TRUE;
+}
+
+guint g_attrib_register(GAttrib *attrib, guint8 opcode,
+                               GAttribNotifyFunc func, gpointer user_data,
+                               GDestroyNotify notify)
+{
+       struct event *event;
+
+       event = g_try_new0(struct event, 1);
+       if (event == NULL)
+               return 0;
+
+       event->expected = opcode;
+       event->func = func;
+       event->user_data = user_data;
+       event->notify = notify;
+       event->id = ++attrib->next_evt_id;
+
+       attrib->events = g_slist_append(attrib->events, event);
+
+       return event->id;
+}
+
+static gint event_cmp_by_id(gconstpointer a, gconstpointer b)
+{
+       const struct event *evt = a;
+       guint id = GPOINTER_TO_UINT(b);
+
+       return evt->id - id;
+}
+
+gboolean g_attrib_is_encrypted(GAttrib *attrib)
+{
+       BtIOSecLevel sec_level;
+
+       if (!bt_io_get(attrib->io, BT_IO_L2CAP, NULL,
+                       BT_IO_OPT_SEC_LEVEL, &sec_level,
+                       BT_IO_OPT_INVALID))
+               return FALSE;
+
+       return sec_level > BT_IO_SEC_LOW;
+}
+
+gboolean g_attrib_unregister(GAttrib *attrib, guint id)
+{
+       struct event *evt;
+       GSList *l;
+
+       l = g_slist_find_custom(attrib->events, GUINT_TO_POINTER(id),
+                                                       event_cmp_by_id);
+       if (l == NULL)
+               return FALSE;
+
+       evt = l->data;
+
+       attrib->events = g_slist_remove(attrib->events, evt);
+
+       if (evt->notify)
+               evt->notify(evt->user_data);
+
+       g_free(evt);
+
+       return TRUE;
+}
+
+gboolean g_attrib_unregister_all(GAttrib *attrib)
+{
+       GSList *l;
+
+       if (attrib->events == NULL)
+               return FALSE;
+
+       for (l = attrib->events; l; l = l->next) {
+               struct event *evt = l->data;
+
+               if (evt->notify)
+                       evt->notify(evt->user_data);
+
+               g_free(evt);
+       }
+
+       g_slist_free(attrib->events);
+       attrib->events = NULL;
+
+       return TRUE;
+}
diff --git a/attrib/gattrib.h b/attrib/gattrib.h
new file mode 100644 (file)
index 0000000..f25208d
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __GATTRIB_H
+#define __GATTRIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GATTRIB_ALL_EVENTS 0xFF
+
+struct _GAttrib;
+typedef struct _GAttrib GAttrib;
+
+typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu,
+                                       guint16 len, gpointer user_data);
+typedef void (*GAttribDisconnectFunc)(gpointer user_data);
+typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data);
+typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len,
+                                                       gpointer user_data);
+
+GAttrib *g_attrib_new(GIOChannel *io);
+GAttrib *g_attrib_ref(GAttrib *attrib);
+void g_attrib_unref(GAttrib *attrib);
+
+GIOChannel *g_attrib_get_channel(GAttrib *attrib);
+
+gboolean g_attrib_set_disconnect_function(GAttrib *attrib,
+               GAttribDisconnectFunc disconnect, gpointer user_data);
+
+gboolean g_attrib_set_destroy_function(GAttrib *attrib,
+               GDestroyNotify destroy, gpointer user_data);
+
+guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode,
+                       const guint8 *pdu, guint16 len, GAttribResultFunc func,
+                       gpointer user_data, GDestroyNotify notify);
+
+gboolean g_attrib_cancel(GAttrib *attrib, guint id);
+gboolean g_attrib_cancel_all(GAttrib *attrib);
+
+gboolean g_attrib_set_debug(GAttrib *attrib,
+               GAttribDebugFunc func, gpointer user_data);
+
+guint g_attrib_register(GAttrib *attrib, guint8 opcode,
+               GAttribNotifyFunc func, gpointer user_data,
+                                       GDestroyNotify notify);
+
+gboolean g_attrib_is_encrypted(GAttrib *attrib);
+
+gboolean g_attrib_unregister(GAttrib *attrib, guint id);
+gboolean g_attrib_unregister_all(GAttrib *attrib);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/attrib/gatttool.c b/attrib/gatttool.c
new file mode 100644 (file)
index 0000000..729e18d
--- /dev/null
@@ -0,0 +1,633 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/uuid.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "gatttool.h"
+
+static gchar *opt_src = NULL;
+static gchar *opt_dst = NULL;
+static gchar *opt_value = NULL;
+static gchar *opt_sec_level = NULL;
+static bt_uuid_t *opt_uuid = NULL;
+static int opt_start = 0x0001;
+static int opt_end = 0xffff;
+static int opt_handle = -1;
+static int opt_mtu = 0;
+static int opt_psm = 0;
+static int opt_offset = 0;
+static gboolean opt_primary = FALSE;
+static gboolean opt_characteristics = FALSE;
+static gboolean opt_char_read = FALSE;
+static gboolean opt_listen = FALSE;
+static gboolean opt_char_desc = FALSE;
+static gboolean opt_char_write = FALSE;
+static gboolean opt_char_write_req = FALSE;
+static gboolean opt_interactive = FALSE;
+static GMainLoop *event_loop;
+static gboolean got_error = FALSE;
+
+struct characteristic_data {
+       GAttrib *attrib;
+       uint16_t start;
+       uint16_t end;
+};
+
+static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+       if (err) {
+               g_printerr("%s\n", err->message);
+               got_error = TRUE;
+               g_main_loop_quit(event_loop);
+       }
+}
+
+static void primary_all_cb(GSList *services, guint8 status, gpointer user_data)
+{
+       GSList *l;
+
+       if (status) {
+               g_printerr("Discover all primary services failed: %s\n",
+                                                       att_ecode2str(status));
+               goto done;
+       }
+
+       for (l = services; l; l = l->next) {
+               struct att_primary *prim = l->data;
+               g_print("attr handle = 0x%04x, end grp handle = 0x%04x "
+                       "uuid: %s\n", prim->start, prim->end, prim->uuid);
+       }
+
+done:
+       g_main_loop_quit(event_loop);
+}
+
+static void primary_by_uuid_cb(GSList *ranges, guint8 status,
+                                                       gpointer user_data)
+{
+       GSList *l;
+
+       if (status != 0) {
+               g_printerr("Discover primary services by UUID failed: %s\n",
+                                                       att_ecode2str(status));
+               goto done;
+       }
+
+       for (l = ranges; l; l = l->next) {
+               struct att_range *range = l->data;
+               g_print("Starting handle: %04x Ending handle: %04x\n",
+                                               range->start, range->end);
+       }
+
+done:
+       g_main_loop_quit(event_loop);
+}
+
+static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+       GAttrib *attrib = user_data;
+       uint8_t opdu[ATT_MAX_MTU];
+       uint16_t handle, i, olen = 0;
+
+       handle = att_get_u16(&pdu[1]);
+
+       switch (pdu[0]) {
+       case ATT_OP_HANDLE_NOTIFY:
+               g_print("Notification handle = 0x%04x value: ", handle);
+               break;
+       case ATT_OP_HANDLE_IND:
+               g_print("Indication   handle = 0x%04x value: ", handle);
+               break;
+       default:
+               g_print("Invalid opcode\n");
+               return;
+       }
+
+       for (i = 3; i < len; i++)
+               g_print("%02x ", pdu[i]);
+
+       g_print("\n");
+
+       if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
+               return;
+
+       olen = enc_confirmation(opdu, sizeof(opdu));
+
+       if (olen > 0)
+               g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL);
+}
+
+static gboolean listen_start(gpointer user_data)
+{
+       GAttrib *attrib = user_data;
+
+       g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, events_handler,
+                                                       attrib, NULL);
+       g_attrib_register(attrib, ATT_OP_HANDLE_IND, events_handler,
+                                                       attrib, NULL);
+
+       return FALSE;
+}
+
+static gboolean primary(gpointer user_data)
+{
+       GAttrib *attrib = user_data;
+
+       if (opt_uuid)
+               gatt_discover_primary(attrib, opt_uuid, primary_by_uuid_cb,
+                                                                       NULL);
+       else
+               gatt_discover_primary(attrib, NULL, primary_all_cb, NULL);
+
+       return FALSE;
+}
+
+static void char_discovered_cb(GSList *characteristics, guint8 status,
+                                                       gpointer user_data)
+{
+       GSList *l;
+
+       if (status) {
+               g_printerr("Discover all characteristics failed: %s\n",
+                                                       att_ecode2str(status));
+               goto done;
+       }
+
+       for (l = characteristics; l; l = l->next) {
+               struct att_char *chars = l->data;
+
+               g_print("handle = 0x%04x, char properties = 0x%02x, char value "
+                       "handle = 0x%04x, uuid = %s\n", chars->handle,
+                       chars->properties, chars->value_handle, chars->uuid);
+       }
+
+done:
+       g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics(gpointer user_data)
+{
+       GAttrib *attrib = user_data;
+
+       gatt_discover_char(attrib, opt_start, opt_end, char_discovered_cb, NULL);
+
+       return FALSE;
+}
+
+static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                                       gpointer user_data)
+{
+       uint8_t value[ATT_MAX_MTU];
+       int i, vlen;
+
+       if (status != 0) {
+               g_printerr("Characteristic value/descriptor read failed: %s\n",
+                                                       att_ecode2str(status));
+               goto done;
+       }
+       if (!dec_read_resp(pdu, plen, value, &vlen)) {
+               g_printerr("Protocol error\n");
+               goto done;
+       }
+       g_print("Characteristic value/descriptor: ");
+       for (i = 0; i < vlen; i++)
+               g_print("%02x ", value[i]);
+       g_print("\n");
+
+done:
+       if (opt_listen == FALSE)
+               g_main_loop_quit(event_loop);
+}
+
+static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu,
+                                       guint16 plen, gpointer user_data)
+{
+       struct characteristic_data *char_data = user_data;
+       struct att_data_list *list;
+       int i;
+
+       if (status == ATT_ECODE_ATTR_NOT_FOUND &&
+                                       char_data->start != opt_start)
+               goto done;
+
+       if (status != 0) {
+               g_printerr("Read characteristics by UUID failed: %s\n",
+                                                       att_ecode2str(status));
+               goto done;
+       }
+
+       list = dec_read_by_type_resp(pdu, plen);
+       if (list == NULL)
+               goto done;
+
+       for (i = 0; i < list->num; i++) {
+               uint8_t *value = list->data[i];
+               int j;
+
+               char_data->start = att_get_u16(value) + 1;
+
+               g_print("handle: 0x%04x \t value: ", att_get_u16(value));
+               value += 2;
+               for (j = 0; j < list->len - 2; j++, value++)
+                       g_print("%02x ", *value);
+               g_print("\n");
+       }
+
+       att_data_list_free(list);
+
+       gatt_read_char_by_uuid(char_data->attrib, char_data->start,
+                                       char_data->end, opt_uuid,
+                                       char_read_by_uuid_cb,
+                                       char_data);
+
+       return;
+done:
+       g_free(char_data);
+       g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_read(gpointer user_data)
+{
+       GAttrib *attrib = user_data;
+
+       if (opt_uuid != NULL) {
+               struct characteristic_data *char_data;
+
+               char_data = g_new(struct characteristic_data, 1);
+               char_data->attrib = attrib;
+               char_data->start = opt_start;
+               char_data->end = opt_end;
+
+               gatt_read_char_by_uuid(attrib, opt_start, opt_end, opt_uuid,
+                                               char_read_by_uuid_cb, char_data);
+
+               return FALSE;
+       }
+
+       if (opt_handle <= 0) {
+               g_printerr("A valid handle is required\n");
+               g_main_loop_quit(event_loop);
+               return FALSE;
+       }
+
+       gatt_read_char(attrib, opt_handle, opt_offset, char_read_cb, attrib);
+
+       return FALSE;
+}
+
+static void mainloop_quit(gpointer user_data)
+{
+       uint8_t *value = user_data;
+
+       g_free(value);
+       g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_write(gpointer user_data)
+{
+       GAttrib *attrib = user_data;
+       uint8_t *value;
+       size_t len;
+
+       if (opt_handle <= 0) {
+               g_printerr("A valid handle is required\n");
+               goto error;
+       }
+
+       if (opt_value == NULL || opt_value[0] == '\0') {
+               g_printerr("A value is required\n");
+               goto error;
+       }
+
+       len = gatt_attr_data_from_string(opt_value, &value);
+       if (len == 0) {
+               g_printerr("Invalid value\n");
+               goto error;
+       }
+
+       gatt_write_cmd(attrib, opt_handle, value, len, mainloop_quit, value);
+
+       return FALSE;
+
+error:
+       g_main_loop_quit(event_loop);
+       return FALSE;
+}
+
+static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                                       gpointer user_data)
+{
+       if (status != 0) {
+               g_printerr("Characteristic Write Request failed: "
+                                               "%s\n", att_ecode2str(status));
+               goto done;
+       }
+
+       if (!dec_write_resp(pdu, plen)) {
+               g_printerr("Protocol error\n");
+               goto done;
+       }
+
+       g_print("Characteristic value was written sucessfully\n");
+
+done:
+       if (opt_listen == FALSE)
+               g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_write_req(gpointer user_data)
+{
+       GAttrib *attrib = user_data;
+       uint8_t *value;
+       size_t len;
+
+       if (opt_handle <= 0) {
+               g_printerr("A valid handle is required\n");
+               goto error;
+       }
+
+       if (opt_value == NULL || opt_value[0] == '\0') {
+               g_printerr("A value is required\n");
+               goto error;
+       }
+
+       len = gatt_attr_data_from_string(opt_value, &value);
+       if (len == 0) {
+               g_printerr("Invalid value\n");
+               goto error;
+       }
+
+       gatt_write_char(attrib, opt_handle, value, len, char_write_req_cb,
+                                                                       NULL);
+
+       return FALSE;
+
+error:
+       g_main_loop_quit(event_loop);
+       return FALSE;
+}
+
+static void char_desc_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                                       gpointer user_data)
+{
+       struct att_data_list *list;
+       guint8 format;
+       int i;
+
+       if (status != 0) {
+               g_printerr("Discover all characteristic descriptors failed: "
+                                               "%s\n", att_ecode2str(status));
+               goto done;
+       }
+
+       list = dec_find_info_resp(pdu, plen, &format);
+       if (list == NULL)
+               goto done;
+
+       for (i = 0; i < list->num; i++) {
+               char uuidstr[MAX_LEN_UUID_STR];
+               uint16_t handle;
+               uint8_t *value;
+               bt_uuid_t uuid;
+
+               value = list->data[i];
+               handle = att_get_u16(value);
+
+               if (format == 0x01)
+                       uuid = att_get_uuid16(&value[2]);
+               else
+                       uuid = att_get_uuid128(&value[2]);
+
+               bt_uuid_to_string(&uuid, uuidstr, MAX_LEN_UUID_STR);
+               g_print("handle = 0x%04x, uuid = %s\n", handle, uuidstr);
+       }
+
+       att_data_list_free(list);
+
+done:
+       if (opt_listen == FALSE)
+               g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_desc(gpointer user_data)
+{
+       GAttrib *attrib = user_data;
+
+       gatt_find_info(attrib, opt_start, opt_end, char_desc_cb, NULL);
+
+       return FALSE;
+}
+
+static gboolean parse_uuid(const char *key, const char *value,
+                               gpointer user_data, GError **error)
+{
+       if (!value)
+               return FALSE;
+
+       opt_uuid = g_try_malloc(sizeof(bt_uuid_t));
+       if (opt_uuid == NULL)
+               return FALSE;
+
+       if (bt_string_to_uuid(opt_uuid, value) < 0)
+               return FALSE;
+
+       return TRUE;
+}
+
+static GOptionEntry primary_char_options[] = {
+       { "start", 's' , 0, G_OPTION_ARG_INT, &opt_start,
+               "Starting handle(optional)", "0x0001" },
+       { "end", 'e' , 0, G_OPTION_ARG_INT, &opt_end,
+               "Ending handle(optional)", "0xffff" },
+       { "uuid", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
+               parse_uuid, "UUID16 or UUID128(optional)", "0x1801"},
+       { NULL },
+};
+
+static GOptionEntry char_rw_options[] = {
+       { "handle", 'a' , 0, G_OPTION_ARG_INT, &opt_handle,
+               "Read/Write characteristic by handle(required)", "0x0001" },
+       { "value", 'n' , 0, G_OPTION_ARG_STRING, &opt_value,
+               "Write characteristic value (required for write operation)",
+               "0x0001" },
+       { "offset", 'o', 0, G_OPTION_ARG_INT, &opt_offset,
+               "Offset to long read characteristic by handle", "N"},
+       {NULL},
+};
+
+static GOptionEntry gatt_options[] = {
+       { "primary", 0, 0, G_OPTION_ARG_NONE, &opt_primary,
+               "Primary Service Discovery", NULL },
+       { "characteristics", 0, 0, G_OPTION_ARG_NONE, &opt_characteristics,
+               "Characteristics Discovery", NULL },
+       { "char-read", 0, 0, G_OPTION_ARG_NONE, &opt_char_read,
+               "Characteristics Value/Descriptor Read", NULL },
+       { "char-write", 0, 0, G_OPTION_ARG_NONE, &opt_char_write,
+               "Characteristics Value Write Without Response (Write Command)",
+               NULL },
+       { "char-write-req", 0, 0, G_OPTION_ARG_NONE, &opt_char_write_req,
+               "Characteristics Value Write (Write Request)", NULL },
+       { "char-desc", 0, 0, G_OPTION_ARG_NONE, &opt_char_desc,
+               "Characteristics Descriptor Discovery", NULL },
+       { "listen", 0, 0, G_OPTION_ARG_NONE, &opt_listen,
+               "Listen for notifications and indications", NULL },
+       { "interactive", 'I', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+               &opt_interactive, "Use interactive mode", NULL },
+       { NULL },
+};
+
+static GOptionEntry options[] = {
+       { "adapter", 'i', 0, G_OPTION_ARG_STRING, &opt_src,
+               "Specify local adapter interface", "hciX" },
+       { "device", 'b', 0, G_OPTION_ARG_STRING, &opt_dst,
+               "Specify remote Bluetooth address", "MAC" },
+       { "mtu", 'm', 0, G_OPTION_ARG_INT, &opt_mtu,
+               "Specify the MTU size", "MTU" },
+       { "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm,
+               "Specify the PSM for GATT/ATT over BR/EDR", "PSM" },
+       { "sec-level", 'l', 0, G_OPTION_ARG_STRING, &opt_sec_level,
+               "Set security level. Default: low", "[low | medium | high]"},
+       { NULL },
+};
+
+int main(int argc, char *argv[])
+{
+       GOptionContext *context;
+       GOptionGroup *gatt_group, *params_group, *char_rw_group;
+       GError *gerr = NULL;
+       GAttrib *attrib;
+       GIOChannel *chan;
+       GSourceFunc callback;
+
+       opt_sec_level = g_strdup("low");
+
+       context = g_option_context_new(NULL);
+       g_option_context_add_main_entries(context, options, NULL);
+
+       /* GATT commands */
+       gatt_group = g_option_group_new("gatt", "GATT commands",
+                                       "Show all GATT commands", NULL, NULL);
+       g_option_context_add_group(context, gatt_group);
+       g_option_group_add_entries(gatt_group, gatt_options);
+
+       /* Primary Services and Characteristics arguments */
+       params_group = g_option_group_new("params",
+                       "Primary Services/Characteristics arguments",
+                       "Show all Primary Services/Characteristics arguments",
+                       NULL, NULL);
+       g_option_context_add_group(context, params_group);
+       g_option_group_add_entries(params_group, primary_char_options);
+
+       /* Characteristics value/descriptor read/write arguments */
+       char_rw_group = g_option_group_new("char-read-write",
+               "Characteristics Value/Descriptor Read/Write arguments",
+               "Show all Characteristics Value/Descriptor Read/Write "
+               "arguments",
+               NULL, NULL);
+       g_option_context_add_group(context, char_rw_group);
+       g_option_group_add_entries(char_rw_group, char_rw_options);
+
+       if (g_option_context_parse(context, &argc, &argv, &gerr) == FALSE) {
+               g_printerr("%s\n", gerr->message);
+               g_error_free(gerr);
+       }
+
+       if (opt_interactive) {
+               interactive(opt_src, opt_dst, opt_psm);
+               goto done;
+       }
+
+       if (opt_primary)
+               callback = primary;
+       else if (opt_characteristics)
+               callback = characteristics;
+       else if (opt_char_read)
+               callback = characteristics_read;
+       else if (opt_char_write)
+               callback = characteristics_write;
+       else if (opt_char_write_req)
+               callback = characteristics_write_req;
+       else if (opt_char_desc)
+               callback = characteristics_desc;
+       else {
+               gchar *help = g_option_context_get_help(context, TRUE, NULL);
+               g_print("%s\n", help);
+               g_free(help);
+               got_error = TRUE;
+               goto done;
+       }
+
+       chan = gatt_connect(opt_src, opt_dst, opt_sec_level,
+                                       opt_psm, opt_mtu, connect_cb);
+       if (chan == NULL) {
+               got_error = TRUE;
+               goto done;
+       }
+
+       attrib = g_attrib_new(chan);
+       g_io_channel_unref(chan);
+
+       event_loop = g_main_loop_new(NULL, FALSE);
+
+       if (opt_listen)
+               g_idle_add(listen_start, attrib);
+
+       g_idle_add(callback, attrib);
+
+       g_main_loop_run(event_loop);
+
+       g_attrib_unregister_all(attrib);
+
+       g_main_loop_unref(event_loop);
+
+       g_attrib_unref(attrib);
+
+done:
+       g_option_context_free(context);
+       g_free(opt_src);
+       g_free(opt_dst);
+       g_free(opt_uuid);
+       g_free(opt_sec_level);
+
+       if (got_error)
+               exit(EXIT_FAILURE);
+       else
+               exit(EXIT_SUCCESS);
+}
diff --git a/attrib/gatttool.h b/attrib/gatttool.h
new file mode 100644 (file)
index 0000000..89ac282
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  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
+ *
+ */
+
+int interactive(const gchar *src, const gchar *dst, gboolean le);
+GIOChannel *gatt_connect(const gchar *src, const gchar *dst,
+                       const gchar *sec_level, int psm, int mtu,
+                       BtIOConnect connect_cb);
+size_t gatt_attr_data_from_string(const char *str, uint8_t **data);
diff --git a/attrib/interactive.c b/attrib/interactive.c
new file mode 100644 (file)
index 0000000..b32e9e7
--- /dev/null
@@ -0,0 +1,768 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  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 <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <glib.h>
+
+#include <bluetooth/uuid.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "gatttool.h"
+
+static GIOChannel *iochannel = NULL;
+static GAttrib *attrib = NULL;
+static GMainLoop *event_loop;
+static GString *prompt;
+
+static gchar *opt_src = NULL;
+static gchar *opt_dst = NULL;
+static gchar *opt_sec_level = NULL;
+static int opt_psm = 0;
+static int opt_mtu = 0;
+
+struct characteristic_data {
+       uint16_t orig_start;
+       uint16_t start;
+       uint16_t end;
+       bt_uuid_t uuid;
+};
+
+static void cmd_help(int argcp, char **argvp);
+
+enum state {
+       STATE_DISCONNECTED,
+       STATE_CONNECTING,
+       STATE_CONNECTED
+} conn_state;
+
+static char *get_prompt(void)
+{
+       if (conn_state == STATE_CONNECTING) {
+               g_string_assign(prompt, "Connecting... ");
+               return prompt->str;
+       }
+
+       if (conn_state == STATE_CONNECTED)
+               g_string_assign(prompt, "[CON]");
+       else
+               g_string_assign(prompt, "[   ]");
+
+       if (opt_dst)
+               g_string_append_printf(prompt, "[%17s]", opt_dst);
+       else
+               g_string_append_printf(prompt, "[%17s]", "");
+
+       if (opt_psm)
+               g_string_append(prompt, "[BR]");
+       else
+               g_string_append(prompt, "[LE]");
+
+       g_string_append(prompt, "> ");
+
+       return prompt->str;
+}
+
+
+static void set_state(enum state st)
+{
+       conn_state = st;
+       rl_set_prompt(get_prompt());
+       rl_redisplay();
+}
+
+static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+       uint8_t opdu[ATT_MAX_MTU];
+       uint16_t handle, i, olen;
+
+       handle = att_get_u16(&pdu[1]);
+
+       printf("\n");
+       switch (pdu[0]) {
+       case ATT_OP_HANDLE_NOTIFY:
+               printf("Notification handle = 0x%04x value: ", handle);
+               break;
+       case ATT_OP_HANDLE_IND:
+               printf("Indication   handle = 0x%04x value: ", handle);
+               break;
+       default:
+               printf("Invalid opcode\n");
+               return;
+       }
+
+       for (i = 3; i < len; i++)
+               printf("%02x ", pdu[i]);
+
+       printf("\n");
+       rl_forced_update_display();
+
+       if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
+               return;
+
+       olen = enc_confirmation(opdu, sizeof(opdu));
+
+       if (olen > 0)
+               g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL);
+}
+
+static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+       if (err) {
+               printf("connect error: %s\n", err->message);
+               set_state(STATE_DISCONNECTED);
+               return;
+       }
+
+       attrib = g_attrib_new(iochannel);
+       g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, events_handler,
+                                                       attrib, NULL);
+       g_attrib_register(attrib, ATT_OP_HANDLE_IND, events_handler,
+                                                       attrib, NULL);
+       set_state(STATE_CONNECTED);
+}
+
+static void primary_all_cb(GSList *services, guint8 status, gpointer user_data)
+{
+       GSList *l;
+
+       if (status) {
+               printf("Discover all primary services failed: %s\n",
+                                                       att_ecode2str(status));
+               return;
+       }
+
+       printf("\n");
+       for (l = services; l; l = l->next) {
+               struct att_primary *prim = l->data;
+               printf("attr handle: 0x%04x, end grp handle: 0x%04x "
+                       "uuid: %s\n", prim->start, prim->end, prim->uuid);
+       }
+
+       rl_forced_update_display();
+}
+
+static void primary_by_uuid_cb(GSList *ranges, guint8 status,
+                                                       gpointer user_data)
+{
+       GSList *l;
+
+       if (status) {
+               printf("Discover primary services by UUID failed: %s\n",
+                                                       att_ecode2str(status));
+               return;
+       }
+
+       printf("\n");
+       for (l = ranges; l; l = l->next) {
+               struct att_range *range = l->data;
+               g_print("Starting handle: 0x%04x Ending handle: 0x%04x\n",
+                                               range->start, range->end);
+       }
+
+       rl_forced_update_display();
+}
+
+static void char_cb(GSList *characteristics, guint8 status, gpointer user_data)
+{
+       GSList *l;
+
+       if (status) {
+               printf("Discover all characteristics failed: %s\n",
+                                                       att_ecode2str(status));
+               return;
+       }
+
+       printf("\n");
+       for (l = characteristics; l; l = l->next) {
+               struct att_char *chars = l->data;
+
+               printf("handle: 0x%04x, char properties: 0x%02x, char value "
+                               "handle: 0x%04x, uuid: %s\n", chars->handle,
+                               chars->properties, chars->value_handle,
+                               chars->uuid);
+       }
+
+       rl_forced_update_display();
+}
+
+static void char_desc_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                                       gpointer user_data)
+{
+       struct att_data_list *list;
+       guint8 format;
+       int i;
+
+       if (status != 0) {
+               printf("Discover all characteristic descriptors failed: "
+                                               "%s\n", att_ecode2str(status));
+               return;
+       }
+
+       list = dec_find_info_resp(pdu, plen, &format);
+       if (list == NULL)
+               return;
+
+       printf("\n");
+       for (i = 0; i < list->num; i++) {
+               char uuidstr[MAX_LEN_UUID_STR];
+               uint16_t handle;
+               uint8_t *value;
+               bt_uuid_t uuid;
+
+               value = list->data[i];
+               handle = att_get_u16(value);
+
+               if (format == 0x01)
+                       uuid = att_get_uuid16(&value[2]);
+               else
+                       uuid = att_get_uuid128(&value[2]);
+
+               bt_uuid_to_string(&uuid, uuidstr, MAX_LEN_UUID_STR);
+               printf("handle: 0x%04x, uuid: %s\n", handle, uuidstr);
+       }
+
+       att_data_list_free(list);
+
+       rl_forced_update_display();
+}
+
+static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                                       gpointer user_data)
+{
+       uint8_t value[ATT_MAX_MTU];
+       int i, vlen;
+
+       if (status != 0) {
+               printf("Characteristic value/descriptor read failed: %s\n",
+                                                       att_ecode2str(status));
+               return;
+       }
+
+       if (!dec_read_resp(pdu, plen, value, &vlen)) {
+               printf("Protocol error\n");
+               return;
+       }
+
+       printf("\nCharacteristic value/descriptor: ");
+       for (i = 0; i < vlen; i++)
+               printf("%02x ", value[i]);
+       printf("\n");
+
+       rl_forced_update_display();
+}
+
+static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu,
+                                       guint16 plen, gpointer user_data)
+{
+       struct characteristic_data *char_data = user_data;
+       struct att_data_list *list;
+       int i;
+
+       if (status == ATT_ECODE_ATTR_NOT_FOUND &&
+                               char_data->start != char_data->orig_start)
+               goto done;
+
+       if (status != 0) {
+               printf("Read characteristics by UUID failed: %s\n",
+                                                       att_ecode2str(status));
+               goto done;
+       }
+
+       list = dec_read_by_type_resp(pdu, plen);
+       if (list == NULL)
+               goto done;
+
+       for (i = 0; i < list->num; i++) {
+               uint8_t *value = list->data[i];
+               int j;
+
+               char_data->start = att_get_u16(value) + 1;
+
+               printf("\nhandle: 0x%04x \t value: ", att_get_u16(value));
+               value += 2;
+               for (j = 0; j < list->len - 2; j++, value++)
+                       printf("%02x ", *value);
+               printf("\n");
+       }
+
+       att_data_list_free(list);
+
+       gatt_read_char_by_uuid(attrib, char_data->start, char_data->end,
+                                       &char_data->uuid, char_read_by_uuid_cb,
+                                       char_data);
+
+       rl_forced_update_display();
+
+       return;
+
+done:
+       g_free(char_data);
+}
+
+static void cmd_exit(int argcp, char **argvp)
+{
+       rl_callback_handler_remove();
+       g_main_loop_quit(event_loop);
+}
+
+static void cmd_connect(int argcp, char **argvp)
+{
+       if (conn_state != STATE_DISCONNECTED)
+               return;
+
+       if (argcp > 1) {
+               g_free(opt_dst);
+               opt_dst = g_strdup(argvp[1]);
+       }
+
+       if (opt_dst == NULL) {
+               printf("Remote Bluetooth address required\n");
+               return;
+       }
+
+       set_state(STATE_CONNECTING);
+       iochannel = gatt_connect(opt_src, opt_dst, opt_sec_level, opt_psm,
+                                               opt_mtu, connect_cb);
+       if (iochannel == NULL)
+               set_state(STATE_DISCONNECTED);
+}
+
+static void cmd_disconnect(int argcp, char **argvp)
+{
+       if (conn_state == STATE_DISCONNECTED)
+               return;
+
+       g_attrib_unref(attrib);
+       attrib = NULL;
+
+       g_io_channel_shutdown(iochannel, FALSE, NULL);
+       g_io_channel_unref(iochannel);
+       iochannel = NULL;
+
+       set_state(STATE_DISCONNECTED);
+}
+
+static void cmd_primary(int argcp, char **argvp)
+{
+       bt_uuid_t uuid;
+
+       if (conn_state != STATE_CONNECTED) {
+               printf("Command failed: disconnected\n");
+               return;
+       }
+
+       if (argcp == 1) {
+               gatt_discover_primary(attrib, NULL, primary_all_cb, NULL);
+               return;
+       }
+
+       if (bt_string_to_uuid(&uuid, argvp[1]) < 0) {
+               printf("Invalid UUID\n");
+               return;
+       }
+
+       gatt_discover_primary(attrib, &uuid, primary_by_uuid_cb, NULL);
+}
+
+static int strtohandle(const char *src)
+{
+       char *e;
+       int dst;
+
+       errno = 0;
+       dst = strtoll(src, &e, 16);
+       if (errno != 0 || *e != '\0')
+               return -EINVAL;
+
+       return dst;
+}
+
+static void cmd_char(int argcp, char **argvp)
+{
+       int start = 0x0001;
+       int end = 0xffff;
+
+       if (conn_state != STATE_CONNECTED) {
+               printf("Command failed: disconnected\n");
+               return;
+       }
+
+       if (argcp > 1) {
+               start = strtohandle(argvp[1]);
+               if (start < 0) {
+                       printf("Invalid start handle: %s\n", argvp[1]);
+                       return;
+               }
+       }
+
+       if (argcp > 2) {
+               end = strtohandle(argvp[2]);
+               if (end < 0) {
+                       printf("Invalid end handle: %s\n", argvp[2]);
+                       return;
+               }
+       }
+
+       gatt_discover_char(attrib, start, end, char_cb, NULL);
+}
+
+static void cmd_char_desc(int argcp, char **argvp)
+{
+       int start = 0x0001;
+       int end = 0xffff;
+
+       if (conn_state != STATE_CONNECTED) {
+               printf("Command failed: disconnected\n");
+               return;
+       }
+
+       if (argcp > 1) {
+               start = strtohandle(argvp[1]);
+               if (start < 0) {
+                       printf("Invalid start handle: %s\n", argvp[1]);
+                       return;
+               }
+       }
+
+       if (argcp > 2) {
+               end = strtohandle(argvp[2]);
+               if (end < 0) {
+                       printf("Invalid end handle: %s\n", argvp[2]);
+                       return;
+               }
+       }
+
+       gatt_find_info(attrib, start, end, char_desc_cb, NULL);
+}
+
+static void cmd_read_hnd(int argcp, char **argvp)
+{
+       int handle;
+       int offset = 0;
+
+       if (conn_state != STATE_CONNECTED) {
+               printf("Command failed: disconnected\n");
+               return;
+       }
+
+       if (argcp < 2) {
+               printf("Missing argument: handle\n");
+               return;
+       }
+
+       handle = strtohandle(argvp[1]);
+       if (handle < 0) {
+               printf("Invalid handle: %s\n", argvp[1]);
+               return;
+       }
+
+       if (argcp > 2) {
+               char *e;
+
+               errno = 0;
+               offset = strtol(argvp[2], &e, 0);
+               if (errno != 0 || *e != '\0') {
+                       printf("Invalid offset: %s\n", argvp[2]);
+                       return;
+               }
+       }
+
+       gatt_read_char(attrib, handle, offset, char_read_cb, attrib);
+}
+
+static void cmd_read_uuid(int argcp, char **argvp)
+{
+       struct characteristic_data *char_data;
+       int start = 0x0001;
+       int end = 0xffff;
+       bt_uuid_t uuid;
+
+       if (conn_state != STATE_CONNECTED) {
+               printf("Command failed: disconnected\n");
+               return;
+       }
+
+       if (argcp < 2) {
+               printf("Missing argument: UUID\n");
+               return;
+       }
+
+       if (bt_string_to_uuid(&uuid, argvp[1]) < 0) {
+               printf("Invalid UUID\n");
+               return;
+       }
+
+       if (argcp > 2) {
+               start = strtohandle(argvp[2]);
+               if (start < 0) {
+                       printf("Invalid start handle: %s\n", argvp[1]);
+                       return;
+               }
+       }
+
+       if (argcp > 3) {
+               end = strtohandle(argvp[3]);
+               if (end < 0) {
+                       printf("Invalid end handle: %s\n", argvp[2]);
+                       return;
+               }
+       }
+
+       char_data = g_new(struct characteristic_data, 1);
+       char_data->orig_start = start;
+       char_data->start = start;
+       char_data->end = end;
+       char_data->uuid = uuid;
+
+       gatt_read_char_by_uuid(attrib, start, end, &char_data->uuid,
+                                       char_read_by_uuid_cb, char_data);
+}
+
+static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                                       gpointer user_data)
+{
+       if (status != 0) {
+               printf("Characteristic Write Request failed: "
+                                               "%s\n", att_ecode2str(status));
+               return;
+       }
+
+       if (!dec_write_resp(pdu, plen)) {
+               printf("Protocol error\n");
+               return;
+       }
+
+       printf("Characteristic value was written successfully\n");
+}
+
+static void cmd_char_write(int argcp, char **argvp)
+{
+       uint8_t *value;
+       size_t plen;
+       int handle;
+
+       if (conn_state != STATE_CONNECTED) {
+               printf("Command failed: disconnected\n");
+               return;
+       }
+
+       if (argcp < 3) {
+               printf("Usage: %s <handle> <new value>\n", argvp[0]);
+               return;
+       }
+
+       handle = strtoll(argvp[1], NULL, 16);
+       if (errno != 0 || handle <= 0) {
+               printf("A valid handle is required\n");
+               return;
+       }
+
+       plen = gatt_attr_data_from_string(argvp[2], &value);
+       if (plen == 0) {
+               g_printerr("Invalid value\n");
+               return;
+       }
+
+       if (g_strcmp0("char-write-req", argvp[0]) == 0)
+               gatt_write_char(attrib, handle, value, plen,
+                                       char_write_req_cb, NULL);
+       else
+               gatt_write_char(attrib, handle, value, plen, NULL, NULL);
+
+       g_free(value);
+}
+
+static void cmd_sec_level(int argcp, char **argvp)
+{
+       GError *gerr = NULL;
+       BtIOSecLevel sec_level;
+
+       if (argcp < 2) {
+               printf("sec-level: %s\n", opt_sec_level);
+               return;
+       }
+
+       if (strcasecmp(argvp[1], "medium") == 0)
+               sec_level = BT_IO_SEC_MEDIUM;
+       else if (strcasecmp(argvp[1], "high") == 0)
+               sec_level = BT_IO_SEC_HIGH;
+       else if (strcasecmp(argvp[1], "low") == 0)
+               sec_level = BT_IO_SEC_LOW;
+       else {
+               printf("Allowed values: low | medium | high\n");
+               return;
+       }
+
+       g_free(opt_sec_level);
+       opt_sec_level = g_strdup(argvp[1]);
+
+       if (conn_state != STATE_CONNECTED)
+               return;
+
+       if (opt_psm) {
+               printf("It must be reconnected to this change take effect\n");
+               return;
+       }
+
+       bt_io_set(iochannel, BT_IO_L2CAP, &gerr,
+                       BT_IO_OPT_SEC_LEVEL, sec_level,
+                       BT_IO_OPT_INVALID);
+
+       if (gerr) {
+               printf("Error: %s\n", gerr->message);
+               g_error_free(gerr);
+       }
+}
+
+static struct {
+       const char *cmd;
+       void (*func)(int argcp, char **argvp);
+       const char *params;
+       const char *desc;
+} commands[] = {
+       { "help",               cmd_help,       "",
+               "Show this help"},
+       { "exit",               cmd_exit,       "",
+               "Exit interactive mode" },
+       { "connect",            cmd_connect,    "[address]",
+               "Connect to a remote device" },
+       { "disconnect",         cmd_disconnect, "",
+               "Disconnect from a remote device" },
+       { "primary",            cmd_primary,    "[UUID]",
+               "Primary Service Discovery" },
+       { "characteristics",    cmd_char,       "[start hnd] [end hnd]",
+               "Characteristics Discovery" },
+       { "char-desc",          cmd_char_desc,  "[start hnd] [end hnd]",
+               "Characteristics Descriptor Discovery" },
+       { "char-read-hnd",      cmd_read_hnd,   "<handle> [offset]",
+               "Characteristics Value/Descriptor Read by handle" },
+       { "char-read-uuid",     cmd_read_uuid,  "<UUID> [start hnd] [end hnd]",
+               "Characteristics Value/Descriptor Read by UUID" },
+       { "char-write-req",     cmd_char_write, "<handle> <new value>",
+               "Characteristic Value Write (Write Request)" },
+       { "char-write-cmd",     cmd_char_write, "<handle> <new value>",
+               "Characteristic Value Write (No response)" },
+       { "sec-level",          cmd_sec_level,  "[low | medium | high]",
+               "Set security level. Default: low" },
+       { NULL, NULL, NULL}
+};
+
+static void cmd_help(int argcp, char **argvp)
+{
+       int i;
+
+       for (i = 0; commands[i].cmd; i++)
+               printf("%-15s %-30s %s\n", commands[i].cmd,
+                               commands[i].params, commands[i].desc);
+}
+
+static void parse_line(char *line_read)
+{
+       gchar **argvp;
+       int argcp;
+       int i;
+
+       if (line_read == NULL) {
+               printf("\n");
+               cmd_exit(0, NULL);
+               return;
+       }
+
+       line_read = g_strstrip(line_read);
+
+       if (*line_read == '\0')
+               return;
+
+       add_history(line_read);
+
+       g_shell_parse_argv(line_read, &argcp, &argvp, NULL);
+
+       for (i = 0; commands[i].cmd; i++)
+               if (strcasecmp(commands[i].cmd, argvp[0]) == 0)
+                       break;
+
+       if (commands[i].cmd)
+               commands[i].func(argcp, argvp);
+       else
+               printf("%s: command not found\n", argvp[0]);
+
+       g_strfreev(argvp);
+}
+
+static gboolean prompt_read(GIOChannel *chan, GIOCondition cond,
+                                                       gpointer user_data)
+{
+       if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+               g_io_channel_unref(chan);
+               return FALSE;
+       }
+
+       rl_callback_read_char();
+
+       return TRUE;
+}
+
+int interactive(const gchar *src, const gchar *dst, int psm)
+{
+       GIOChannel *pchan;
+       gint events;
+
+       opt_sec_level = g_strdup("low");
+
+       opt_src = g_strdup(src);
+       opt_dst = g_strdup(dst);
+       opt_psm = psm;
+
+       prompt = g_string_new(NULL);
+
+       event_loop = g_main_loop_new(NULL, FALSE);
+
+       pchan = g_io_channel_unix_new(fileno(stdin));
+       g_io_channel_set_close_on_unref(pchan, TRUE);
+       events = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+       g_io_add_watch(pchan, events, prompt_read, NULL);
+
+       rl_callback_handler_install(get_prompt(), parse_line);
+
+       g_main_loop_run(event_loop);
+
+       rl_callback_handler_remove();
+       cmd_disconnect(0, NULL);
+       g_io_channel_unref(pchan);
+       g_main_loop_unref(event_loop);
+       g_string_free(prompt, TRUE);
+
+       g_free(opt_src);
+       g_free(opt_dst);
+       g_free(opt_sec_level);
+
+       return 0;
+}
diff --git a/attrib/main.c b/attrib/main.c
new file mode 100644 (file)
index 0000000..6c946be
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "manager.h"
+
+static DBusConnection *connection;
+
+static int attrib_init(void)
+{
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (connection == NULL)
+               return -EIO;
+
+       if (attrib_manager_init(connection) < 0) {
+               dbus_connection_unref(connection);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void attrib_exit(void)
+{
+       attrib_manager_exit();
+
+       dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(attrib, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, attrib_init, attrib_exit)
diff --git a/attrib/manager.c b/attrib/manager.c
new file mode 100644 (file)
index 0000000..a5a7de4
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+#include "hcid.h"
+
+#include "manager.h"
+#include "client.h"
+#include "example.h"
+
+#define GATT_UUID      "00001801-0000-1000-8000-00805f9b34fb"
+
+static DBusConnection *connection;
+
+static int client_probe(struct btd_device *device, GSList *uuids)
+{
+       const sdp_record_t *rec;
+       int psm = -1;
+
+       rec = btd_device_get_record(device, GATT_UUID);
+       if (rec) {
+               sdp_list_t *list;
+               if (sdp_get_access_protos(rec, &list) < 0)
+                       return -1;
+
+               psm = sdp_get_proto_port(list, L2CAP_UUID);
+
+               sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL);
+               sdp_list_free(list, NULL);
+
+               if (psm < 0)
+                       return -1;
+       }
+
+       return attrib_client_register(device, psm);
+}
+
+static void client_remove(struct btd_device *device)
+{
+       attrib_client_unregister(device);
+}
+
+static struct btd_device_driver client_driver = {
+       .name = "gatt-client",
+       .uuids = BTD_UUIDS(GATT_UUID),
+       .probe = client_probe,
+       .remove = client_remove,
+};
+
+int attrib_manager_init(DBusConnection *conn)
+{
+       connection = dbus_connection_ref(conn);
+
+       attrib_client_init(connection);
+
+       btd_register_device_driver(&client_driver);
+
+
+       if (main_opts.attrib_server)
+               return server_example_init();
+
+       return 0;
+}
+
+void attrib_manager_exit(void)
+{
+       btd_unregister_device_driver(&client_driver);
+
+       if (main_opts.attrib_server)
+               server_example_exit();
+
+       attrib_client_exit();
+
+       dbus_connection_unref(connection);
+}
diff --git a/attrib/manager.h b/attrib/manager.h
new file mode 100644 (file)
index 0000000..fabf342
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 attrib_manager_init(DBusConnection *conn);
+void attrib_manager_exit(void);
diff --git a/attrib/utils.c b/attrib/utils.c
new file mode 100644 (file)
index 0000000..5f4444a
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  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 <stdlib.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+
+#include "gattrib.h"
+#include "gatt.h"
+#include "btio.h"
+#include "gatttool.h"
+
+/* Minimum MTU for ATT connections */
+#define ATT_MIN_MTU_LE         23
+#define ATT_MIN_MTU_L2CAP      48
+
+GIOChannel *gatt_connect(const gchar *src, const gchar *dst,
+                               const gchar *sec_level, int psm, int mtu,
+                               BtIOConnect connect_cb)
+{
+       GIOChannel *chan;
+       bdaddr_t sba, dba;
+       GError *err = NULL;
+       BtIOSecLevel sec;
+       int minimum_mtu;
+
+       /* This check is required because currently setsockopt() returns no
+        * errors for MTU values smaller than the allowed minimum. */
+       minimum_mtu = psm ? ATT_MIN_MTU_L2CAP : ATT_MIN_MTU_LE;
+       if (mtu != 0 && mtu < minimum_mtu) {
+               g_printerr("MTU cannot be smaller than %d\n", minimum_mtu);
+               return NULL;
+       }
+
+       /* Remote device */
+       if (dst == NULL) {
+               g_printerr("Remote Bluetooth address required\n");
+               return NULL;
+       }
+       str2ba(dst, &dba);
+
+       /* Local adapter */
+       if (src != NULL) {
+               if (!strncmp(src, "hci", 3))
+                       hci_devba(atoi(src + 3), &sba);
+               else
+                       str2ba(src, &sba);
+       } else
+               bacpy(&sba, BDADDR_ANY);
+
+       if (strcmp(sec_level, "medium") == 0)
+               sec = BT_IO_SEC_MEDIUM;
+       else if (strcmp(sec_level, "high") == 0)
+               sec = BT_IO_SEC_HIGH;
+       else
+               sec = BT_IO_SEC_LOW;
+
+       if (psm == 0)
+               chan = bt_io_connect(BT_IO_L2CAP, connect_cb, NULL, NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, &sba,
+                               BT_IO_OPT_DEST_BDADDR, &dba,
+                               BT_IO_OPT_CID, GATT_CID,
+                               BT_IO_OPT_OMTU, mtu,
+                               BT_IO_OPT_SEC_LEVEL, sec,
+                               BT_IO_OPT_INVALID);
+       else
+               chan = bt_io_connect(BT_IO_L2CAP, connect_cb, NULL, NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, &sba,
+                               BT_IO_OPT_DEST_BDADDR, &dba,
+                               BT_IO_OPT_PSM, psm,
+                               BT_IO_OPT_OMTU, mtu,
+                               BT_IO_OPT_SEC_LEVEL, sec,
+                               BT_IO_OPT_INVALID);
+
+       if (err) {
+               g_printerr("%s\n", err->message);
+               g_error_free(err);
+               return NULL;
+       }
+
+       return chan;
+}
+
+size_t gatt_attr_data_from_string(const char *str, uint8_t **data)
+{
+       char tmp[3];
+       size_t size, i;
+
+       size = strlen(str) / 2;
+       *data = g_try_malloc0(size);
+       if (*data == NULL)
+               return 0;
+
+       tmp[2] = '\0';
+       for (i = 0; i < size; i++) {
+               memcpy(tmp, str + (i * 2), 2);
+               (*data)[i] = (uint8_t) strtol(tmp, NULL, 16);
+       }
+
+       return size;
+}
diff --git a/audio/a2dp-codecs.h b/audio/a2dp-codecs.h
new file mode 100644 (file)
index 0000000..51c796a
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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
+ *
+ */
+
+#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
+
+typedef struct {
+       uint8_t channel_mode:4;
+       uint8_t frequency:4;
+       uint8_t allocation_method:2;
+       uint8_t subbands:2;
+       uint8_t block_length:4;
+       uint8_t min_bitpool;
+       uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+       uint8_t channel_mode:4;
+       uint8_t crc:1;
+       uint8_t layer:3;
+       uint8_t frequency:6;
+       uint8_t mpf:1;
+       uint8_t rfa:1;
+       uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+typedef struct {
+       uint8_t frequency:4;
+       uint8_t channel_mode:4;
+       uint8_t block_length:4;
+       uint8_t subbands:2;
+       uint8_t allocation_method:2;
+       uint8_t min_bitpool;
+       uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+       uint8_t layer:3;
+       uint8_t crc:1;
+       uint8_t channel_mode:4;
+       uint8_t rfa:1;
+       uint8_t mpf:1;
+       uint8_t frequency:6;
+       uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+#else
+#error "Unknown byte order"
+#endif
diff --git a/audio/a2dp.c b/audio/a2dp.c
new file mode 100644 (file)
index 0000000..4c8a02f
--- /dev/null
@@ -0,0 +1,2391 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "glib-helper.h"
+#include "log.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "sink.h"
+#include "source.h"
+#include "unix.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 {
+       struct a2dp_server *server;
+       struct a2dp_endpoint *endpoint;
+       uint8_t type;
+       uint8_t codec;
+       struct avdtp_local_sep *lsep;
+       struct avdtp *session;
+       struct avdtp_stream *stream;
+       guint suspend_timer;
+       gboolean delay_reporting;
+       gboolean locked;
+       gboolean suspending;
+       gboolean starting;
+       void *user_data;
+       GDestroyNotify destroy;
+};
+
+struct a2dp_setup_cb {
+       struct a2dp_setup *setup;
+       a2dp_select_cb_t select_cb;
+       a2dp_config_cb_t config_cb;
+       a2dp_stream_cb_t resume_cb;
+       a2dp_stream_cb_t suspend_cb;
+       guint source_id;
+       void *user_data;
+       unsigned int id;
+};
+
+struct a2dp_setup {
+       struct audio_device *dev;
+       struct avdtp *session;
+       struct a2dp_sep *sep;
+       struct avdtp_remote_sep *rsep;
+       struct avdtp_stream *stream;
+       struct avdtp_error *err;
+       avdtp_set_configuration_cb setconf_cb;
+       GSList *caps;
+       gboolean reconfigure;
+       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;
+       uint16_t version;
+       gboolean sink_enabled;
+       gboolean source_enabled;
+};
+
+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++;
+
+       DBG("%p: ref=%d", setup, setup->ref);
+
+       return 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 struct a2dp_setup *setup_new(struct avdtp *session)
+{
+       struct audio_device *dev;
+       struct a2dp_setup *setup;
+
+       dev = a2dp_get_dev(session);
+       if (!dev) {
+               error("Unable to create setup");
+               return NULL;
+       }
+
+       setup = g_new0(struct a2dp_setup, 1);
+       setup->session = avdtp_ref(session);
+       setup->dev = a2dp_get_dev(session);
+       setups = g_slist_append(setups, setup);
+
+       return setup;
+}
+
+static void setup_free(struct a2dp_setup *s)
+{
+       DBG("%p", s);
+
+       setups = g_slist_remove(setups, s);
+       if (s->session)
+               avdtp_unref(s->session);
+       g_slist_free_full(s->cb, g_free);
+       g_slist_free_full(s->caps, g_free);
+       g_free(s);
+}
+
+static void setup_unref(struct a2dp_setup *setup)
+{
+       setup->ref--;
+
+       DBG("%p: ref=%d", setup, setup->ref);
+
+       if (setup->ref > 0)
+               return;
+
+       setup_free(setup);
+}
+
+static struct a2dp_setup_cb *setup_cb_new(struct a2dp_setup *setup)
+{
+       struct a2dp_setup_cb *cb;
+
+       cb = g_new0(struct a2dp_setup_cb, 1);
+       cb->setup = setup;
+       cb->id = ++cb_id;
+
+       setup->cb = g_slist_append(setup->cb, cb);
+       return cb;
+}
+
+static void setup_cb_free(struct a2dp_setup_cb *cb)
+{
+       struct a2dp_setup *setup = cb->setup;
+
+       if (cb->source_id)
+               g_source_remove(cb->source_id);
+
+       setup->cb = g_slist_remove(setup->cb, cb);
+       setup_unref(cb->setup);
+       g_free(cb);
+}
+
+static void finalize_setup_errno(struct a2dp_setup *s, int err,
+                                       GSourceFunc cb1, ...)
+{
+       GSourceFunc finalize;
+       va_list args;
+       struct avdtp_error avdtp_err;
+
+       if (err < 0) {
+               avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err);
+               s->err = &avdtp_err;
+       }
+
+       va_start(args, cb1);
+       finalize = cb1;
+       setup_ref(s);
+       while (finalize != NULL) {
+               finalize(s);
+               finalize = va_arg(args, GSourceFunc);
+       }
+       setup_unref(s);
+       va_end(args);
+}
+
+static gboolean finalize_config(gpointer data)
+{
+       struct a2dp_setup *s = data;
+       GSList *l;
+       struct avdtp_stream *stream = s->err ? NULL : s->stream;
+
+       for (l = s->cb; l != NULL; ) {
+               struct a2dp_setup_cb *cb = l->data;
+
+               l = l->next;
+
+               if (!cb->config_cb)
+                       continue;
+
+               cb->config_cb(s->session, s->sep, stream, s->err,
+                                                       cb->user_data);
+               setup_cb_free(cb);
+       }
+
+       return FALSE;
+}
+
+static gboolean finalize_resume(gpointer data)
+{
+       struct a2dp_setup *s = data;
+       GSList *l;
+
+       for (l = s->cb; l != NULL; ) {
+               struct a2dp_setup_cb *cb = l->data;
+
+               l = l->next;
+
+               if (!cb->resume_cb)
+                       continue;
+
+               cb->resume_cb(s->session, s->err, cb->user_data);
+               setup_cb_free(cb);
+       }
+
+       return FALSE;
+}
+
+static gboolean finalize_suspend(gpointer data)
+{
+       struct a2dp_setup *s = data;
+       GSList *l;
+
+       for (l = s->cb; l != NULL; ) {
+               struct a2dp_setup_cb *cb = l->data;
+
+               l = l->next;
+
+               if (!cb->suspend_cb)
+                       continue;
+
+               cb->suspend_cb(s->session, s->err, cb->user_data);
+               setup_cb_free(cb);
+       }
+
+       return FALSE;
+}
+
+static void finalize_select(struct a2dp_setup *s)
+{
+       GSList *l;
+
+       for (l = s->cb; l != NULL; ) {
+               struct a2dp_setup_cb *cb = l->data;
+
+               l = l->next;
+
+               if (!cb->select_cb)
+                       continue;
+
+               cb->select_cb(s->session, s->sep, s->caps, cb->user_data);
+               setup_cb_free(cb);
+       }
+}
+
+static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
+{
+       GSList *l;
+
+       for (l = setups; l != NULL; l = l->next) {
+               struct a2dp_setup *setup = l->data;
+
+               if (setup->session == session)
+                       return setup;
+       }
+
+       return NULL;
+}
+
+static struct a2dp_setup *a2dp_setup_get(struct avdtp *session)
+{
+       struct a2dp_setup *setup;
+
+       setup = find_setup_by_session(session);
+       if (!setup) {
+               setup = setup_new(session);
+               if (!setup)
+                       return NULL;
+       }
+
+       return setup_ref(setup);
+}
+
+static struct a2dp_setup *find_setup_by_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;
+
+       if (sep->endpoint && sep->endpoint->clear_configuration)
+               sep->endpoint->clear_configuration(sep, sep->user_data);
+}
+
+static gboolean auto_config(gpointer data)
+{
+       struct a2dp_setup *setup = data;
+       struct avdtp_error *err = NULL;
+
+       /* Check if configuration was aborted */
+       if (setup->sep->stream == NULL)
+               return FALSE;
+
+       if (setup->err != NULL) {
+               err = setup->err;
+               goto done;
+       }
+
+       avdtp_stream_add_cb(setup->session, setup->stream,
+                               stream_state_changed, setup->sep);
+
+       if (setup->sep->type == AVDTP_SEP_TYPE_SOURCE)
+               sink_new_stream(setup->dev, setup->session, setup->stream);
+       else
+               source_new_stream(setup->dev, setup->session, setup->stream);
+
+done:
+       if (setup->setconf_cb)
+               setup->setconf_cb(setup->session, setup->stream, setup->err);
+
+       finalize_config(setup);
+
+       if (err)
+               g_free(err);
+
+       setup_unref(setup);
+
+       return FALSE;
+}
+
+static gboolean sbc_setconf_ind(struct avdtp *session,
+                                       struct avdtp_local_sep *sep,
+                                       struct avdtp_stream *stream,
+                                       GSList *caps,
+                                       avdtp_set_configuration_cb cb,
+                                       void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct a2dp_setup *setup;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Set_Configuration_Ind", sep);
+       else
+               DBG("Source %p: Set_Configuration_Ind", sep);
+
+       setup = a2dp_setup_get(session);
+       if (!setup)
+               return FALSE;
+
+       a2dp_sep->stream = stream;
+       setup->sep = a2dp_sep;
+       setup->stream = stream;
+       setup->setconf_cb = cb;
+
+       /* Check valid settings */
+       for (; caps != NULL; caps = g_slist_next(caps)) {
+               struct avdtp_service_capability *cap = caps->data;
+               struct avdtp_media_codec_capability *codec_cap;
+               struct sbc_codec_cap *sbc_cap;
+
+               if (cap->category == AVDTP_DELAY_REPORTING &&
+                                       !a2dp_sep->delay_reporting) {
+                       setup->err = g_new(struct avdtp_error, 1);
+                       avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+                                               AVDTP_UNSUPPORTED_CONFIGURATION);
+                       goto done;
+               }
+
+               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) {
+                       setup->err = g_new(struct avdtp_error, 1);
+                       avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+                                       AVDTP_UNSUPPORTED_CONFIGURATION);
+                       goto done;
+               }
+       }
+
+done:
+       g_idle_add(auto_config, setup);
+       return TRUE;
+}
+
+static gboolean sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+                               gboolean get_all, GSList **caps, uint8_t *err,
+                               void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct avdtp_service_capability *media_transport, *media_codec;
+       struct sbc_codec_cap sbc_cap;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Get_Capability_Ind", sep);
+       else
+               DBG("Source %p: Get_Capability_Ind", sep);
+
+       *caps = NULL;
+
+       media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+                                               NULL, 0);
+
+       *caps = g_slist_append(*caps, media_transport);
+
+       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;
+
+       sbc_cap.frequency = ( SBC_SAMPLING_FREQ_48000 |
+                               SBC_SAMPLING_FREQ_44100 |
+                               SBC_SAMPLING_FREQ_32000 |
+                               SBC_SAMPLING_FREQ_16000 );
+
+       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);
+
+       if (get_all) {
+               struct avdtp_service_capability *delay_reporting;
+               delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+                                                               NULL, 0);
+               *caps = g_slist_append(*caps, delay_reporting);
+       }
+
+       return TRUE;
+}
+
+static gboolean mpeg_setconf_ind(struct avdtp *session,
+                                       struct avdtp_local_sep *sep,
+                                       struct avdtp_stream *stream,
+                                       GSList *caps,
+                                       avdtp_set_configuration_cb cb,
+                                       void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct a2dp_setup *setup;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Set_Configuration_Ind", sep);
+       else
+               DBG("Source %p: Set_Configuration_Ind", sep);
+
+       setup = a2dp_setup_get(session);
+       if (!setup)
+               return FALSE;
+
+       a2dp_sep->stream = stream;
+       setup->sep = a2dp_sep;
+       setup->stream = stream;
+       setup->setconf_cb = cb;
+
+       for (; caps != NULL; caps = g_slist_next(caps)) {
+               struct avdtp_service_capability *cap = caps->data;
+
+               if (cap->category == AVDTP_DELAY_REPORTING &&
+                                       !a2dp_sep->delay_reporting) {
+                       setup->err = g_new(struct avdtp_error, 1);
+                       avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+                                       AVDTP_UNSUPPORTED_CONFIGURATION);
+                       goto done;
+               }
+       }
+
+done:
+       g_idle_add(auto_config, setup);
+       return TRUE;
+}
+
+static gboolean mpeg_getcap_ind(struct avdtp *session,
+                               struct avdtp_local_sep *sep,
+                               gboolean get_all,
+                               GSList **caps, uint8_t *err, void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct avdtp_service_capability *media_transport, *media_codec;
+       struct mpeg_codec_cap mpeg_cap;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Get_Capability_Ind", sep);
+       else
+               DBG("Source %p: Get_Capability_Ind", sep);
+
+       *caps = NULL;
+
+       media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+                                               NULL, 0);
+
+       *caps = g_slist_append(*caps, media_transport);
+
+       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);
+
+       if (get_all) {
+               struct avdtp_service_capability *delay_reporting;
+               delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+                                                               NULL, 0);
+               *caps = g_slist_append(*caps, delay_reporting);
+       }
+
+       return TRUE;
+}
+
+static void endpoint_setconf_cb(struct a2dp_sep *sep, guint setup_id,
+                                                               gboolean ret)
+{
+       struct a2dp_setup *setup = GUINT_TO_POINTER(setup_id);
+
+       if (ret == FALSE) {
+               setup->err = g_new(struct avdtp_error, 1);
+               avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+                                       AVDTP_UNSUPPORTED_CONFIGURATION);
+       }
+
+       auto_config(setup);
+}
+
+static gboolean endpoint_setconf_ind(struct avdtp *session,
+                                               struct avdtp_local_sep *sep,
+                                               struct avdtp_stream *stream,
+                                               GSList *caps,
+                                               avdtp_set_configuration_cb cb,
+                                               void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct a2dp_setup *setup;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Set_Configuration_Ind", sep);
+       else
+               DBG("Source %p: Set_Configuration_Ind", sep);
+
+       setup = a2dp_setup_get(session);
+       if (!session)
+               return FALSE;
+
+       a2dp_sep->stream = stream;
+       setup->sep = a2dp_sep;
+       setup->stream = stream;
+       setup->setconf_cb = cb;
+
+       for (; caps != NULL; caps = g_slist_next(caps)) {
+               struct avdtp_service_capability *cap = caps->data;
+               struct avdtp_media_codec_capability *codec;
+               gboolean ret;
+
+               if (cap->category == AVDTP_DELAY_REPORTING &&
+                                       !a2dp_sep->delay_reporting) {
+                       setup->err = g_new(struct avdtp_error, 1);
+                       avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+                                       AVDTP_UNSUPPORTED_CONFIGURATION);
+                       goto done;
+               }
+
+               if (cap->category != AVDTP_MEDIA_CODEC)
+                       continue;
+
+               codec = (struct avdtp_media_codec_capability *) cap->data;
+
+               if (codec->media_codec_type != a2dp_sep->codec) {
+                       setup->err = g_new(struct avdtp_error, 1);
+                       avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+                                       AVDTP_UNSUPPORTED_CONFIGURATION);
+                       goto done;
+               }
+
+               ret = a2dp_sep->endpoint->set_configuration(a2dp_sep,
+                                               setup->dev, codec->data,
+                                               cap->length - sizeof(*codec),
+                                               GPOINTER_TO_UINT(setup),
+                                               endpoint_setconf_cb,
+                                               a2dp_sep->user_data);
+               if (ret == 0)
+                       return TRUE;
+
+               setup->err = g_new(struct avdtp_error, 1);
+               avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+                                       AVDTP_UNSUPPORTED_CONFIGURATION);
+               break;
+       }
+
+done:
+       g_idle_add(auto_config, setup);
+       return TRUE;
+}
+
+static gboolean endpoint_getcap_ind(struct avdtp *session,
+                                       struct avdtp_local_sep *sep,
+                                       gboolean get_all, GSList **caps,
+                                       uint8_t *err, void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct avdtp_service_capability *media_transport, *media_codec;
+       struct avdtp_media_codec_capability *codec_caps;
+       uint8_t *capabilities;
+       size_t length;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Get_Capability_Ind", sep);
+       else
+               DBG("Source %p: Get_Capability_Ind", sep);
+
+       *caps = NULL;
+
+       media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+                                               NULL, 0);
+
+       *caps = g_slist_append(*caps, media_transport);
+
+       length = a2dp_sep->endpoint->get_capabilities(a2dp_sep, &capabilities,
+                                                       a2dp_sep->user_data);
+
+       codec_caps = g_malloc0(sizeof(*codec_caps) + length);
+       codec_caps->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+       codec_caps->media_codec_type = a2dp_sep->codec;
+       memcpy(codec_caps->data, capabilities, length);
+
+       media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec_caps,
+                                               sizeof(*codec_caps) + length);
+
+       *caps = g_slist_append(*caps, media_codec);
+       g_free(codec_caps);
+
+       if (get_all) {
+               struct avdtp_service_capability *delay_reporting;
+               delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+                                                               NULL, 0);
+               *caps = g_slist_append(*caps, delay_reporting);
+       }
+
+       return TRUE;
+}
+
+static void endpoint_open_cb(struct a2dp_sep *sep, guint setup_id,
+                                                               gboolean ret)
+{
+       struct a2dp_setup *setup = GUINT_TO_POINTER(setup_id);
+       int err;
+
+       if (ret == FALSE) {
+               setup->stream = NULL;
+               finalize_setup_errno(setup, -EPERM, finalize_config, NULL);
+               return;
+       }
+
+       err = avdtp_open(setup->session, setup->stream);
+       if (err == 0)
+               return;
+
+       error("Error on avdtp_open %s (%d)", strerror(-err), -err);
+       setup->stream = NULL;
+       finalize_setup_errno(setup, err, finalize_config, NULL);
+}
+
+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)
+               DBG("Sink %p: Set_Configuration_Cfm", sep);
+       else
+               DBG("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);
+
+       /* Notify Endpoint */
+       if (a2dp_sep->endpoint) {
+               struct avdtp_service_capability *service;
+               struct avdtp_media_codec_capability *codec;
+               int err;
+
+               service = avdtp_stream_get_codec(stream);
+               codec = (struct avdtp_media_codec_capability *) service->data;
+
+               err = a2dp_sep->endpoint->set_configuration(a2dp_sep, dev,
+                                               codec->data, service->length -
+                                               sizeof(*codec),
+                                               GPOINTER_TO_UINT(setup),
+                                               endpoint_open_cb,
+                                               a2dp_sep->user_data);
+               if (err == 0)
+                       return;
+
+               setup->stream = NULL;
+               finalize_setup_errno(setup, -EPERM, finalize_config, NULL);
+               return;
+       }
+
+       ret = avdtp_open(session, stream);
+       if (ret < 0) {
+               error("Error on avdtp_open %s (%d)", strerror(-ret), -ret);
+               setup->stream = NULL;
+               finalize_setup_errno(setup, ret, finalize_config, NULL);
+       }
+}
+
+static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+                               uint8_t *err, void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Get_Configuration_Ind", sep);
+       else
+               DBG("Source %p: Get_Configuration_Ind", sep);
+       return TRUE;
+}
+
+static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+                       struct avdtp_stream *stream, struct avdtp_error *err,
+                       void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Set_Configuration_Cfm", sep);
+       else
+               DBG("Source %p: Set_Configuration_Cfm", sep);
+}
+
+static 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)
+               DBG("Sink %p: Open_Ind", sep);
+       else
+               DBG("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)
+               DBG("Sink %p: Open_Cfm", sep);
+       else
+               DBG("Source %p: Open_Cfm", sep);
+
+       setup = find_setup_by_session(session);
+       if (!setup)
+               return;
+
+       if (setup->reconfigure)
+               setup->reconfigure = FALSE;
+
+       if (err) {
+               setup->stream = NULL;
+               setup->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)
+               DBG("Sink %p: Start_Ind", sep);
+       else
+               DBG("Source %p: Start_Ind", sep);
+
+       setup = find_setup_by_session(session);
+       if (setup)
+               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)
+               DBG("Sink %p: Start_Cfm", sep);
+       else
+               DBG("Source %p: Start_Cfm", sep);
+
+       setup = find_setup_by_session(session);
+       if (!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)
+               DBG("Sink %p: Suspend_Ind", sep);
+       else
+               DBG("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;
+       int perr;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Suspend_Cfm", sep);
+       else
+               DBG("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);
+
+       if (!start)
+               return;
+
+       if (err) {
+               finalize_resume(setup);
+               return;
+       }
+
+       perr = avdtp_start(session, a2dp_sep->stream);
+       if (perr < 0) {
+               error("Error on avdtp_start %s (%d)", strerror(-perr), -perr);
+               finalize_setup_errno(setup, -EIO, finalize_suspend, NULL);
+       }
+}
+
+static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+                               struct avdtp_stream *stream, uint8_t *err,
+                               void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct a2dp_setup *setup;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Close_Ind", sep);
+       else
+               DBG("Source %p: Close_Ind", sep);
+
+       setup = find_setup_by_session(session);
+       if (!setup)
+               return TRUE;
+
+       finalize_setup_errno(setup, -ECONNRESET, finalize_suspend,
+                                                       finalize_resume, NULL);
+
+       return TRUE;
+}
+
+static gboolean a2dp_reconfigure(gpointer data)
+{
+       struct a2dp_setup *setup = data;
+       struct a2dp_sep *sep = setup->sep;
+       int posix_err;
+       struct avdtp_media_codec_capability *rsep_codec;
+       struct avdtp_service_capability *cap;
+
+       if (setup->rsep) {
+               cap = avdtp_get_codec(setup->rsep);
+               rsep_codec = (struct avdtp_media_codec_capability *) cap->data;
+       }
+
+       if (!setup->rsep || sep->codec != rsep_codec->media_codec_type)
+               setup->rsep = avdtp_find_remote_sep(setup->session, sep->lsep);
+
+       posix_err = avdtp_set_configuration(setup->session, setup->rsep,
+                                               sep->lsep,
+                                               setup->caps,
+                                               &setup->stream);
+       if (posix_err < 0) {
+               error("avdtp_set_configuration: %s", strerror(-posix_err));
+               goto failed;
+       }
+
+       return FALSE;
+
+failed:
+       finalize_setup_errno(setup, posix_err, finalize_config, NULL);
+       return FALSE;
+}
+
+static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+                       struct avdtp_stream *stream, struct avdtp_error *err,
+                       void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct a2dp_setup *setup;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: Close_Cfm", sep);
+       else
+               DBG("Source %p: Close_Cfm", sep);
+
+       setup = find_setup_by_session(session);
+       if (!setup)
+               return;
+
+       if (err) {
+               setup->stream = NULL;
+               setup->err = err;
+               finalize_config(setup);
+               return;
+       }
+
+       if (!setup->rsep)
+               setup->rsep = avdtp_stream_get_remote_sep(stream);
+
+       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)
+               DBG("Sink %p: Abort_Ind", sep);
+       else
+               DBG("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)
+               DBG("Sink %p: Abort_Cfm", sep);
+       else
+               DBG("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)
+               DBG("Sink %p: ReConfigure_Ind", sep);
+       else
+               DBG("Source %p: ReConfigure_Ind", sep);
+
+       return TRUE;
+}
+
+static gboolean delayreport_ind(struct avdtp *session,
+                               struct avdtp_local_sep *sep,
+                               uint8_t rseid, uint16_t delay,
+                               uint8_t *err, void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct audio_device *dev = a2dp_get_dev(session);
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: DelayReport_Ind", sep);
+       else
+               DBG("Source %p: DelayReport_Ind", sep);
+
+       unix_delay_report(dev, rseid, delay);
+
+       return TRUE;
+}
+
+static gboolean endpoint_delayreport_ind(struct avdtp *session,
+                                               struct avdtp_local_sep *sep,
+                                               uint8_t rseid, uint16_t delay,
+                                               uint8_t *err, void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: DelayReport_Ind", sep);
+       else
+               DBG("Source %p: DelayReport_Ind", sep);
+
+       if (a2dp_sep->endpoint == NULL ||
+                               a2dp_sep->endpoint->set_delay == NULL)
+               return FALSE;
+
+       a2dp_sep->endpoint->set_delay(a2dp_sep, delay, a2dp_sep->user_data);
+
+       return TRUE;
+}
+
+static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+                       struct avdtp_stream *stream, struct avdtp_error *err,
+                       void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+       struct a2dp_setup *setup;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: ReConfigure_Cfm", sep);
+       else
+               DBG("Source %p: ReConfigure_Cfm", sep);
+
+       setup = find_setup_by_session(session);
+       if (!setup)
+               return;
+
+       if (err) {
+               setup->stream = NULL;
+               setup->err = err;
+       }
+
+       finalize_config(setup);
+}
+
+static void delay_report_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+                               struct avdtp_stream *stream,
+                               struct avdtp_error *err, void *user_data)
+{
+       struct a2dp_sep *a2dp_sep = user_data;
+
+       if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+               DBG("Sink %p: DelayReport_Cfm", sep);
+       else
+               DBG("Source %p: DelayReport_Cfm", sep);
+}
+
+static struct avdtp_sep_cfm cfm = {
+       .set_configuration      = setconf_cfm,
+       .get_configuration      = getconf_cfm,
+       .open                   = open_cfm,
+       .start                  = start_cfm,
+       .suspend                = suspend_cfm,
+       .close                  = close_cfm,
+       .abort                  = abort_cfm,
+       .reconfigure            = reconf_cfm,
+       .delay_report           = delay_report_cfm,
+};
+
+static struct avdtp_sep_ind 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,
+       .delayreport            = delayreport_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,
+       .delayreport            = delayreport_ind,
+};
+
+static struct avdtp_sep_ind endpoint_ind = {
+       .get_capability         = endpoint_getcap_ind,
+       .set_configuration      = endpoint_setconf_ind,
+       .get_configuration      = getconf_ind,
+       .open                   = open_ind,
+       .start                  = start_ind,
+       .suspend                = suspend_ind,
+       .close                  = close_ind,
+       .abort                  = abort_ind,
+       .reconfigure            = reconf_ind,
+       .delayreport            = endpoint_delayreport_ind,
+};
+
+static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t *record;
+       sdp_data_t *psm, *version, *features;
+       uint16_t lp = AVDTP_UUID;
+       uint16_t a2dp_ver = 0x0102, feat = 0x000f;
+
+       record = sdp_record_alloc();
+       if (!record)
+               return NULL;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(record, root);
+
+       if (type == AVDTP_SEP_TYPE_SOURCE)
+               sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID);
+       else
+               sdp_uuid16_create(&a2dp_uuid, AUDIO_SINK_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &a2dp_uuid);
+       sdp_set_service_classes(record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID);
+       profile[0].version = a2dp_ver;
+       pfseq = sdp_list_append(0, &profile[0]);
+       sdp_set_profile_descs(record, pfseq);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto[0] = sdp_list_append(0, &l2cap_uuid);
+       psm = sdp_data_alloc(SDP_UINT16, &lp);
+       proto[0] = sdp_list_append(proto[0], psm);
+       apseq = sdp_list_append(0, proto[0]);
+
+       sdp_uuid16_create(&avdtp_uuid, AVDTP_UUID);
+       proto[1] = sdp_list_append(0, &avdtp_uuid);
+       version = sdp_data_alloc(SDP_UINT16, &avdtp_ver);
+       proto[1] = sdp_list_append(proto[1], version);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(record, aproto);
+
+       features = sdp_data_alloc(SDP_UINT16, &feat);
+       sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+       if (type == AVDTP_SEP_TYPE_SOURCE)
+               sdp_set_info_attr(record, "Audio Source", 0, 0);
+       else
+               sdp_set_info_attr(record, "Audio Sink", 0, 0);
+
+       free(psm);
+       free(version);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(pfseq, 0);
+       sdp_list_free(aproto, 0);
+       sdp_list_free(root, 0);
+       sdp_list_free(svclass_id, 0);
+
+       return record;
+}
+
+static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+
+       for (; list; list = list->next) {
+               struct a2dp_server *server = list->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, socket = TRUE;
+       gboolean delay_reporting = 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) {
+               DBG("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) {
+               DBG("audio.conf: %s", err->message);
+               g_clear_error(&err);
+       } else {
+               if (strstr(str, "Sink"))
+                       source = FALSE;
+               if (strstr(str, "Source"))
+                       sink = FALSE;
+               if (strstr(str, "Socket"))
+                       socket = FALSE;
+               g_free(str);
+       }
+
+       /* Don't register any local sep if Socket is disabled */
+       if (socket == FALSE) {
+               sbc_srcs = 0;
+               sbc_sinks = 0;
+               mpeg12_srcs = 0;
+               mpeg12_sinks = 0;
+               goto proceed;
+       }
+
+       str = g_key_file_get_string(config, "A2DP", "SBCSources", &err);
+       if (err) {
+               DBG("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) {
+               DBG("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) {
+               DBG("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) {
+               DBG("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, &server->version);
+               if (av_err < 0) {
+                       g_free(server);
+                       return av_err;
+               }
+
+               bacpy(&server->src, src);
+               servers = g_slist_append(servers, server);
+       }
+
+       if (config)
+               delay_reporting = g_key_file_get_boolean(config, "A2DP",
+                                               "DelayReporting", NULL);
+
+       if (delay_reporting)
+               server->version = 0x0103;
+       else
+               server->version = 0x0102;
+
+       server->source_enabled = source;
+       if (source) {
+               for (i = 0; i < sbc_srcs; i++)
+                       a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE,
+                                       A2DP_CODEC_SBC, delay_reporting,
+                                       NULL, NULL, NULL, NULL);
+
+               for (i = 0; i < mpeg12_srcs; i++)
+                       a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE,
+                                       A2DP_CODEC_MPEG12, delay_reporting,
+                                       NULL, NULL, NULL, NULL);
+       }
+       server->sink_enabled = sink;
+       if (sink) {
+               for (i = 0; i < sbc_sinks; i++)
+                       a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK,
+                                       A2DP_CODEC_SBC, delay_reporting,
+                                       NULL, NULL, NULL, NULL);
+
+               for (i = 0; i < mpeg12_sinks; i++)
+                       a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK,
+                                       A2DP_CODEC_MPEG12, delay_reporting,
+                                       NULL, NULL, NULL, NULL);
+       }
+
+       return 0;
+}
+
+static void a2dp_unregister_sep(struct a2dp_sep *sep)
+{
+       if (sep->destroy) {
+               sep->destroy(sep->user_data);
+               sep->endpoint = NULL;
+       }
+
+       avdtp_unregister_sep(sep->lsep);
+       g_free(sep);
+}
+
+void a2dp_unregister(const bdaddr_t *src)
+{
+       struct a2dp_server *server;
+
+       server = find_server(servers, src);
+       if (!server)
+               return;
+
+       g_slist_free_full(server->sinks, (GDestroyNotify) a2dp_unregister_sep);
+       g_slist_free_full(server->sources,
+                                       (GDestroyNotify) a2dp_unregister_sep);
+
+       avdtp_exit(src);
+
+       servers = g_slist_remove(servers, server);
+
+       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);
+
+       g_free(server);
+
+       if (servers)
+               return;
+
+       dbus_connection_unref(connection);
+       connection = NULL;
+}
+
+struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type,
+                               uint8_t codec, gboolean delay_reporting,
+                               struct a2dp_endpoint *endpoint,
+                               void *user_data, GDestroyNotify destroy,
+                               int *err)
+{
+       struct a2dp_server *server;
+       struct a2dp_sep *sep;
+       GSList **l;
+       uint32_t *record_id;
+       sdp_record_t *record;
+       struct avdtp_sep_ind *ind;
+
+       server = find_server(servers, src);
+       if (server == NULL) {
+               if (err)
+                       *err = -EINVAL;
+               return NULL;
+       }
+
+       if (type == AVDTP_SEP_TYPE_SINK && !server->sink_enabled) {
+               if (err)
+                       *err = -EPROTONOSUPPORT;
+               return NULL;
+       }
+
+       if (type == AVDTP_SEP_TYPE_SOURCE && !server->source_enabled) {
+               if (err)
+                       *err = -EPROTONOSUPPORT;
+               return NULL;
+       }
+
+       sep = g_new0(struct a2dp_sep, 1);
+
+       if (endpoint) {
+               ind = &endpoint_ind;
+               goto proceed;
+       }
+
+       ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
+
+proceed:
+       sep->lsep = avdtp_register_sep(&server->src, type,
+                                       AVDTP_MEDIA_TYPE_AUDIO, codec,
+                                       delay_reporting, ind, &cfm, sep);
+       if (sep->lsep == NULL) {
+               g_free(sep);
+               if (err)
+                       *err = -EINVAL;
+               return NULL;
+       }
+
+       sep->server = server;
+       sep->endpoint = endpoint;
+       sep->codec = codec;
+       sep->type = type;
+       sep->delay_reporting = delay_reporting;
+       sep->user_data = user_data;
+       sep->destroy = destroy;
+
+       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, server->version);
+       if (!record) {
+               error("Unable to allocate new service record");
+               avdtp_unregister_sep(sep->lsep);
+               g_free(sep);
+               if (err)
+                       *err = -EINVAL;
+               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->lsep);
+               g_free(sep);
+               if (err)
+                       *err = -EINVAL;
+               return NULL;
+       }
+       *record_id = record->handle;
+
+add:
+       *l = g_slist_append(*l, sep);
+
+       if (err)
+               *err = 0;
+       return sep;
+}
+
+void a2dp_remove_sep(struct a2dp_sep *sep)
+{
+       struct a2dp_server *server = sep->server;
+
+       if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
+               if (g_slist_find(server->sources, sep) == NULL)
+                       return;
+               server->sources = g_slist_remove(server->sources, sep);
+               if (server->sources == NULL && server->source_record_id) {
+                       remove_record_from_server(server->source_record_id);
+                       server->source_record_id = 0;
+               }
+       } else {
+               if (g_slist_find(server->sinks, sep) == NULL)
+                       return;
+               server->sinks = g_slist_remove(server->sinks, sep);
+               if (server->sinks == NULL && server->sink_record_id) {
+                       remove_record_from_server(server->sink_record_id);
+                       server->sink_record_id = 0;
+               }
+       }
+
+       if (sep->locked)
+               return;
+
+       a2dp_unregister_sep(sep);
+}
+
+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;
+}
+
+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:
+#ifdef USE_SBC_ORIGINAL_BITPOOL
+                       return 53;
+#else
+                       return 32;
+#endif
+               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);
+
+       if (avdtp_get_delay_reporting(rsep)) {
+               struct avdtp_service_capability *delay_reporting;
+               delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+                                                               NULL, 0);
+               *caps = g_slist_append(*caps, delay_reporting);
+       }
+
+       return TRUE;
+}
+
+static void select_cb(struct a2dp_sep *sep, guint setup_id, void *ret,
+                                                               int size)
+{
+       struct a2dp_setup *setup = GUINT_TO_POINTER(setup_id);
+       struct avdtp_service_capability *media_transport, *media_codec;
+       struct avdtp_media_codec_capability *cap;
+
+       if (size < 0) {
+               DBG("Endpoint replied an invalid configuration");
+               goto done;
+       }
+
+       media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+                                               NULL, 0);
+
+       setup->caps = g_slist_append(setup->caps, media_transport);
+
+       cap = g_malloc0(sizeof(*cap) + size);
+       cap->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+       cap->media_codec_type = setup->sep->codec;
+       memcpy(cap->data, ret, size);
+
+       media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap,
+                                               sizeof(*cap) + size);
+
+       setup->caps = g_slist_append(setup->caps, media_codec);
+       g_free(cap);
+
+done:
+       finalize_select(setup);
+}
+
+static gboolean auto_select(gpointer data)
+{
+       struct a2dp_setup *setup = data;
+
+       finalize_select(setup);
+
+       return FALSE;
+}
+
+static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list,
+                                       const char *sender)
+{
+       for (; list; list = list->next) {
+               struct a2dp_sep *sep = list->data;
+
+               /* Use sender's endpoint if available */
+               if (sender) {
+                       const char *name;
+
+                       if (sep->endpoint == NULL)
+                               continue;
+
+                       name = sep->endpoint->get_name(sep, sep->user_data);
+                       if (g_strcmp0(sender, name) != 0)
+                               continue;
+               }
+
+               if (avdtp_find_remote_sep(session, sep->lsep) == NULL)
+                       continue;
+
+               return sep;
+       }
+
+       return NULL;
+}
+
+static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type,
+                                       const char *sender)
+{
+       struct a2dp_server *server;
+       struct a2dp_sep *sep;
+       GSList *l;
+       bdaddr_t src;
+
+       avdtp_get_peers(session, &src, NULL);
+       server = find_server(servers, &src);
+       if (!server)
+               return NULL;
+
+       l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks;
+
+       /* Check sender's seps first */
+       sep = a2dp_find_sep(session, l, sender);
+       if (sep != NULL)
+               return sep;
+
+       return a2dp_find_sep(session, l, NULL);
+}
+
+unsigned int a2dp_select_capabilities(struct avdtp *session,
+                                       uint8_t type, const char *sender,
+                                       a2dp_select_cb_t cb,
+                                       void *user_data)
+{
+       struct a2dp_setup *setup;
+       struct a2dp_setup_cb *cb_data;
+       struct a2dp_sep *sep;
+       struct avdtp_service_capability *service;
+       struct avdtp_media_codec_capability *codec;
+       int err;
+
+       sep = a2dp_select_sep(session, type, sender);
+       if (!sep) {
+               error("Unable to select SEP");
+               return 0;
+       }
+
+       setup = a2dp_setup_get(session);
+       if (!setup)
+               return 0;
+
+       cb_data = setup_cb_new(setup);
+       cb_data->select_cb = cb;
+       cb_data->user_data = user_data;
+
+       setup->sep = sep;
+       setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
+
+       if (setup->rsep == NULL) {
+               error("Could not find remote sep");
+               goto fail;
+       }
+
+       /* FIXME: Remove auto select when it is not longer possible to register
+       endpoint in the configuration file */
+       if (sep->endpoint == NULL) {
+               if (!select_capabilities(session, setup->rsep,
+                                       &setup->caps)) {
+                       error("Unable to auto select remote SEP capabilities");
+                       goto fail;
+               }
+
+               g_idle_add(auto_select, setup);
+
+               return cb_data->id;
+       }
+
+       service = avdtp_get_codec(setup->rsep);
+       codec = (struct avdtp_media_codec_capability *) service->data;
+
+       err = sep->endpoint->select_configuration(sep, codec->data,
+                                       service->length - sizeof(*codec),
+                                       GPOINTER_TO_UINT(setup),
+                                       select_cb, sep->user_data);
+       if (err == 0)
+               return cb_data->id;
+
+fail:
+       setup_cb_free(cb_data);
+       return 0;
+
+}
+
+unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
+                               a2dp_config_cb_t cb, GSList *caps,
+                               void *user_data)
+{
+       struct a2dp_setup_cb *cb_data;
+       GSList *l;
+       struct a2dp_server *server;
+       struct a2dp_setup *setup;
+       struct a2dp_sep *tmp;
+       struct avdtp_service_capability *cap;
+       struct avdtp_media_codec_capability *codec_cap = NULL;
+       int posix_err;
+       bdaddr_t src;
+
+       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;
+
+       DBG("a2dp_config: selected SEP %p", sep->lsep);
+
+       setup = a2dp_setup_get(session);
+       if (!setup)
+               return 0;
+
+       cb_data = setup_cb_new(setup);
+       cb_data->config_cb = cb;
+       cb_data->user_data = user_data;
+
+       setup->sep = sep;
+       setup->stream = sep->stream;
+
+       /* Copy given caps if they are different than current caps */
+       if (setup->caps != caps) {
+               g_slist_free_full(setup->caps, g_free);
+               setup->caps = g_slist_copy(caps);
+       }
+
+       switch (avdtp_sep_get_state(sep->lsep)) {
+       case AVDTP_STATE_IDLE:
+               if (sep->type == AVDTP_SEP_TYPE_SOURCE)
+                       l = server->sources;
+               else
+                       l = server->sinks;
+
+               for (; l != NULL; l = l->next) {
+                       tmp = l->data;
+                       if (avdtp_has_stream(session, tmp->stream))
+                               break;
+               }
+
+               if (l != NULL) {
+                       if (a2dp_sep_get_lock(tmp))
+                               goto failed;
+                       setup->reconfigure = TRUE;
+                       if (avdtp_close(session, tmp->stream, FALSE) < 0) {
+                               error("avdtp_close failed");
+                               goto failed;
+                       }
+                       break;
+               }
+
+               setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
+               if (setup->rsep == NULL) {
+                       error("No matching ACP and INT SEPs found");
+                       goto failed;
+               }
+
+               posix_err = avdtp_set_configuration(session, setup->rsep,
+                                                       sep->lsep, caps,
+                                                       &setup->stream);
+               if (posix_err < 0) {
+                       error("avdtp_set_configuration: %s",
+                               strerror(-posix_err));
+                       goto failed;
+               }
+               break;
+       case AVDTP_STATE_OPEN:
+       case AVDTP_STATE_STREAMING:
+               if (avdtp_stream_has_capabilities(setup->stream, caps)) {
+                       DBG("Configuration match: resuming");
+                       cb_data->source_id = g_idle_add(finalize_config,
+                                                               setup);
+               } else if (!setup->reconfigure) {
+                       setup->reconfigure = TRUE;
+                       if (avdtp_close(session, sep->stream, FALSE) < 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_cb_free(cb_data);
+       return 0;
+}
+
+unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep,
+                               a2dp_stream_cb_t cb, void *user_data)
+{
+       struct a2dp_setup_cb *cb_data;
+       struct a2dp_setup *setup;
+
+       setup = a2dp_setup_get(session);
+       if (!setup)
+               return 0;
+
+       cb_data = setup_cb_new(setup);
+       cb_data->resume_cb = cb;
+       cb_data->user_data = user_data;
+
+       setup->sep = sep;
+       setup->stream = sep->stream;
+
+       switch (avdtp_sep_get_state(sep->lsep)) {
+       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
+                       cb_data->source_id = g_idle_add(finalize_resume,
+                                                               setup);
+               break;
+       default:
+               error("SEP in bad state for resume");
+               goto failed;
+       }
+
+       return cb_data->id;
+
+failed:
+       setup_cb_free(cb_data);
+       return 0;
+}
+
+unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
+                               a2dp_stream_cb_t cb, void *user_data)
+{
+       struct a2dp_setup_cb *cb_data;
+       struct a2dp_setup *setup;
+
+       setup = a2dp_setup_get(session);
+       if (!setup)
+               return 0;
+
+       cb_data = setup_cb_new(setup);
+       cb_data->suspend_cb = cb;
+       cb_data->user_data = user_data;
+
+       setup->sep = sep;
+       setup->stream = sep->stream;
+
+       switch (avdtp_sep_get_state(sep->lsep)) {
+       case AVDTP_STATE_IDLE:
+               error("a2dp_suspend: no stream to suspend");
+               goto failed;
+               break;
+       case AVDTP_STATE_OPEN:
+               cb_data->source_id = g_idle_add(finalize_suspend, setup);
+               break;
+       case AVDTP_STATE_STREAMING:
+               if (avdtp_suspend(session, sep->stream) < 0) {
+                       error("avdtp_suspend failed");
+                       goto failed;
+               }
+               sep->suspending = TRUE;
+               break;
+       default:
+               error("SEP in bad state for suspend");
+               goto failed;
+       }
+
+       return cb_data->id;
+
+failed:
+       setup_cb_free(cb_data);
+       return 0;
+}
+
+gboolean a2dp_cancel(struct audio_device *dev, unsigned int id)
+{
+       struct a2dp_setup *setup;
+       GSList *l;
+
+       setup = find_setup_by_dev(dev);
+       if (!setup)
+               return FALSE;
+
+       for (l = setup->cb; l != NULL; l = g_slist_next(l)) {
+               struct a2dp_setup_cb *cb = l->data;
+
+               if (cb->id != id)
+                       continue;
+
+               setup_ref(setup);
+               setup_cb_free(cb);
+
+               if (!setup->cb) {
+                       DBG("aborting setup %p", setup);
+                       avdtp_abort(setup->session, setup->stream);
+                       return TRUE;
+               }
+
+               setup_unref(setup);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
+{
+       if (sep->locked)
+               return FALSE;
+
+       DBG("SEP %p locked", sep->lsep);
+       sep->locked = TRUE;
+
+       return TRUE;
+}
+
+gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session)
+{
+       struct a2dp_server *server = sep->server;
+       avdtp_state_t state;
+       GSList *l;
+
+       state = avdtp_sep_get_state(sep->lsep);
+
+       sep->locked = FALSE;
+
+       DBG("SEP %p unlocked", sep->lsep);
+
+       if (sep->type == AVDTP_SEP_TYPE_SOURCE)
+               l = server->sources;
+       else
+               l = server->sinks;
+
+       /* Unregister sep if it was removed */
+       if (g_slist_find(l, sep) == NULL) {
+               a2dp_unregister_sep(sep);
+               return TRUE;
+       }
+
+       if (!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;
+}
+
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep)
+{
+       return sep->stream;
+}
diff --git a/audio/a2dp.h b/audio/a2dp.h
new file mode 100644 (file)
index 0000000..1637580
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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_endpoint_select_t) (struct a2dp_sep *sep, guint setup_id,
+                                                       void *ret, int size);
+typedef void (*a2dp_endpoint_config_t) (struct a2dp_sep *sep, guint setup_id,
+                                                               gboolean ret);
+
+struct a2dp_endpoint {
+       const char *(*get_name) (struct a2dp_sep *sep, void *user_data);
+       size_t (*get_capabilities) (struct a2dp_sep *sep,
+                                               uint8_t **capabilities,
+                                               void *user_data);
+       int (*select_configuration) (struct a2dp_sep *sep,
+                                               uint8_t *capabilities,
+                                               size_t length,
+                                               guint setup_id,
+                                               a2dp_endpoint_select_t cb,
+                                               void *user_data);
+       int (*set_configuration) (struct a2dp_sep *sep,
+                                               struct audio_device *dev,
+                                               uint8_t *configuration,
+                                               size_t length,
+                                               guint setup_id,
+                                               a2dp_endpoint_config_t cb,
+                                               void *user_data);
+       void (*clear_configuration) (struct a2dp_sep *sep, void *user_data);
+       void (*set_delay) (struct a2dp_sep *sep, uint16_t delay,
+                                                       void *user_data);
+};
+
+typedef void (*a2dp_select_cb_t) (struct avdtp *session,
+                                       struct a2dp_sep *sep, GSList *caps,
+                                       void *user_data);
+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_add_sep(const bdaddr_t *src, uint8_t type,
+                               uint8_t codec, gboolean delay_reporting,
+                               struct a2dp_endpoint *endpoint,
+                               void *user_data, GDestroyNotify destroy,
+                               int *err);
+void a2dp_remove_sep(struct a2dp_sep *sep);
+
+struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep);
+
+unsigned int a2dp_select_capabilities(struct avdtp *session,
+                                       uint8_t type, const char *sender,
+                                       a2dp_select_cb_t cb,
+                                       void *user_data);
+unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
+                               a2dp_config_cb_t cb, GSList *caps,
+                               void *user_data);
+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 avdtp_stream *a2dp_sep_get_stream(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 (file)
index 0000000..b75a39f
--- /dev/null
@@ -0,0 +1,47 @@
+# Configuration file for the audio service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+
+# 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=Gateway
+Enable=Media
+
+# 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, false means only HSP is supported
+# Defaults to true
+HFP=true
+
+# Maximum number of connected HSP/HFP devices per adapter. Defaults to 1
+MaxConnected=1
+
+# Set to true to enable use of fast connectable mode (faster page scanning)
+# for HFP when incomming call starts. Default settings are restored after
+# call is answered or rejected. Page scan interval is much shorter and page
+# scan type changed to interlaced. Such allows faster connection initiated
+# by a headset.
+FastConnectable=false
+
+# Just an example of potential config options for the other interfaces
+[A2DP]
+SBCSources=1
+APTXSources=1
+#MPEG12Sources=0
diff --git a/audio/avctp.c b/audio/avctp.c
new file mode 100644 (file)
index 0000000..df3b2b8
--- /dev/null
@@ -0,0 +1,1045 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2011  Texas Instruments, 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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+
+#include "adapter.h"
+#include "../src/device.h"
+
+#include "log.h"
+#include "error.h"
+#include "uinput.h"
+#include "btio.h"
+#include "manager.h"
+#include "device.h"
+#include "avctp.h"
+
+#define QUIRK_NO_RELEASE 1 << 0
+
+/* Message types */
+#define AVCTP_COMMAND          0
+#define AVCTP_RESPONSE         1
+
+/* Packet types */
+#define AVCTP_PACKET_SINGLE    0
+#define AVCTP_PACKET_START     1
+#define AVCTP_PACKET_CONTINUE  2
+#define AVCTP_PACKET_END       3
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avctp_header {
+       uint8_t ipid:1;
+       uint8_t cr:1;
+       uint8_t packet_type:2;
+       uint8_t transaction:4;
+       uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avc_header {
+       uint8_t code:4;
+       uint8_t _hdr0:4;
+       uint8_t subunit_id:3;
+       uint8_t subunit_type:5;
+       uint8_t opcode;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avctp_header {
+       uint8_t transaction:4;
+       uint8_t packet_type:2;
+       uint8_t cr:1;
+       uint8_t ipid:1;
+       uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avc_header {
+       uint8_t _hdr0:4;
+       uint8_t code:4;
+       uint8_t subunit_type:5;
+       uint8_t subunit_id:3;
+       uint8_t opcode;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp_state_callback {
+       avctp_state_cb cb;
+       void *user_data;
+       unsigned int id;
+};
+
+struct avctp_server {
+       bdaddr_t src;
+       GIOChannel *io;
+       GSList *sessions;
+};
+
+struct avctp {
+       struct avctp_server *server;
+       bdaddr_t dst;
+
+       avctp_state_t state;
+
+       int uinput;
+
+       GIOChannel *io;
+       guint io_id;
+
+       uint16_t mtu;
+
+       uint8_t key_quirks[256];
+};
+
+struct avctp_pdu_handler {
+       uint8_t opcode;
+       avctp_pdu_cb cb;
+       void *user_data;
+       unsigned int id;
+};
+
+static struct {
+       const char *name;
+       uint8_t avc;
+       uint16_t uinput;
+} key_map[] = {
+       { "PLAY",               PLAY_OP,                KEY_PLAYCD },
+       { "STOP",               STAVC_OP_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 *callbacks = NULL;
+static GSList *servers = NULL;
+static GSList *handlers = NULL;
+
+static void auth_cb(DBusError *derr, void *user_data);
+
+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 size_t handle_panel_passthrough(struct avctp *session,
+                                       uint8_t transaction, uint8_t *code,
+                                       uint8_t *subunit, uint8_t *operands,
+                                       size_t operand_count, void *user_data)
+{
+       const char *status;
+       int pressed, i;
+
+       if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) {
+               *code = AVC_CTYPE_REJECTED;
+               return 0;
+       }
+
+       if (operand_count == 0)
+               goto done;
+
+       if (operands[0] & 0x80) {
+               status = "released";
+               pressed = 0;
+       } else {
+               status = "pressed";
+               pressed = 1;
+       }
+
+       for (i = 0; key_map[i].name != NULL; i++) {
+               uint8_t key_quirks;
+
+               if ((operands[0] & 0x7F) != key_map[i].avc)
+                       continue;
+
+               DBG("AV/C: %s %s", key_map[i].name, status);
+
+               key_quirks = session->key_quirks[key_map[i].avc];
+
+               if (key_quirks & QUIRK_NO_RELEASE) {
+                       if (!pressed) {
+                               DBG("AV/C: Ignoring release");
+                               break;
+                       }
+
+                       DBG("AV/C: treating key press as press + release");
+                       send_key(session->uinput, key_map[i].uinput, 1);
+                       send_key(session->uinput, key_map[i].uinput, 0);
+                       break;
+               }
+
+               send_key(session->uinput, key_map[i].uinput, pressed);
+               break;
+       }
+
+       if (key_map[i].name == NULL)
+               DBG("AV/C: unknown button 0x%02X %s",
+                                               operands[0] & 0x7F, status);
+
+done:
+       *code = AVC_CTYPE_ACCEPTED;
+       return operand_count;
+}
+
+static size_t handle_unit_info(struct avctp *session,
+                                       uint8_t transaction, uint8_t *code,
+                                       uint8_t *subunit, uint8_t *operands,
+                                       size_t operand_count, void *user_data)
+{
+       if (*code != AVC_CTYPE_STATUS) {
+               *code = AVC_CTYPE_REJECTED;
+               return 0;
+       }
+
+       *code = AVC_CTYPE_STABLE;
+
+       /* The first operand should be 0x07 for the UNITINFO response.
+        * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+        * Interface Command Set (section 9.2.1, page 45) specs
+        * explain this value but both use it */
+       if (operand_count >= 1)
+               operands[0] = 0x07;
+       if (operand_count >= 2)
+               operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+       DBG("reply to AVC_OP_UNITINFO");
+
+       return 0;
+}
+
+static size_t handle_subunit_info(struct avctp *session,
+                                       uint8_t transaction, uint8_t *code,
+                                       uint8_t *subunit, uint8_t *operands,
+                                       size_t operand_count, void *user_data)
+{
+       if (*code != AVC_CTYPE_STATUS) {
+               *code = AVC_CTYPE_REJECTED;
+               return 0;
+       }
+
+       *code = AVC_CTYPE_STABLE;
+
+       /* The first operand should be 0x07 for the UNITINFO response.
+        * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+        * Interface Command Set (section 9.2.1, page 45) specs
+        * explain this value but both use it */
+       if (operand_count >= 2)
+               operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+       DBG("reply to AVC_OP_SUBUNITINFO");
+
+       return 0;
+}
+
+static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode)
+{
+       for (; list; list = list->next) {
+               struct avctp_pdu_handler *handler = list->data;
+
+               if (handler->opcode == opcode)
+                       return handler;
+       }
+
+       return NULL;
+}
+
+static void avctp_disconnected(struct avctp *session)
+{
+       struct avctp_server *server = session->server;
+
+       if (!session)
+               return;
+
+       if (session->io) {
+               g_io_channel_shutdown(session->io, TRUE, NULL);
+               g_io_channel_unref(session->io);
+               session->io = NULL;
+       }
+
+       if (session->io_id) {
+               g_source_remove(session->io_id);
+               session->io_id = 0;
+
+               if (session->state == AVCTP_STATE_CONNECTING) {
+                       struct audio_device *dev;
+
+                       dev = manager_get_device(&session->server->src,
+                                                       &session->dst, FALSE);
+                       audio_device_cancel_authorization(dev, auth_cb,
+                                                               session);
+               }
+       }
+
+       if (session->uinput >= 0) {
+               char address[18];
+
+               ba2str(&session->dst, address);
+               DBG("AVCTP: closing uinput for %s", address);
+
+               ioctl(session->uinput, UI_DEV_DESTROY);
+               close(session->uinput);
+               session->uinput = -1;
+       }
+
+       server->sessions = g_slist_remove(server->sessions, session);
+       g_free(session);
+}
+
+static void avctp_set_state(struct avctp *session, avctp_state_t new_state)
+{
+       GSList *l;
+       struct audio_device *dev;
+       avctp_state_t old_state = session->state;
+
+       dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+       if (dev == NULL) {
+               error("avdtp_set_state(): no matching audio device");
+               return;
+       }
+
+       session->state = new_state;
+
+       for (l = callbacks; l != NULL; l = l->next) {
+               struct avctp_state_callback *cb = l->data;
+               cb->cb(dev, old_state, new_state, cb->user_data);
+       }
+
+       switch (new_state) {
+       case AVCTP_STATE_DISCONNECTED:
+               DBG("AVCTP Disconnected");
+
+               avctp_disconnected(session);
+
+               if (old_state != AVCTP_STATE_CONNECTED)
+                       break;
+
+               if (!audio_device_is_active(dev, NULL))
+                       audio_device_set_authorized(dev, FALSE);
+
+               break;
+       case AVCTP_STATE_CONNECTING:
+               DBG("AVCTP Connecting");
+               break;
+       case AVCTP_STATE_CONNECTED:
+               DBG("AVCTP Connected");
+               break;
+       default:
+               error("Invalid AVCTP state %d", new_state);
+               return;
+       }
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+                               gpointer data)
+{
+       struct avctp *session = data;
+       uint8_t buf[1024], *operands, code, subunit;
+       struct avctp_header *avctp;
+       struct avc_header *avc;
+       int ret, packet_size, operand_count, sock;
+       struct avctp_pdu_handler *handler;
+
+       if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+               goto failed;
+
+       sock = g_io_channel_unix_get_fd(session->io);
+
+       ret = read(sock, buf, sizeof(buf));
+       if (ret <= 0)
+               goto failed;
+
+       DBG("Got %d bytes of data for AVCTP session %p", ret, session);
+
+       if ((unsigned int) ret < sizeof(struct avctp_header)) {
+               error("Too small AVCTP packet");
+               goto failed;
+       }
+
+       avctp = (struct avctp_header *) buf;
+
+       DBG("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 avc_header)) {
+               error("Too small AVCTP packet");
+               goto failed;
+       }
+
+       avc = (struct avc_header *) (buf + sizeof(struct avctp_header));
+
+       ret -= sizeof(struct avc_header);
+
+       operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
+       operand_count = ret;
+
+       DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
+                       "opcode 0x%02X, %d operands",
+                       avctp->cr ? "response" : "command",
+                       avc->code, avc->subunit_type, avc->subunit_id,
+                       avc->opcode, operand_count);
+
+       if (avctp->cr == AVCTP_RESPONSE)
+               return TRUE;
+
+       packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH;
+       avctp->cr = AVCTP_RESPONSE;
+
+       if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
+               avc->code = AVC_CTYPE_NOT_IMPLEMENTED;
+               goto done;
+       }
+
+       if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+               avctp->ipid = 1;
+               avc->code = AVC_CTYPE_REJECTED;
+               goto done;
+       }
+
+       handler = find_handler(handlers, avc->opcode);
+       if (!handler) {
+               DBG("handler not found for 0x%02x", avc->opcode);
+               avc->code = AVC_CTYPE_REJECTED;
+               goto done;
+       }
+
+       code = avc->code;
+       subunit = avc->subunit_type;
+
+       packet_size += handler->cb(session, avctp->transaction, &code,
+                                       &subunit, operands, operand_count,
+                                       handler->user_data);
+
+       avc->code = code;
+       avc->subunit_type = subunit;
+
+done:
+       ret = write(sock, buf, packet_size);
+       if (ret != packet_size)
+               goto failed;
+
+       return TRUE;
+
+failed:
+       DBG("AVCTP session %p got disconnected", session);
+       avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+       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 avctp *session)
+{
+       struct audio_device *dev;
+       char address[18], name[248 + 1];
+
+       dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+
+       device_get_name(dev->btd_dev, name, sizeof(name));
+       if (g_str_equal(name, "Nokia CK-20W")) {
+               session->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE;
+               session->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE;
+               session->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE;
+               session->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE;
+       }
+
+       ba2str(&session->dst, address);
+
+       session->uinput = uinput_create(address);
+       if (session->uinput < 0)
+               error("AVRCP: failed to init uinput for %s", address);
+       else
+               DBG("AVRCP: uinput initialized for %s", address);
+}
+
+static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+       struct avctp *session = data;
+       char address[18];
+       uint16_t imtu;
+       GError *gerr = NULL;
+
+       if (err) {
+               avctp_set_state(session, 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(session, AVCTP_STATE_DISCONNECTED);
+               error("%s", gerr->message);
+               g_error_free(gerr);
+               return;
+       }
+
+       DBG("AVCTP: connected to %s", address);
+
+       if (!session->io)
+               session->io = g_io_channel_ref(chan);
+
+       init_uinput(session);
+
+       avctp_set_state(session, AVCTP_STATE_CONNECTED);
+       session->mtu = imtu;
+       session->io_id = g_io_add_watch(chan,
+                               G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                               (GIOFunc) session_cb, session);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+       struct avctp *session = user_data;
+       GError *err = NULL;
+
+       if (session->io_id) {
+               g_source_remove(session->io_id);
+               session->io_id = 0;
+       }
+
+       if (derr && dbus_error_is_set(derr)) {
+               error("Access denied: %s", derr->message);
+               avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+               return;
+       }
+
+       if (!bt_io_accept(session->io, avctp_connect_cb, session,
+                                                               NULL, &err)) {
+               error("bt_io_accept: %s", err->message);
+               g_error_free(err);
+               avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+       }
+}
+
+static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+       for (; list; list = list->next) {
+               struct avctp_server *server = list->data;
+
+               if (bacmp(&server->src, src) == 0)
+                       return server;
+       }
+
+       return NULL;
+}
+
+static struct avctp *find_session(GSList *list, const bdaddr_t *dst)
+{
+       for (; list != NULL; list = g_slist_next(list)) {
+               struct avctp *s = list->data;
+
+               if (bacmp(dst, &s->dst))
+                       continue;
+
+               return s;
+       }
+
+       return NULL;
+}
+
+static struct avctp *avctp_get_internal(const bdaddr_t *src,
+                                                       const bdaddr_t *dst)
+{
+       struct avctp_server *server;
+       struct avctp *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)
+               return session;
+
+       session = g_new0(struct avctp, 1);
+
+       session->server = server;
+       bacpy(&session->dst, dst);
+       session->state = AVCTP_STATE_DISCONNECTED;
+
+       server->sessions = g_slist_append(server->sessions, session);
+
+       return session;
+}
+
+static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+       struct avctp *session;
+       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;
+       }
+
+       DBG("AVCTP: incoming connect from %s", address);
+
+       session = avctp_get_internal(&src, &dst);
+       if (!session)
+               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;
+               }
+       }
+
+       if (dev->control == NULL) {
+               btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
+               if (dev->control == NULL)
+                       goto drop;
+       }
+
+       if (session->io) {
+               error("Refusing unexpected connect from %s", address);
+               goto drop;
+       }
+
+       avctp_set_state(session, AVCTP_STATE_CONNECTING);
+       session->io = g_io_channel_ref(chan);
+
+       if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
+                                               auth_cb, session) < 0)
+               goto drop;
+
+       session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                                       session_cb, session);
+       return;
+
+drop:
+       if (!session || !session->io)
+               g_io_channel_shutdown(chan, TRUE, NULL);
+       if (session)
+               avctp_set_state(session, 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;
+}
+
+static unsigned int passthrough_id = 0;
+static unsigned int unit_id = 0;
+static unsigned int subunit_id = 0;
+
+int avctp_register(const bdaddr_t *src, gboolean master)
+{
+       struct avctp_server *server;
+
+       server = g_new0(struct avctp_server, 1);
+       if (!server)
+               return -ENOMEM;
+
+       server->io = avctp_server_socket(src, master);
+       if (!server->io) {
+               g_free(server);
+               return -1;
+       }
+
+       bacpy(&server->src, src);
+
+       servers = g_slist_append(servers, server);
+
+       if (!passthrough_id)
+               passthrough_id = avctp_register_pdu_handler(AVC_OP_PASSTHROUGH,
+                                       handle_panel_passthrough, NULL);
+
+       if (!unit_id)
+               unit_id = avctp_register_pdu_handler(AVC_OP_UNITINFO, handle_unit_info,
+                                                                       NULL);
+
+       if (!subunit_id)
+               subunit_id = avctp_register_pdu_handler(AVC_OP_SUBUNITINFO,
+                                               handle_subunit_info, NULL);
+
+       return 0;
+}
+
+void avctp_unregister(const bdaddr_t *src)
+{
+       struct avctp_server *server;
+
+       server = find_server(servers, src);
+       if (!server)
+               return;
+
+       while (server->sessions)
+               avctp_disconnected(server->sessions->data);
+
+       servers = g_slist_remove(servers, server);
+
+       g_io_channel_shutdown(server->io, TRUE, NULL);
+       g_io_channel_unref(server->io);
+       g_free(server);
+
+       if (servers)
+               return;
+
+       if (passthrough_id) {
+               avctp_unregister_pdu_handler(passthrough_id);
+               passthrough_id = 0;
+       }
+
+       if (unit_id) {
+               avctp_unregister_pdu_handler(unit_id);
+               passthrough_id = 0;
+       }
+
+       if (subunit_id) {
+               avctp_unregister_pdu_handler(subunit_id);
+               subunit_id = 0;
+       }
+}
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op)
+{
+       unsigned char buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + 2];
+       struct avctp_header *avctp = (void *) buf;
+       struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+       uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
+       int sk;
+       static uint8_t transaction = 0;
+
+       if (session->state != AVCTP_STATE_CONNECTED)
+               return -ENOTCONN;
+
+       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);
+
+       avc->code = AVC_CTYPE_CONTROL;
+       avc->subunit_type = AVC_SUBUNIT_PANEL;
+       avc->opcode = AVC_OP_PASSTHROUGH;
+
+       operands[0] = op & 0x7f;
+       operands[1] = 0;
+
+       sk = g_io_channel_unix_get_fd(session->io);
+
+       if (write(sk, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       /* Button release */
+       avctp->transaction = transaction++;
+       operands[0] |= 0x80;
+
+       if (write(sk, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+                               uint8_t code, uint8_t subunit,
+                               uint8_t *operands, size_t operand_count)
+{
+       uint8_t *buf;
+       struct avctp_header *avctp;
+       struct avc_header *avc;
+       uint8_t *pdu;
+       int sk, err;
+       uint16_t size;
+
+       if (session->state != AVCTP_STATE_CONNECTED)
+               return -ENOTCONN;
+
+       sk = g_io_channel_unix_get_fd(session->io);
+       size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + operand_count;
+       buf = g_malloc0(size);
+
+       avctp = (void *) buf;
+       avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+       pdu = (void *) &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
+
+       avctp->transaction = transaction;
+       avctp->packet_type = AVCTP_PACKET_SINGLE;
+       avctp->cr = AVCTP_RESPONSE;
+       avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+       avc->code = code;
+       avc->subunit_type = subunit;
+       avc->opcode = AVC_OP_VENDORDEP;
+
+       memcpy(pdu, operands, operand_count);
+
+       err = write(sk, buf, size);
+       if (err < 0) {
+               g_free(buf);
+               return -errno;
+       }
+
+       g_free(buf);
+       return 0;
+}
+
+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;
+
+       callbacks = g_slist_append(callbacks, state_cb);
+
+       return state_cb->id;
+}
+
+gboolean avctp_remove_state_cb(unsigned int id)
+{
+       GSList *l;
+
+       for (l = callbacks; l != NULL; l = l->next) {
+               struct avctp_state_callback *cb = l->data;
+               if (cb && cb->id == id) {
+                       callbacks = g_slist_remove(callbacks, cb);
+                       g_free(cb);
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
+                                                       void *user_data)
+{
+       struct avctp_pdu_handler *handler;
+       static unsigned int id = 0;
+
+       handler = find_handler(handlers, opcode);
+       if (handler)
+               return 0;
+
+       handler = g_new(struct avctp_pdu_handler, 1);
+       handler->opcode = opcode;
+       handler->cb = cb;
+       handler->user_data = user_data;
+       handler->id = ++id;
+
+       handlers = g_slist_append(handlers, handler);
+
+       return handler->id;
+}
+
+gboolean avctp_unregister_pdu_handler(unsigned int id)
+{
+       GSList *l;
+
+       for (l = handlers; l != NULL; l = l->next) {
+               struct avctp_pdu_handler *handler = l->data;
+
+               if (handler->id == id) {
+                       handlers = g_slist_remove(handlers, handler);
+                       g_free(handler);
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst)
+{
+       struct avctp *session;
+       GError *err = NULL;
+       GIOChannel *io;
+
+       session = avctp_get_internal(src, dst);
+       if (!session)
+               return NULL;
+
+       if (session->state > AVCTP_STATE_DISCONNECTED)
+               return session;
+
+       avctp_set_state(session, AVCTP_STATE_CONNECTING);
+
+       io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, session, NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, &session->server->src,
+                               BT_IO_OPT_DEST_BDADDR, &session->dst,
+                               BT_IO_OPT_PSM, AVCTP_PSM,
+                               BT_IO_OPT_INVALID);
+       if (err) {
+               avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+               error("%s", err->message);
+               g_error_free(err);
+               return NULL;
+       }
+
+       session->io = io;
+
+       return session;
+}
+
+void avctp_disconnect(struct avctp *session)
+{
+       if (!session->io)
+               return;
+
+       avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+}
+
+struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst)
+{
+       return avctp_get_internal(src, dst);
+}
diff --git a/audio/avctp.h b/audio/avctp.h
new file mode 100644 (file)
index 0000000..9727485
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 AVCTP_PSM 23
+
+#define AVC_MTU 512
+#define AVC_HEADER_LENGTH 3
+
+/* ctype entries */
+#define AVC_CTYPE_CONTROL              0x0
+#define AVC_CTYPE_STATUS               0x1
+#define AVC_CTYPE_NOTIFY               0x3
+#define AVC_CTYPE_NOT_IMPLEMENTED      0x8
+#define AVC_CTYPE_ACCEPTED             0x9
+#define AVC_CTYPE_REJECTED             0xA
+#define AVC_CTYPE_STABLE               0xC
+#define AVC_CTYPE_CHANGED              0xD
+#define AVC_CTYPE_INTERIM              0xF
+
+/* opcodes */
+#define AVC_OP_VENDORDEP               0x00
+#define AVC_OP_UNITINFO                        0x30
+#define AVC_OP_SUBUNITINFO             0x31
+#define AVC_OP_PASSTHROUGH             0x7c
+
+/* subunits of interest */
+#define AVC_SUBUNIT_PANEL              0x09
+
+/* operands in passthrough commands */
+#define VOL_UP_OP                      0x41
+#define VOL_DOWN_OP                    0x42
+#define MUTE_OP                                0x43
+#define PLAY_OP                                0x44
+#define STAVC_OP_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
+
+struct avctp;
+
+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);
+
+typedef size_t (*avctp_pdu_cb) (struct avctp *session, uint8_t transaction,
+                                       uint8_t *code, uint8_t *subunit,
+                                       uint8_t *operands, size_t operand_count,
+                                       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 avctp_register(const bdaddr_t *src, gboolean master);
+void avctp_unregister(const bdaddr_t *src);
+
+struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst);
+struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst);
+void avctp_disconnect(struct avctp *session);
+
+unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
+                                                       void *user_data);
+gboolean avctp_unregister_pdu_handler(unsigned int id);
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op);
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+                               uint8_t code, uint8_t subunit,
+                               uint8_t *operands, size_t operand_count);
diff --git a/audio/avdtp.c b/audio/avdtp.c
new file mode 100644 (file)
index 0000000..9aba77a
--- /dev/null
@@ -0,0 +1,3966 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "log.h"
+
+#include "../src/adapter.h"
+#include "../src/manager.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"
+
+#define AVDTP_PSM 25
+
+#define MAX_SEID 0x3E
+
+#ifndef MAX
+# define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+#define AVDTP_DISCOVER                         0x01
+#define AVDTP_GET_CAPABILITIES                 0x02
+#define AVDTP_SET_CONFIGURATION                        0x03
+#define AVDTP_GET_CONFIGURATION                        0x04
+#define AVDTP_RECONFIGURE                      0x05
+#define AVDTP_OPEN                             0x06
+#define AVDTP_START                            0x07
+#define AVDTP_CLOSE                            0x08
+#define AVDTP_SUSPEND                          0x09
+#define AVDTP_ABORT                            0x0A
+#define AVDTP_SECURITY_CONTROL                 0x0B
+#define AVDTP_GET_ALL_CAPABILITIES             0x0C
+#define AVDTP_DELAY_REPORT                     0x0D
+
+#define AVDTP_PKT_TYPE_SINGLE                  0x00
+#define AVDTP_PKT_TYPE_START                   0x01
+#define AVDTP_PKT_TYPE_CONTINUE                        0x02
+#define AVDTP_PKT_TYPE_END                     0x03
+
+#define AVDTP_MSG_TYPE_COMMAND                 0x00
+#define AVDTP_MSG_TYPE_GEN_REJECT              0x01
+#define AVDTP_MSG_TYPE_ACCEPT                  0x02
+#define AVDTP_MSG_TYPE_REJECT                  0x03
+
+#ifdef __TIZEN_PATCH__
+#define REQ_TIMEOUT 10
+#else
+#define REQ_TIMEOUT 6
+#endif
+
+#define ABORT_TIMEOUT 2
+#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));
+
+struct delay_req {
+       uint8_t rfa0:2;
+       uint8_t acp_seid:6;
+       uint16_t delay;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct seid_req {
+       uint8_t acp_seid:6;
+       uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct setconf_req {
+       uint8_t acp_seid:6;
+       uint8_t rfa0:2;
+       uint8_t int_seid:6;
+       uint8_t rfa1:2;
+
+       uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+       uint8_t acp_seid:6;
+       uint8_t rfa0:2;
+       uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+       uint8_t acp_seid:6;
+       uint8_t rfa0:2;
+
+       uint8_t serv_cap;
+       uint8_t serv_cap_len;
+
+       uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct delay_req {
+       uint8_t acp_seid:6;
+       uint8_t rfa0:2;
+       uint16_t delay;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct in_buf {
+       gboolean active;
+       int no_of_packets;
+       uint8_t transaction;
+       uint8_t 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;
+       gboolean delay_reporting;
+       GSList *caps; /* of type struct avdtp_service_capability */
+       struct avdtp_stream *stream;
+};
+
+struct avdtp_server {
+       bdaddr_t src;
+       uint16_t version;
+       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;
+       gboolean delay_reporting;
+       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;
+       gboolean delay_reporting;
+       uint16_t delay;         /* AVDTP 1.3 Delay Reporting feature */
+       gboolean starting;      /* only valid while sep state == OPEN */
+};
+
+/* Structure describing an AVDTP connection between two devices */
+
+struct avdtp {
+       int ref;
+       int free_lock;
+
+       uint16_t version;
+
+       struct avdtp_server *server;
+       bdaddr_t dst;
+
+       avdtp_session_state_t state;
+
+       /* True if the session should be automatically disconnected */
+       gboolean auto_dc;
+
+       /* True if the entire device is being disconnected */
+       gboolean device_disconnect;
+
+       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 void auth_cb(DBusError *derr, void *user_data);
+
+static struct avdtp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+       for (; list; list = list->next) {
+               struct avdtp_server *server = list->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 "<unknown state>";
+       }
+}
+
+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);
+       }
+
+       /* Check if there is enough space to start packet */
+       if (session->omtu < sizeof(start)) {
+               error("No enough space to fragment packet");
+               return FALSE;
+       }
+
+       /* Count the number of needed fragments */
+       cont_fragments = (len - (session->omtu - sizeof(start))) /
+                                       (session->omtu - sizeof(cont)) + 1;
+
+       DBG("%zu bytes split into %d fragments", len, cont_fragments + 1);
+
+       /* Send the start packet */
+       memset(&start, 0, sizeof(start));
+       start.transaction = transaction;
+       start.packet_type = AVDTP_PKT_TYPE_START;
+       start.message_type = message_type;
+       start.no_of_packets = cont_fragments + 1;
+       start.signal_id = signal_id;
+
+       memcpy(session->buf, &start, sizeof(start));
+       memcpy(session->buf + sizeof(start), data,
+                                       session->omtu - sizeof(start));
+
+       if (!try_send(sock, session->buf, session->omtu))
+               return FALSE;
+
+       DBG("first packet with %zu bytes sent", session->omtu - sizeof(start));
+
+       sent = session->omtu - sizeof(start);
+
+       /* Send the continue fragments and the end packet */
+       while (sent < len) {
+               int left, to_copy;
+
+               left = len - sent;
+               if (left + sizeof(cont) > session->omtu) {
+                       cont.packet_type = AVDTP_PKT_TYPE_CONTINUE;
+                       to_copy = session->omtu - sizeof(cont);
+                       DBG("sending continue with %d bytes", to_copy);
+               } else {
+                       cont.packet_type = AVDTP_PKT_TYPE_END;
+                       to_copy = left;
+                       DBG("sending end with %d bytes", to_copy);
+               }
+
+               cont.transaction = transaction;
+               cont.message_type = message_type;
+
+               memcpy(session->buf, &cont, sizeof(cont));
+               memcpy(session->buf + sizeof(cont), data + sent, to_copy);
+
+               if (!try_send(sock, session->buf, to_copy + sizeof(cont)))
+                       return FALSE;
+
+               sent += to_copy;
+       }
+
+       return TRUE;
+}
+
+static void pending_req_free(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;
+
+       DBG("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;
+
+       DBG("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);
+
+       if (session->device_disconnect) {
+               session->dc_timer = g_idle_add(disconnect_timeout, session);
+               return;
+       }
+
+       session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT,
+                                               disconnect_timeout,
+                                               session);
+}
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t category, int id)
+{
+       err->category = category;
+
+       if (category == AVDTP_ERRNO)
+               err->err.posix_errno = id;
+       else
+               err->err.error_code = id;
+}
+
+uint8_t avdtp_error_category(struct avdtp_error *err)
+{
+       return err->category;
+}
+
+int avdtp_error_error_code(struct avdtp_error *err)
+{
+       assert(err->category != AVDTP_ERRNO);
+       return err->err.error_code;
+}
+
+int avdtp_error_posix_errno(struct avdtp_error *err)
+{
+       assert(err->category == AVDTP_ERRNO);
+       return err->err.posix_errno;
+}
+
+static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session,
+                                                       uint8_t rseid)
+{
+       GSList *l;
+
+       for (l = session->streams; l != NULL; l = g_slist_next(l)) {
+               struct avdtp_stream *stream = l->data;
+
+               if (stream->rseid == rseid)
+                       return stream;
+       }
+
+       return NULL;
+}
+
+static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid)
+{
+       GSList *l;
+
+       for (l = seps; l != NULL; l = g_slist_next(l)) {
+               struct avdtp_remote_sep *sep = l->data;
+
+               if (sep->seid == seid)
+                       return sep;
+       }
+
+       return NULL;
+}
+
+static void avdtp_set_state(struct avdtp *session,
+                                       avdtp_session_state_t new_state)
+{
+       GSList *l;
+       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)
+               close_stream(stream);
+
+       if (stream->io_id)
+               g_source_remove(stream->io_id);
+
+       g_slist_free_full(stream->callbacks, g_free);
+       g_slist_free_full(stream->caps, g_free);
+
+       g_free(stream);
+}
+
+static gboolean stream_timeout(gpointer user_data)
+{
+       struct avdtp_stream *stream = user_data;
+       struct avdtp *session = stream->session;
+
+       if (avdtp_close(session, stream, FALSE) < 0)
+               error("stream_timeout: closing AVDTP stream failed");
+
+       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 int get_send_buffer_size(int sk)
+{
+       int size;
+       socklen_t optlen = sizeof(size);
+
+       if (getsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, &optlen) < 0) {
+               int err = -errno;
+               error("getsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err),
+                                                                       -err);
+               return err;
+       }
+
+       /*
+        * Doubled value is returned by getsockopt since kernel uses that
+        * space for its own purposes (see man 7 socket, bookkeeping overhead
+        * for SO_SNDBUF).
+        */
+       return size / 2;
+}
+
+static int set_send_buffer_size(int sk, int size)
+{
+       socklen_t optlen = sizeof(size);
+
+       if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, optlen) < 0) {
+               int err = -errno;
+               error("setsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err),
+                                                                       -err);
+               return err;
+       }
+
+       return 0;
+}
+
+static void handle_transport_connect(struct avdtp *session, GIOChannel *io,
+                                       uint16_t imtu, uint16_t omtu)
+{
+       struct avdtp_stream *stream = session->pending_open;
+       struct avdtp_local_sep *sep = stream->lsep;
+       int sk, buf_size, min_buf_size;
+       GError *err = NULL;
+
+       session->pending_open = NULL;
+
+       if (stream->timer) {
+               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_ERRNO, EIO);
+                       sep->cfm->open(session, sep, NULL, &err,
+                                       sep->user_data);
+               }
+               return;
+       }
+
+       if (stream->io == NULL)
+               stream->io = g_io_channel_ref(io);
+
+       stream->omtu = omtu;
+       stream->imtu = imtu;
+
+       /* Apply special settings only if local SEP is of type SRC */
+       if (sep->info.type != AVDTP_SEP_TYPE_SOURCE)
+               goto proceed;
+
+       bt_io_set(stream->io, BT_IO_L2CAP, &err,
+                                       BT_IO_OPT_FLUSHABLE, TRUE,
+                                       BT_IO_OPT_INVALID);
+       if (err != NULL) {
+               error("Enabling flushable packets failed: %s", err->message);
+               g_error_free(err);
+       } else
+               DBG("Flushable packets enabled");
+
+       sk = g_io_channel_unix_get_fd(stream->io);
+       buf_size = get_send_buffer_size(sk);
+       if (buf_size < 0)
+               goto proceed;
+
+       DBG("sk %d, omtu %d, send buffer size %d", sk, omtu, buf_size);
+       min_buf_size = omtu * 2;
+       if (buf_size < min_buf_size) {
+               DBG("send buffer size to be increassed to %d",
+                               min_buf_size);
+               set_send_buffer_size(sk, min_buf_size);
+       }
+
+proceed:
+       if (!stream->open_acp && sep->cfm && sep->cfm->open)
+               sep->cfm->open(session, sep, stream, NULL, sep->user_data);
+
+       avdtp_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 handle_unanswered_req(struct avdtp *session,
+                                               struct avdtp_stream *stream)
+{
+       struct pending_req *req;
+       struct avdtp_local_sep *lsep;
+       struct avdtp_error err;
+
+       if (session->req->signal_id == AVDTP_ABORT) {
+               /* Avoid freeing the Abort request here */
+               DBG("handle_unanswered_req: Abort req, returning");
+               session->req->stream = NULL;
+               return;
+       }
+
+       req = session->req;
+       session->req = NULL;
+
+       avdtp_error_init(&err, AVDTP_ERRNO, EIO);
+
+       lsep = stream->lsep;
+
+       switch (req->signal_id) {
+       case AVDTP_RECONFIGURE:
+               error("No reply to Reconfigure request");
+               if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+                       lsep->cfm->reconfigure(session, lsep, stream, &err,
+                                               lsep->user_data);
+               break;
+       case AVDTP_OPEN:
+               error("No reply to Open request");
+               if (lsep && lsep->cfm && lsep->cfm->open)
+                       lsep->cfm->open(session, lsep, stream, &err,
+                                       lsep->user_data);
+               break;
+       case AVDTP_START:
+               error("No reply to Start request");
+               if (lsep && lsep->cfm && lsep->cfm->start)
+                       lsep->cfm->start(session, lsep, stream, &err,
+                                               lsep->user_data);
+               break;
+       case AVDTP_SUSPEND:
+               error("No reply to Suspend request");
+               if (lsep && lsep->cfm && lsep->cfm->suspend)
+                       lsep->cfm->suspend(session, lsep, stream, &err,
+                                               lsep->user_data);
+               break;
+       case AVDTP_CLOSE:
+               error("No reply to Close request");
+               if (lsep && lsep->cfm && lsep->cfm->close)
+                       lsep->cfm->close(session, lsep, stream, &err,
+                                               lsep->user_data);
+               break;
+       case AVDTP_SET_CONFIGURATION:
+               error("No reply to SetConfiguration request");
+               if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+                       lsep->cfm->set_configuration(session, lsep, stream,
+                                                       &err, lsep->user_data);
+       }
+
+       pending_req_free(req);
+}
+
+static void avdtp_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_ERRNO, EIO);
+               DBG("stream state change failed: %s", avdtp_strerror(&err));
+               err_ptr = &err;
+       } else {
+               err_ptr = NULL;
+               DBG("stream state changed: %s -> %s",
+                               avdtp_statestr(sep->state),
+                               avdtp_statestr(state));
+       }
+
+       old_state = sep->state;
+       sep->state = state;
+
+       switch (state) {
+       case AVDTP_STATE_CONFIGURED:
+               if (sep->info.type == AVDTP_SEP_TYPE_SINK)
+                       avdtp_delay_report(session, stream, stream->delay);
+               break;
+       case AVDTP_STATE_OPEN:
+               stream->starting = FALSE;
+               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;
+               }
+               if (session->pending_open == stream)
+                       handle_transport_connect(session, NULL, 0, 0);
+               if (session->req && session->req->stream == stream)
+                       handle_unanswered_req(session, stream);
+               /* Remove pending commands for this stream from the queue */
+               cleanup_queue(session, stream);
+               break;
+       default:
+               break;
+       }
+
+       l = stream->callbacks;
+       while (l != NULL) {
+               struct stream_callback *cb = l->data;
+               l = g_slist_next(l);
+               cb->cb(stream, old_state, state, err_ptr, cb->user_data);
+       }
+
+       if (state == AVDTP_STATE_IDLE &&
+                               g_slist_find(session->streams, stream)) {
+               session->streams = g_slist_remove(session->streams, stream);
+               stream_free(stream);
+       }
+}
+
+static void finalize_discovery(struct avdtp *session, int err)
+{
+       struct avdtp_error avdtp_err;
+
+       avdtp_error_init(&avdtp_err, AVDTP_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 int avdtp_cancel_authorization(struct avdtp *session)
+{
+       struct audio_device *dev;
+
+       if (session->state != AVDTP_SESSION_STATE_CONNECTING)
+               return 0;
+
+       dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+       if (dev == NULL)
+               return -ENODEV;
+
+       return audio_device_cancel_authorization(dev, auth_cb, session);
+}
+
+static void connection_lost(struct avdtp *session, int err)
+{
+       char address[18];
+
+       ba2str(&session->dst, address);
+       DBG("Disconnected from %s", address);
+
+       if (err != EACCES)
+               avdtp_cancel_authorization(session);
+
+       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--;
+
+       DBG("%p: ref=%d", session, session->ref);
+
+       if (session->ref == 1) {
+               if (session->state == AVDTP_SESSION_STATE_CONNECTING &&
+                                                               session->io) {
+                       avdtp_cancel_authorization(session);
+                       g_io_channel_shutdown(session->io, TRUE, NULL);
+                       g_io_channel_unref(session->io);
+                       session->io = NULL;
+                       avdtp_set_state(session,
+                                       AVDTP_SESSION_STATE_DISCONNECTED);
+               }
+
+               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;
+
+       DBG("%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_free_full(session->seps, g_free);
+
+       g_free(session->buf);
+
+       g_free(session);
+}
+
+struct avdtp *avdtp_ref(struct avdtp *session)
+{
+       session->ref++;
+       DBG("%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;
+}
+
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+                                               struct avdtp_local_sep *lsep)
+{
+       GSList *l;
+
+       if (lsep->info.inuse)
+               return NULL;
+
+       for (l = session->seps; l != NULL; l = g_slist_next(l)) {
+               struct avdtp_remote_sep *sep = l->data;
+               struct avdtp_service_capability *cap;
+               struct avdtp_media_codec_capability *codec_data;
+
+               /* Type must be different: source <-> sink */
+               if (sep->type == lsep->info.type)
+                       continue;
+
+               if (sep->media_type != lsep->info.media_type)
+                       continue;
+
+               if (!sep->codec)
+                       continue;
+
+               cap = sep->codec;
+               codec_data = (void *) cap->data;
+
+               if (codec_data->media_codec_type != lsep->codec)
+                       continue;
+
+               if (sep->stream == NULL)
+                       return sep;
+       }
+
+       return NULL;
+}
+
+static GSList *caps_to_list(uint8_t *data, int size,
+                               struct avdtp_service_capability **codec,
+                               gboolean *delay_reporting)
+{
+       GSList *caps;
+       int processed;
+
+       if (delay_reporting)
+               *delay_reporting = FALSE;
+
+       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;
+               else if (category == AVDTP_DELAY_REPORTING && delay_reporting)
+                       *delay_reporting = TRUE;
+       }
+
+       return caps;
+}
+
+static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction,
+                                                       uint8_t signal_id)
+{
+       return avdtp_send(session, transaction, AVDTP_MSG_TYPE_GEN_REJECT,
+                                                       signal_id, NULL, 0);
+}
+
+static 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,
+                                       gboolean get_all)
+{
+       GSList *l, *caps;
+       struct avdtp_local_sep *sep = NULL;
+       unsigned int rsp_size;
+       uint8_t err, buf[1024], *ptr = buf;
+       uint8_t cmd;
+
+       cmd = get_all ? AVDTP_GET_ALL_CAPABILITIES : AVDTP_GET_CAPABILITIES;
+
+       if (size < sizeof(struct seid_req)) {
+               err = AVDTP_BAD_LENGTH;
+               goto failed;
+       }
+
+       sep = find_local_sep_by_seid(session->server, req->acp_seid);
+       if (!sep) {
+               err = AVDTP_BAD_ACP_SEID;
+               goto failed;
+       }
+
+       if (get_all && session->server->version < 0x0103)
+               return avdtp_unknown_cmd(session, transaction, cmd);
+
+       if (!sep->ind->get_capability(session, sep, get_all, &caps,
+                                                       &err, sep->user_data))
+               goto failed;
+
+       for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
+               struct avdtp_service_capability *cap = l->data;
+
+               if (rsp_size + cap->length + 2 > sizeof(buf))
+                       break;
+
+               memcpy(ptr, cap, cap->length + 2);
+               rsp_size += cap->length + 2;
+               ptr += cap->length + 2;
+
+               g_free(cap);
+       }
+
+       g_slist_free(caps);
+
+       return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, cmd,
+                                                               buf, rsp_size);
+
+failed:
+       return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, cmd,
+                                                       &err, sizeof(err));
+}
+
+static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream,
+                                               struct avdtp_error *err)
+{
+       struct conf_rej rej;
+       struct avdtp_local_sep *sep;
+
+       if (err != NULL) {
+               rej.error = AVDTP_UNSUPPORTED_CONFIGURATION;
+               rej.category = err->err.error_code;
+               avdtp_send(session, session->in.transaction,
+                               AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION,
+                               &rej, sizeof(rej));
+               return;
+       }
+
+       if (!avdtp_send(session, session->in.transaction, AVDTP_MSG_TYPE_ACCEPT,
+                                       AVDTP_SET_CONFIGURATION, NULL, 0)) {
+               stream_free(stream);
+               return;
+       }
+
+       sep = stream->lsep;
+       sep->stream = stream;
+       sep->info.inuse = 1;
+       session->streams = g_slist_append(session->streams, stream);
+
+       avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+}
+
+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");
+               err = AVDTP_BAD_STATE;
+               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");
+                               err = AVDTP_BAD_STATE;
+                               goto failed;
+                       }
+               }
+               break;
+       case AVDTP_SEP_TYPE_SINK:
+               if (!dev->source) {
+                       btd_device_add_uuid(dev->btd_dev, A2DP_SOURCE_UUID);
+                       if (!dev->sink) {
+                               error("Unable to get a audio source object");
+                               err = AVDTP_BAD_STATE;
+                               goto failed;
+                       }
+               }
+               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,
+                                       &stream->delay_reporting);
+
+       /* 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 (stream->delay_reporting && session->version < 0x0103)
+               session->version = 0x0103;
+
+       if (sep->ind && sep->ind->set_configuration) {
+               if (!sep->ind->set_configuration(session, sep, stream,
+                                                       stream->caps,
+                                                       setconf_cb,
+                                                       sep->user_data)) {
+                       err = AVDTP_UNSUPPORTED_CONFIGURATION;
+                       category = 0x00;
+                       goto failed_stream;
+               }
+       } else {
+               if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+                                       AVDTP_SET_CONFIGURATION, NULL, 0)) {
+                       stream_free(stream);
+                       return FALSE;
+               }
+
+               sep->stream = stream;
+               sep->info.inuse = 1;
+               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, AVDTP_RECONFIGURE);
+}
+
+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;
+
+               /* Also reject start cmd if we already initiated start */
+               if (sep->state != AVDTP_STATE_OPEN ||
+                                               stream->starting == TRUE) {
+                       err = AVDTP_BAD_STATE;
+                       goto failed;
+               }
+               stream->starting = TRUE;
+
+               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:
+       DBG("Rejecting (%d)", err);
+       memset(&rej, 0, sizeof(rej));
+       rej.acp_seid = failed_seid;
+       rej.error = err;
+       return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+                               AVDTP_START, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_close_cmd(struct avdtp *session, uint8_t transaction,
+                               struct seid_req *req, unsigned int size)
+{
+       struct avdtp_local_sep *sep;
+       struct avdtp_stream *stream;
+       uint8_t err;
+
+       if (size < sizeof(struct seid_req)) {
+               error("Too short close request");
+               return FALSE;
+       }
+
+       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, AVDTP_SECURITY_CONTROL);
+}
+
+static gboolean avdtp_delayreport_cmd(struct avdtp *session,
+                                       uint8_t transaction,
+                                       struct delay_req *req,
+                                       unsigned int size)
+{
+       struct avdtp_local_sep *sep;
+       struct avdtp_stream *stream;
+       uint8_t err;
+
+       if (size < sizeof(struct delay_req)) {
+               error("Too short delay report request");
+               return FALSE;
+       }
+
+       sep = find_local_sep_by_seid(session->server, req->acp_seid);
+       if (!sep || !sep->stream) {
+               err = AVDTP_BAD_ACP_SEID;
+               goto failed;
+       }
+
+       stream = sep->stream;
+
+       if (sep->state != AVDTP_STATE_CONFIGURED &&
+                                       sep->state != AVDTP_STATE_STREAMING) {
+               err = AVDTP_BAD_STATE;
+               goto failed;
+       }
+
+       stream->delay = ntohs(req->delay);
+
+       if (sep->ind && sep->ind->delayreport) {
+               if (!sep->ind->delayreport(session, sep, stream->rseid,
+                                               stream->delay, &err,
+                                               sep->user_data))
+                       goto failed;
+       }
+
+       return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+                                               AVDTP_DELAY_REPORT, NULL, 0);
+
+failed:
+       return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+                                       AVDTP_DELAY_REPORT, &err, sizeof(err));
+}
+
+static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
+                               uint8_t signal_id, void *buf, int size)
+{
+       switch (signal_id) {
+       case AVDTP_DISCOVER:
+               DBG("Received DISCOVER_CMD");
+               return avdtp_discover_cmd(session, transaction, buf, size);
+       case AVDTP_GET_CAPABILITIES:
+               DBG("Received  GET_CAPABILITIES_CMD");
+               return avdtp_getcap_cmd(session, transaction, buf, size,
+                                                                       FALSE);
+       case AVDTP_GET_ALL_CAPABILITIES:
+               DBG("Received  GET_ALL_CAPABILITIES_CMD");
+               return avdtp_getcap_cmd(session, transaction, buf, size, TRUE);
+       case AVDTP_SET_CONFIGURATION:
+               DBG("Received SET_CONFIGURATION_CMD");
+               return avdtp_setconf_cmd(session, transaction, buf, size);
+       case AVDTP_GET_CONFIGURATION:
+               DBG("Received GET_CONFIGURATION_CMD");
+               return avdtp_getconf_cmd(session, transaction, buf, size);
+       case AVDTP_RECONFIGURE:
+               DBG("Received RECONFIGURE_CMD");
+               return avdtp_reconf_cmd(session, transaction, buf, size);
+       case AVDTP_OPEN:
+               DBG("Received OPEN_CMD");
+               return avdtp_open_cmd(session, transaction, buf, size);
+       case AVDTP_START:
+               DBG("Received START_CMD");
+               return avdtp_start_cmd(session, transaction, buf, size);
+       case AVDTP_CLOSE:
+               DBG("Received CLOSE_CMD");
+               return avdtp_close_cmd(session, transaction, buf, size);
+       case AVDTP_SUSPEND:
+               DBG("Received SUSPEND_CMD");
+               return avdtp_suspend_cmd(session, transaction, buf, size);
+       case AVDTP_ABORT:
+               DBG("Received ABORT_CMD");
+               return avdtp_abort_cmd(session, transaction, buf, size);
+       case AVDTP_SECURITY_CONTROL:
+               DBG("Received SECURITY_CONTROL_CMD");
+               return avdtp_secctl_cmd(session, transaction, buf, size);
+       case AVDTP_DELAY_REPORT:
+               DBG("Received DELAY_REPORT_CMD");
+               return avdtp_delayreport_cmd(session, transaction, buf, size);
+       default:
+               DBG("Received unknown request id %u", signal_id);
+               return avdtp_unknown_cmd(session, transaction, signal_id);
+       }
+}
+
+enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS };
+
+static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session,
+                                                       void *buf, size_t size)
+{
+       struct avdtp_common_header *header = buf;
+       struct avdtp_single_header *single = (void *) session->buf;
+       struct avdtp_start_header *start = (void *) session->buf;
+       void *payload;
+       gsize payload_size;
+
+       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--;
+               DBG("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;
+       ssize_t size;
+       int fd;
+
+       DBG("");
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       header = (void *) session->buf;
+
+       if (cond & (G_IO_HUP | G_IO_ERR))
+               goto failed;
+
+       fd = g_io_channel_unix_get_fd(chan);
+       size = read(fd, session->buf, session->imtu);
+       if (size < 0) {
+               error("IO Channel read error");
+               goto failed;
+       }
+
+       if ((size_t) size < sizeof(struct avdtp_common_header)) {
+               error("Received too small packet (%zu bytes)", size);
+               goto failed;
+       }
+
+       switch (avdtp_parse_data(session, session->buf, size)) {
+       case PARSE_ERROR:
+               goto failed;
+       case PARSE_FRAGMENT:
+               return TRUE;
+       case PARSE_SUCCESS:
+               break;
+       }
+
+       if (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("Response 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;
+       case AVDTP_MSG_TYPE_GEN_REJECT:
+               error("Received a General Reject message");
+               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)
+{
+       for (; list != NULL; list = g_slist_next(list)) {
+               struct avdtp *s = list->data;
+
+               if (bacmp(dst, &s->dst))
+                       continue;
+
+               return s;
+       }
+
+       return NULL;
+}
+
+static uint16_t get_version(struct avdtp *session)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+       const sdp_record_t *rec;
+       sdp_list_t *protos;
+       sdp_data_t *proto_desc;
+       char addr[18];
+       uint16_t ver = 0x0100;
+
+       adapter = manager_find_adapter(&session->server->src);
+       if (!adapter)
+               return ver;
+
+       ba2str(&session->dst, addr);
+       device = adapter_find_device(adapter, addr);
+       if (!device)
+               return ver;
+
+       rec = btd_device_get_record(device, A2DP_SINK_UUID);
+       if (!rec)
+               rec = btd_device_get_record(device, A2DP_SOURCE_UUID);
+
+       if (!rec)
+               return ver;
+
+       if (sdp_get_access_protos(rec, &protos) < 0)
+               return ver;
+
+       proto_desc = sdp_get_proto_desc(protos, AVDTP_UUID);
+       if (proto_desc && proto_desc->dtd == SDP_UINT16)
+               ver = proto_desc->val.uint16;
+
+       sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+       sdp_list_free(protos, NULL);
+
+       return ver;
+}
+
+static 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;
+
+       session->version = get_version(session);
+
+       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);
+       DBG("AVDTP: connected %s channel to %s",
+                       session->pending_open ? "transport" : "signaling",
+                       address);
+
+       if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+               DBG("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu);
+
+               session->buf = g_malloc0(MAX(session->imtu, session->omtu));
+               avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED);
+
+               if (session->io_id)
+                       g_source_remove(session->io_id);
+
+               /* 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);
+}
+
+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;
+       }
+
+       DBG("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) {
+               DBG("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_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED);
+               avdtp_unref(session);
+               goto drop;
+       }
+
+       dev->auto_connect = auto_connect;
+
+       return;
+
+drop:
+       g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static GIOChannel *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 NULL;
+       }
+
+       return io;
+}
+
+static void queue_request(struct avdtp *session, struct pending_req *req,
+                       gboolean priority)
+{
+       if (priority)
+               session->prio_queue = g_slist_append(session->prio_queue, req);
+       else
+               session->req_queue = g_slist_append(session->req_queue, req);
+}
+
+static uint8_t req_get_seid(struct pending_req *req)
+{
+       if (req->signal_id == AVDTP_DISCOVER)
+               return 0;
+
+       return ((struct seid_req *) (req->data))->acp_seid;
+}
+
+static int cancel_request(struct avdtp *session, int err)
+{
+       struct pending_req *req;
+       struct seid_req sreq;
+       struct avdtp_local_sep *lsep;
+       struct avdtp_stream *stream;
+       uint8_t seid;
+       struct avdtp_error averr;
+
+       req = session->req;
+       session->req = NULL;
+
+       avdtp_error_init(&averr, AVDTP_ERRNO, err);
+
+       seid = req_get_seid(req);
+       if (seid)
+               stream = find_stream_by_rseid(session, seid);
+       else
+               stream = NULL;
+
+       if (stream) {
+               stream->abort_int = TRUE;
+               lsep = stream->lsep;
+       } else
+               lsep = NULL;
+
+       switch (req->signal_id) {
+       case AVDTP_RECONFIGURE:
+               error("Reconfigure: %s (%d)", strerror(err), err);
+               if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+                       lsep->cfm->reconfigure(session, lsep, stream, &averr,
+                                               lsep->user_data);
+               break;
+       case AVDTP_OPEN:
+               error("Open: %s (%d)", strerror(err), err);
+               if (lsep && lsep->cfm && lsep->cfm->open)
+                       lsep->cfm->open(session, lsep, stream, &averr,
+                                       lsep->user_data);
+               break;
+       case AVDTP_START:
+               error("Start: %s (%d)", strerror(err), err);
+               if (lsep && lsep->cfm && lsep->cfm->start) {
+                       lsep->cfm->start(session, lsep, stream, &averr,
+                                               lsep->user_data);
+                       if (stream)
+                               stream->starting = FALSE;
+               }
+               break;
+       case AVDTP_SUSPEND:
+               error("Suspend: %s (%d)", strerror(err), err);
+               if (lsep && lsep->cfm && lsep->cfm->suspend)
+                       lsep->cfm->suspend(session, lsep, stream, &averr,
+                                               lsep->user_data);
+               break;
+       case AVDTP_CLOSE:
+               error("Close: %s (%d)", strerror(err), err);
+               if (lsep && lsep->cfm && lsep->cfm->close) {
+                       lsep->cfm->close(session, lsep, stream, &averr,
+                                               lsep->user_data);
+                       if (stream)
+                               stream->close_int = FALSE;
+               }
+               break;
+       case AVDTP_SET_CONFIGURATION:
+               error("SetConfiguration: %s (%d)", strerror(err), err);
+               if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+                       lsep->cfm->set_configuration(session, lsep, stream,
+                                                       &averr, lsep->user_data);
+               goto failed;
+       case AVDTP_DISCOVER:
+               error("Discover: %s (%d)", strerror(err), err);
+               goto failed;
+       case AVDTP_GET_CAPABILITIES:
+               error("GetCapabilities: %s (%d)", strerror(err), err);
+               goto failed;
+       case AVDTP_ABORT:
+               error("Abort: %s (%d)", strerror(err), err);
+               goto failed;
+       }
+
+       if (!stream)
+               goto failed;
+
+       memset(&sreq, 0, sizeof(sreq));
+       sreq.acp_seid = seid;
+
+       err = send_request(session, TRUE, stream, AVDTP_ABORT, &sreq,
+                               sizeof(sreq));
+       if (err < 0) {
+               error("Unable to send abort request");
+               goto failed;
+       }
+
+       goto done;
+
+failed:
+       connection_lost(session, err);
+done:
+       pending_req_free(req);
+       return err;
+}
+
+static gboolean request_timeout(gpointer user_data)
+{
+       struct avdtp *session = user_data;
+
+       cancel_request(session, ETIMEDOUT);
+
+       return FALSE;
+}
+
+static int send_req(struct avdtp *session, gboolean priority,
+                       struct pending_req *req)
+{
+       static int transaction = 0;
+       int err;
+
+       if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) {
+               session->io = l2cap_connect(session);
+               if (!session->io) {
+                       err = -EIO;
+                       goto failed;
+               }
+               avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING);
+       }
+
+       if (session->state < AVDTP_SESSION_STATE_CONNECTED ||
+                       session->req != NULL) {
+               queue_request(session, req, priority);
+               return 0;
+       }
+
+       req->transaction = transaction++;
+       transaction %= 16;
+
+       /* FIXME: Should we retry to send if the buffer
+       was not totally sent or in case of EINTR? */
+       if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND,
+                               req->signal_id, req->data, req->data_size)) {
+               err = -EIO;
+               goto failed;
+       }
+
+       session->req = req;
+
+       req->timeout = g_timeout_add_seconds(req->signal_id == AVDTP_ABORT ?
+                                       ABORT_TIMEOUT : 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;
+
+       if (stream && stream->abort_int && signal_id != AVDTP_ABORT) {
+               DBG("Unable to send requests while aborting");
+               return -EINVAL;
+       }
+
+       req = g_new0(struct pending_req, 1);
+       req->signal_id = signal_id;
+       req->data = 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;
+       uint8_t getcap_cmd;
+       int ret = 0;
+       gboolean getcap_pending = FALSE;
+
+       if (session->version >= 0x0103 && session->server->version >= 0x0103)
+               getcap_cmd = AVDTP_GET_ALL_CAPABILITIES;
+       else
+               getcap_cmd = AVDTP_GET_CAPABILITIES;
+
+       sep_count = size / sizeof(struct seid_info);
+
+       for (i = 0; i < sep_count; i++) {
+               struct avdtp_remote_sep *sep;
+               struct avdtp_stream *stream;
+               struct seid_req req;
+
+               DBG("seid %d type %d media %d in use %d",
+                               resp->seps[i].seid, resp->seps[i].type,
+                               resp->seps[i].media_type, resp->seps[i].inuse);
+
+               stream = find_stream_by_rseid(session, resp->seps[i].seid);
+
+               sep = find_remote_sep(session->seps, resp->seps[i].seid);
+               if (!sep) {
+                       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, getcap_cmd,
+                                                       &req, sizeof(req));
+               if (ret < 0)
+                       break;
+               getcap_pending = TRUE;
+       }
+
+       if (!getcap_pending)
+               finalize_discovery(session, -ret);
+
+       return TRUE;
+}
+
+static gboolean avdtp_get_capabilities_resp(struct avdtp *session,
+                                               struct getcap_resp *resp,
+                                               unsigned int size)
+{
+       struct avdtp_remote_sep *sep;
+       uint8_t seid;
+
+       /* Check for minimum required packet size includes:
+        *   1. getcap resp header
+        *   2. media transport capability (2 bytes)
+        *   3. media codec capability type + length (2 bytes)
+        *   4. the actual media codec elements
+        * */
+       if (size < (sizeof(struct getcap_resp) + 4 +
+                               sizeof(struct avdtp_media_codec_capability))) {
+               error("Too short getcap resp packet");
+               return FALSE;
+       }
+
+       seid = ((struct seid_req *) session->req->data)->acp_seid;
+
+       sep = find_remote_sep(session->seps, seid);
+
+       DBG("seid %d type %d media %d", sep->seid,
+                                       sep->type, sep->media_type);
+
+       if (sep->caps) {
+               g_slist_free_full(sep->caps, g_free);
+               sep->caps = NULL;
+               sep->codec = NULL;
+               sep->delay_reporting = FALSE;
+       }
+
+       sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp),
+                                       &sep->codec, &sep->delay_reporting);
+
+       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;
+
+       stream->io = l2cap_connect(session);
+       if (!stream->io) {
+               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_delay_report_resp(struct avdtp *session,
+                                       struct avdtp_stream *stream,
+                                       void *data, int size)
+{
+       struct avdtp_local_sep *sep = stream->lsep;
+
+       if (sep->cfm && sep->cfm->delay_report)
+               sep->cfm->delay_report(session, sep, stream, NULL, sep->user_data);
+
+       return TRUE;
+}
+
+static gboolean avdtp_parse_resp(struct avdtp *session,
+                                       struct avdtp_stream *stream,
+                                       uint8_t transaction, uint8_t signal_id,
+                                       void *buf, int size)
+{
+       struct pending_req *next;
+       const char *get_all = "";
+
+       if (session->prio_queue)
+               next = session->prio_queue->data;
+       else if (session->req_queue)
+               next = session->req_queue->data;
+       else
+               next = NULL;
+
+       switch (signal_id) {
+       case AVDTP_DISCOVER:
+               DBG("DISCOVER request succeeded");
+               return avdtp_discover_resp(session, buf, size);
+       case AVDTP_GET_ALL_CAPABILITIES:
+               get_all = "ALL_";
+       case AVDTP_GET_CAPABILITIES:
+               DBG("GET_%sCAPABILITIES request succeeded", get_all);
+               if (!avdtp_get_capabilities_resp(session, buf, size))
+                       return FALSE;
+               if (!(next && (next->signal_id == AVDTP_GET_CAPABILITIES ||
+                               next->signal_id == AVDTP_GET_ALL_CAPABILITIES)))
+                       finalize_discovery(session, 0);
+               return TRUE;
+       }
+
+       /* The remaining commands require an existing stream so bail out
+        * here if the stream got unexpectedly disconnected */
+       if (!stream) {
+               DBG("AVDTP: stream was closed while waiting for reply");
+               return TRUE;
+       }
+
+       switch (signal_id) {
+       case AVDTP_SET_CONFIGURATION:
+               DBG("SET_CONFIGURATION request succeeded");
+               return avdtp_set_configuration_resp(session, stream,
+                                                               buf, size);
+       case AVDTP_RECONFIGURE:
+               DBG("RECONFIGURE request succeeded");
+               return avdtp_reconfigure_resp(session, stream, buf, size);
+       case AVDTP_OPEN:
+               DBG("OPEN request succeeded");
+               return avdtp_open_resp(session, stream, buf, size);
+       case AVDTP_SUSPEND:
+               DBG("SUSPEND request succeeded");
+               return avdtp_suspend_resp(session, stream, buf, size);
+       case AVDTP_START:
+               DBG("START request succeeded");
+               return avdtp_start_resp(session, stream, buf, size);
+       case AVDTP_CLOSE:
+               DBG("CLOSE request succeeded");
+               return avdtp_close_resp(session, stream, buf, size);
+       case AVDTP_ABORT:
+               DBG("ABORT request succeeded");
+               return avdtp_abort_resp(session, stream, buf, size);
+       case AVDTP_DELAY_REPORT:
+               DBG("DELAY_REPORT request succeeded");
+               return avdtp_delay_report_resp(session, stream, buf, size);
+       }
+
+       error("Unknown signal id in accept response: %u", signal_id);
+       return TRUE;
+}
+
+static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned int size,
+                                       struct avdtp_error *err)
+{
+       if (size < sizeof(struct seid_rej)) {
+               error("Too small packet for seid_rej");
+               return FALSE;
+       }
+
+       avdtp_error_init(err, 0x00, rej->error);
+
+       return TRUE;
+}
+
+static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size,
+                               struct avdtp_error *err)
+{
+       if (size < sizeof(struct conf_rej)) {
+               error("Too small packet for conf_rej");
+               return FALSE;
+       }
+
+       avdtp_error_init(err, rej->category, rej->error);
+
+       return TRUE;
+}
+
+static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size,
+                                       struct avdtp_error *err,
+                                       uint8_t *acp_seid)
+{
+       if (size < sizeof(struct stream_rej)) {
+               error("Too small packet for stream_rej");
+               return FALSE;
+       }
+
+       avdtp_error_init(err, 0x00, rej->error);
+
+       if (acp_seid)
+               *acp_seid = rej->acp_seid;
+
+       return TRUE;
+}
+
+static gboolean avdtp_parse_rej(struct avdtp *session,
+                                       struct avdtp_stream *stream,
+                                       uint8_t transaction, uint8_t signal_id,
+                                       void *buf, int size)
+{
+       struct avdtp_error err;
+       uint8_t acp_seid;
+       struct avdtp_local_sep *sep = stream ? stream->lsep : NULL;
+
+       switch (signal_id) {
+       case AVDTP_DISCOVER:
+               if (!seid_rej_to_err(buf, size, &err))
+                       return FALSE;
+               error("DISCOVER request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               return TRUE;
+       case AVDTP_GET_CAPABILITIES:
+       case AVDTP_GET_ALL_CAPABILITIES:
+               if (!seid_rej_to_err(buf, size, &err))
+                       return FALSE;
+               error("GET_CAPABILITIES request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               return TRUE;
+       case AVDTP_OPEN:
+               if (!seid_rej_to_err(buf, size, &err))
+                       return FALSE;
+               error("OPEN request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               if (sep && sep->cfm && sep->cfm->open)
+                       sep->cfm->open(session, sep, stream, &err,
+                                       sep->user_data);
+               return TRUE;
+       case AVDTP_SET_CONFIGURATION:
+               if (!conf_rej_to_err(buf, size, &err))
+                       return FALSE;
+               error("SET_CONFIGURATION request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               if (sep && sep->cfm && sep->cfm->set_configuration)
+                       sep->cfm->set_configuration(session, sep, stream,
+                                                       &err, sep->user_data);
+               return TRUE;
+       case AVDTP_RECONFIGURE:
+               if (!conf_rej_to_err(buf, size, &err))
+                       return FALSE;
+               error("RECONFIGURE request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               if (sep && sep->cfm && sep->cfm->reconfigure)
+                       sep->cfm->reconfigure(session, sep, stream, &err,
+                                               sep->user_data);
+               return TRUE;
+       case AVDTP_START:
+               if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+                       return FALSE;
+               error("START request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               if (sep && sep->cfm && sep->cfm->start) {
+                       sep->cfm->start(session, sep, stream, &err,
+                                       sep->user_data);
+                       stream->starting = FALSE;
+               }
+               return TRUE;
+       case AVDTP_SUSPEND:
+               if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+                       return FALSE;
+               error("SUSPEND request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               if (sep && sep->cfm && sep->cfm->suspend)
+                       sep->cfm->suspend(session, sep, stream, &err,
+                                               sep->user_data);
+               return TRUE;
+       case AVDTP_CLOSE:
+               if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+                       return FALSE;
+               error("CLOSE request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               if (sep && sep->cfm && sep->cfm->close) {
+                       sep->cfm->close(session, sep, stream, &err,
+                                       sep->user_data);
+                       stream->close_int = FALSE;
+               }
+               return TRUE;
+       case AVDTP_ABORT:
+               if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+                       return FALSE;
+               error("ABORT request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               if (sep && sep->cfm && sep->cfm->abort)
+                       sep->cfm->abort(session, sep, stream, &err,
+                                       sep->user_data);
+               return FALSE;
+       case AVDTP_DELAY_REPORT:
+               if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+                       return FALSE;
+               error("DELAY_REPORT request rejected: %s (%d)",
+                               avdtp_strerror(&err), err.err.error_code);
+               if (sep && sep->cfm && sep->cfm->delay_report)
+                       sep->cfm->delay_report(session, sep, stream, &err,
+                                                       sep->user_data);
+               return TRUE;
+       default:
+               error("Unknown reject response signal id: %u", signal_id);
+               return TRUE;
+       }
+}
+
+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)
+{
+       for (; caps; caps = g_slist_next(caps)) {
+               struct avdtp_service_capability *cap = caps->data;
+
+               if (!avdtp_stream_has_capability(stream, cap))
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
+                                               struct avdtp_stream *stream)
+{
+       return avdtp_get_remote_sep(stream->session, stream->rseid);
+}
+
+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;
+}
+
+gboolean avdtp_get_delay_reporting(struct avdtp_remote_sep *sep)
+{
+       return sep->delay_reporting;
+}
+
+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_DELAY_REPORTING)
+               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 err;
+
+       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;
+       }
+
+       err = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0);
+       if (err == 0) {
+               session->discov_cb = cb;
+               session->user_data = user_data;
+       }
+
+       return err;
+}
+
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+                               struct avdtp_stream *stream,
+                               unsigned int id)
+{
+       GSList *l;
+       struct stream_callback *cb;
+
+       if (!stream)
+               return FALSE;
+
+       for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) {
+               struct stream_callback *tmp = l->data;
+               if (tmp && tmp->id == id) {
+                       cb = tmp;
+                       break;
+               }
+       }
+
+       if (!cb)
+               return FALSE;
+
+       stream->callbacks = g_slist_remove(stream->callbacks, cb);
+       g_free(cb);
+
+       return TRUE;
+}
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+                                       struct avdtp_stream *stream,
+                                       avdtp_stream_state_cb cb, void *data)
+{
+       struct stream_callback *stream_cb;
+       static unsigned int id = 0;
+
+       stream_cb = g_new(struct stream_callback, 1);
+       stream_cb->cb = cb;
+       stream_cb->user_data = data;
+       stream_cb->id = ++id;
+
+       stream->callbacks = g_slist_append(stream->callbacks, stream_cb);
+
+       return stream_cb->id;
+}
+
+int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream)
+{
+       struct seid_req req;
+
+       if (session->state < AVDTP_SESSION_STATE_CONNECTED)
+               return -EINVAL;
+
+       memset(&req, 0, sizeof(req));
+       req.acp_seid = stream->rseid;
+
+       return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION,
+                                                       &req, sizeof(req));
+}
+
+static void copy_capabilities(gpointer data, gpointer user_data)
+{
+       struct avdtp_service_capability *src_cap = data;
+       struct avdtp_service_capability *dst_cap;
+       GSList **l = user_data;
+
+       dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data,
+                                       src_cap->length);
+
+       *l = g_slist_append(*l, dst_cap);
+}
+
+int avdtp_set_configuration(struct avdtp *session,
+                               struct avdtp_remote_sep *rsep,
+                               struct avdtp_local_sep *lsep,
+                               GSList *caps,
+                               struct avdtp_stream **stream)
+{
+       struct setconf_req *req;
+       struct avdtp_stream *new_stream;
+       unsigned char *ptr;
+       int err, caps_len;
+       struct avdtp_service_capability *cap;
+       GSList *l;
+
+       if (session->state != AVDTP_SESSION_STATE_CONNECTED)
+               return -ENOTCONN;
+
+       if (!(lsep && rsep))
+               return -EINVAL;
+
+       DBG("%p: int_seid=%u, acp_seid=%u", session,
+                       lsep->info.seid, rsep->seid);
+
+       new_stream = g_new0(struct avdtp_stream, 1);
+       new_stream->session = session;
+       new_stream->lsep = lsep;
+       new_stream->rseid = rsep->seid;
+
+       if (rsep->delay_reporting && lsep->delay_reporting) {
+               struct avdtp_service_capability *delay_reporting;
+
+               delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+                                                               NULL, 0);
+               caps = g_slist_append(caps, delay_reporting);
+               new_stream->delay_reporting = TRUE;
+       }
+
+       g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
+
+       /* Calculate total size of request */
+       for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
+               cap = l->data;
+               caps_len += cap->length + 2;
+       }
+
+       req = g_malloc0(sizeof(struct setconf_req) + caps_len);
+
+       req->int_seid = lsep->info.seid;
+       req->acp_seid = rsep->seid;
+
+       /* Copy the capabilities into the request */
+       for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
+               cap = l->data;
+               memcpy(ptr, cap, cap->length + 2);
+               ptr += cap->length + 2;
+       }
+
+       err = send_request(session, FALSE, new_stream,
+                               AVDTP_SET_CONFIGURATION, req,
+                               sizeof(struct setconf_req) + caps_len);
+       if (err < 0)
+               stream_free(new_stream);
+       else {
+               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 err;
+}
+
+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;
+       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_start: rejecting start since close is initiated");
+               return -EINVAL;
+       }
+
+       if (stream->starting == TRUE) {
+               DBG("stream already started");
+               return -EINVAL;
+       }
+
+       memset(&req, 0, sizeof(req));
+       req.first_seid.seid = stream->rseid;
+
+       ret = send_request(session, FALSE, stream, AVDTP_START,
+                                                       &req, sizeof(req));
+       if (ret == 0)
+               stream->starting = TRUE;
+
+       return ret;
+}
+
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream,
+               gboolean immediate)
+{
+       struct seid_req req;
+       int ret;
+
+       if (!g_slist_find(session->streams, stream))
+               return -EINVAL;
+
+       if (stream->lsep->state < AVDTP_STATE_OPEN)
+               return -EINVAL;
+
+       if (stream->close_int == TRUE) {
+               error("avdtp_close: rejecting since close is already initiated");
+               return -EINVAL;
+       }
+
+       if (immediate && session->req && stream == session->req->stream)
+               return avdtp_abort(session, stream);
+
+       memset(&req, 0, sizeof(req));
+       req.acp_seid = stream->rseid;
+
+       ret = send_request(session, FALSE, stream, AVDTP_CLOSE,
+                                                       &req, sizeof(req));
+       if (ret == 0)
+               stream->close_int = TRUE;
+
+       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_ABORTING)
+               return -EINVAL;
+
+       if (session->req && stream == session->req->stream)
+               return cancel_request(session, ECANCELED);
+
+       memset(&req, 0, sizeof(req));
+       req.acp_seid = stream->rseid;
+
+       ret = send_request(session, TRUE, stream, AVDTP_ABORT,
+                                                       &req, sizeof(req));
+       if (ret == 0)
+               stream->abort_int = TRUE;
+
+       return ret;
+}
+
+int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream,
+                                                       uint16_t delay)
+{
+       struct delay_req req;
+
+       if (!g_slist_find(session->streams, stream))
+               return -EINVAL;
+
+       if (stream->lsep->state != AVDTP_STATE_CONFIGURED &&
+                               stream->lsep->state != AVDTP_STATE_STREAMING)
+               return -EINVAL;
+
+       if (!stream->delay_reporting || session->version < 0x0103 ||
+                                       session->server->version < 0x0103)
+               return -EINVAL;
+
+       stream->delay = delay;
+
+       memset(&req, 0, sizeof(req));
+       req.acp_seid = stream->rseid;
+       req.delay = htons(delay);
+
+       return send_request(session, TRUE, stream, AVDTP_DELAY_REPORT,
+                                                       &req, sizeof(req));
+}
+
+struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
+                                               uint8_t media_type,
+                                               uint8_t codec_type,
+                                               gboolean delay_reporting,
+                                               struct avdtp_sep_ind *ind,
+                                               struct avdtp_sep_cfm *cfm,
+                                               void *user_data)
+{
+       struct avdtp_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;
+       sep->delay_reporting = TRUE;
+
+       DBG("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);
+
+       DBG("SEP %p unregistered: type:%d codec:%d seid:%d", sep,
+                       sep->info.type, sep->codec, sep->info.seid);
+
+       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->category == AVDTP_ERRNO)
+               return strerror(err->err.posix_errno);
+
+       switch(err->err.error_code) {
+       case AVDTP_BAD_HEADER_FORMAT:
+               return "Bad Header Format";
+       case AVDTP_BAD_LENGTH:
+               return "Bad Packet Length";
+       case AVDTP_BAD_ACP_SEID:
+               return "Bad Acceptor SEID";
+       case AVDTP_SEP_IN_USE:
+               return "Stream End Point in Use";
+       case AVDTP_SEP_NOT_IN_USE:
+               return "Stream End Point Not in Use";
+       case AVDTP_BAD_SERV_CATEGORY:
+               return "Bad Service Category";
+       case AVDTP_BAD_PAYLOAD_FORMAT:
+               return "Bad Payload format";
+       case AVDTP_NOT_SUPPORTED_COMMAND:
+               return "Command Not Supported";
+       case AVDTP_INVALID_CAPABILITIES:
+               return "Invalid Capabilities";
+       case AVDTP_BAD_RECOVERY_TYPE:
+               return "Bad Recovery Type";
+       case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT:
+               return "Bad Media Transport Format";
+       case AVDTP_BAD_RECOVERY_FORMAT:
+               return "Bad Recovery Format";
+       case AVDTP_BAD_ROHC_FORMAT:
+               return "Bad Header Compression Format";
+       case AVDTP_BAD_CP_FORMAT:
+               return "Bad Content 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, uint16_t *version)
+{
+       GError *err = NULL;
+       gboolean tmp, master = TRUE;
+       struct avdtp_server *server;
+       uint16_t ver = 0x0102;
+
+       if (!config)
+               goto proceed;
+
+       tmp = g_key_file_get_boolean(config, "General",
+                       "Master", &err);
+       if (err) {
+               DBG("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;
+
+       if (g_key_file_get_boolean(config, "A2DP", "DelayReporting", NULL))
+               ver = 0x0103;
+
+proceed:
+       server = g_new0(struct avdtp_server, 1);
+       if (!server)
+               return -ENOMEM;
+
+       server->version = ver;
+
+       if (version)
+               *version = server->version;
+
+       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;
+
+       l = server->sessions;
+       while (l) {
+               struct avdtp *session = l->data;
+
+               l = l->next;
+               /* value of l pointer should be updated before invoking
+                * connection_lost since it internally uses avdtp_unref
+                * which operates on server->session list as well
+                */
+               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;
+}
+
+void avdtp_set_device_disconnect(struct avdtp *session, gboolean dev_dc)
+{
+       session->device_disconnect = dev_dc;
+}
+
+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 (file)
index 0000000..5f37dc3
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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_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 {
+       uint8_t category;
+       union {
+               uint8_t error_code;
+               int posix_errno;
+       } err;
+};
+
+/* SEP capability categories */
+#define AVDTP_MEDIA_TRANSPORT                  0x01
+#define AVDTP_REPORTING                                0x02
+#define AVDTP_RECOVERY                         0x03
+#define AVDTP_CONTENT_PROTECTION               0x04
+#define AVDTP_HEADER_COMPRESSION               0x05
+#define AVDTP_MULTIPLEXING                     0x06
+#define AVDTP_MEDIA_CODEC                      0x07
+#define AVDTP_DELAY_REPORTING                  0x08
+#define AVDTP_ERRNO                            0xff
+
+/* AVDTP error definitions */
+#define AVDTP_BAD_HEADER_FORMAT                        0x01
+#define AVDTP_BAD_LENGTH                       0x11
+#define AVDTP_BAD_ACP_SEID                     0x12
+#define AVDTP_SEP_IN_USE                       0x13
+#define AVDTP_SEP_NOT_IN_USE                   0x14
+#define AVDTP_BAD_SERV_CATEGORY                        0x17
+#define AVDTP_BAD_PAYLOAD_FORMAT               0x18
+#define AVDTP_NOT_SUPPORTED_COMMAND            0x19
+#define AVDTP_INVALID_CAPABILITIES             0x1A
+#define AVDTP_BAD_RECOVERY_TYPE                        0x22
+#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT       0x23
+#define AVDTP_BAD_RECOVERY_FORMAT              0x25
+#define AVDTP_BAD_ROHC_FORMAT                  0x26
+#define AVDTP_BAD_CP_FORMAT                    0x27
+#define AVDTP_BAD_MULTIPLEXING_FORMAT          0x28
+#define AVDTP_UNSUPPORTED_CONFIGURATION                0x29
+#define AVDTP_BAD_STATE                                0x31
+
+/* SEP types definitions */
+#define AVDTP_SEP_TYPE_SOURCE                  0x00
+#define AVDTP_SEP_TYPE_SINK                    0x01
+
+/* Media types definitions */
+#define AVDTP_MEDIA_TYPE_AUDIO                 0x00
+#define AVDTP_MEDIA_TYPE_VIDEO                 0x01
+#define AVDTP_MEDIA_TYPE_MULTIMEDIA            0x02
+
+typedef enum {
+       AVDTP_STATE_IDLE,
+       AVDTP_STATE_CONFIGURED,
+       AVDTP_STATE_OPEN,
+       AVDTP_STATE_STREAMING,
+       AVDTP_STATE_CLOSING,
+       AVDTP_STATE_ABORTING,
+} avdtp_state_t;
+
+struct avdtp_service_capability {
+       uint8_t category;
+       uint8_t length;
+       uint8_t data[0];
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_media_codec_capability {
+       uint8_t rfa0:4;
+       uint8_t media_type:4;
+       uint8_t media_codec_type;
+       uint8_t data[0];
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_media_codec_capability {
+       uint8_t media_type:4;
+       uint8_t rfa0:4;
+       uint8_t media_codec_type;
+       uint8_t data[0];
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+typedef void (*avdtp_session_state_cb) (struct 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);
+
+typedef void (*avdtp_set_configuration_cb) (struct avdtp *session,
+                                               struct avdtp_stream *stream,
+                                               struct avdtp_error *err);
+
+/* Callbacks for when a reply is received to a command that we sent */
+struct avdtp_sep_cfm {
+       void (*set_configuration) (struct avdtp *session,
+                                       struct avdtp_local_sep *lsep,
+                                       struct avdtp_stream *stream,
+                                       struct avdtp_error *err,
+                                       void *user_data);
+       void (*get_configuration) (struct avdtp *session,
+                                       struct avdtp_local_sep *lsep,
+                                       struct avdtp_stream *stream,
+                                       struct avdtp_error *err,
+                                       void *user_data);
+       void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+                       struct avdtp_stream *stream, struct avdtp_error *err,
+                       void *user_data);
+       void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+                       struct avdtp_stream *stream, struct avdtp_error *err,
+                       void *user_data);
+       void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep,
+                               struct avdtp_stream *stream,
+                               struct avdtp_error *err, void *user_data);
+       void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep,
+                               struct avdtp_stream *stream,
+                               struct avdtp_error *err, void *user_data);
+       void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep,
+                               struct avdtp_stream *stream,
+                               struct avdtp_error *err, void *user_data);
+       void (*reconfigure) (struct avdtp *session,
+                               struct avdtp_local_sep *lsep,
+                               struct avdtp_stream *stream,
+                               struct avdtp_error *err, void *user_data);
+       void (*delay_report) (struct avdtp *session, struct avdtp_local_sep *lsep,
+                               struct avdtp_stream *stream,
+                               struct avdtp_error *err, void *user_data);
+};
+
+/* Callbacks for indicating when we received a new command. The return value
+ * indicates whether the command should be rejected or accepted */
+struct avdtp_sep_ind {
+       gboolean (*get_capability) (struct avdtp *session,
+                                       struct avdtp_local_sep *sep,
+                                       gboolean get_all,
+                                       GSList **caps, uint8_t *err,
+                                       void *user_data);
+       gboolean (*set_configuration) (struct avdtp *session,
+                                       struct avdtp_local_sep *lsep,
+                                       struct avdtp_stream *stream,
+                                       GSList *caps,
+                                       avdtp_set_configuration_cb cb,
+                                       void *user_data);
+       gboolean (*get_configuration) (struct avdtp *session,
+                                       struct avdtp_local_sep *lsep,
+                                       uint8_t *err, void *user_data);
+       gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+                               struct avdtp_stream *stream, uint8_t *err,
+                               void *user_data);
+       gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+                               struct avdtp_stream *stream, uint8_t *err,
+                               void *user_data);
+       gboolean (*suspend) (struct avdtp *session,
+                               struct avdtp_local_sep *sep,
+                               struct avdtp_stream *stream, uint8_t *err,
+                               void *user_data);
+       gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep,
+                               struct avdtp_stream *stream, uint8_t *err,
+                               void *user_data);
+       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);
+       gboolean (*delayreport) (struct avdtp *session,
+                                       struct avdtp_local_sep *lsep,
+                                       uint8_t rseid, uint16_t delay,
+                                       uint8_t *err, void *user_data);
+};
+
+typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps,
+                                       struct avdtp_error *err, void *user_data);
+
+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);
+
+gboolean avdtp_get_delay_reporting(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);
+struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
+                                               struct avdtp_stream *stream);
+
+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,
+               gboolean immediate);
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream,
+                                                       uint16_t delay);
+
+struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
+                                               uint8_t media_type,
+                                               uint8_t codec_type,
+                                               gboolean delay_reporting,
+                                               struct avdtp_sep_ind *ind,
+                                               struct avdtp_sep_cfm *cfm,
+                                               void *user_data);
+
+/* Find a matching pair of local and remote SEP ID's */
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+                                               struct avdtp_local_sep *lsep);
+
+int avdtp_unregister_sep(struct 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);
+uint8_t avdtp_error_category(struct avdtp_error *err);
+int avdtp_error_error_code(struct avdtp_error *err);
+int avdtp_error_posix_errno(struct avdtp_error *err);
+
+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);
+void avdtp_set_device_disconnect(struct avdtp *session, gboolean dev_dc);
+
+int avdtp_init(const bdaddr_t *src, GKeyFile *config, uint16_t *version);
+void avdtp_exit(const bdaddr_t *src);
diff --git a/audio/avrcp.c b/audio/avrcp.c
new file mode 100644 (file)
index 0000000..29891fd
--- /dev/null
@@ -0,0 +1,1312 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2011  Texas Instruments, 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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "error.h"
+#include "device.h"
+#include "manager.h"
+#include "avctp.h"
+#include "avrcp.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+#include "dbus-common.h"
+
+/* Company IDs for vendor dependent commands */
+#define IEEEID_BTSIG           0x001958
+
+/* Error codes for metadata transfer */
+#define E_INVALID_COMMAND      0x00
+#define E_INVALID_PARAM                0x01
+#define E_PARAM_NOT_FOUND      0x02
+#define E_INTERNAL             0x03
+
+/* Packet types */
+#define AVRCP_PACKET_TYPE_SINGLE       0x00
+#define AVRCP_PACKET_TYPE_START                0x01
+#define AVRCP_PACKET_TYPE_CONTINUING   0x02
+#define AVRCP_PACKET_TYPE_END          0x03
+
+/* PDU types for metadata transfer */
+#define AVRCP_GET_CAPABILITIES         0x10
+#define AVRCP_LIST_PLAYER_ATTRIBUTES   0X11
+#define AVRCP_LIST_PLAYER_VALUES       0x12
+#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13
+#define AVRCP_SET_PLAYER_VALUE         0x14
+#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT        0x15
+#define AVRCP_GET_PLAYER_VALUE_TEXT    0x16
+#define AVRCP_DISPLAYABLE_CHARSET      0x17
+#define AVRCP_CT_BATTERY_STATUS                0x18
+#define AVRCP_GET_ELEMENT_ATTRIBUTES   0x20
+#define AVRCP_GET_PLAY_STATUS          0x30
+#define AVRCP_REGISTER_NOTIFICATION    0x31
+#define AVRCP_REQUEST_CONTINUING       0x40
+#define AVRCP_ABORT_CONTINUING         0x41
+
+/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
+#define CAP_COMPANY_ID         0x02
+#define CAP_EVENTS_SUPPORTED   0x03
+
+enum battery_status {
+       BATTERY_STATUS_NORMAL =         0,
+       BATTERY_STATUS_WARNING =        1,
+       BATTERY_STATUS_CRITICAL =       2,
+       BATTERY_STATUS_EXTERNAL =       3,
+       BATTERY_STATUS_FULL_CHARGE =    4,
+};
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avrcp_header {
+       uint8_t company_id[3];
+       uint8_t pdu_id;
+       uint8_t packet_type:2;
+       uint8_t rsvd:6;
+       uint16_t params_len;
+       uint8_t params[0];
+} __attribute__ ((packed));
+#define AVRCP_HEADER_LENGTH 7
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avrcp_header {
+       uint8_t company_id[3];
+       uint8_t pdu_id;
+       uint8_t rsvd:6;
+       uint8_t packet_type:2;
+       uint16_t params_len;
+       uint8_t params[0];
+} __attribute__ ((packed));
+#define AVRCP_HEADER_LENGTH 7
+
+#else
+#error "Unknown byte order"
+#endif
+
+#define AVRCP_MTU      (AVC_MTU - AVC_HEADER_LENGTH)
+#define AVRCP_PDU_MTU  (AVRCP_MTU - AVRCP_HEADER_LENGTH)
+
+struct avrcp_server {
+       bdaddr_t src;
+       uint32_t tg_record_id;
+       uint32_t ct_record_id;
+       GSList *players;
+       struct avrcp_player *active_player;
+};
+
+struct pending_pdu {
+       uint8_t pdu_id;
+       GList *attr_ids;
+       uint16_t offset;
+};
+
+struct avrcp_player {
+       struct avrcp_server *server;
+       struct avctp *session;
+       struct audio_device *dev;
+
+       unsigned int handler;
+       uint16_t registered_events;
+       uint8_t transaction_events[AVRCP_EVENT_LAST + 1];
+       struct pending_pdu *pending_pdu;
+
+       struct avrcp_player_cb *cb;
+       void *user_data;
+       GDestroyNotify destroy;
+};
+
+static GSList *servers = NULL;
+static unsigned int avctp_id = 0;
+
+/* Company IDs supported by this device */
+static uint32_t company_ids[] = {
+       IEEEID_BTSIG,
+};
+
+static sdp_record_t *avrcp_ct_record(void)
+{
+       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;
+       uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f;
+
+       record = sdp_record_alloc();
+       if (!record)
+               return NULL;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(record, root);
+
+       /* 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, &avctp_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 = avrcp_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(void)
+{
+       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;
+       uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f;
+
+       record = sdp_record_alloc();
+       if (!record)
+               return NULL;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(record, root);
+
+       /* 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, &avctp_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 = avrcp_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 unsigned int attr_get_max_val(uint8_t attr)
+{
+       switch (attr) {
+       case AVRCP_ATTRIBUTE_EQUALIZER:
+               return AVRCP_EQUALIZER_ON;
+       case AVRCP_ATTRIBUTE_REPEAT_MODE:
+               return AVRCP_REPEAT_MODE_GROUP;
+       case AVRCP_ATTRIBUTE_SHUFFLE:
+               return AVRCP_SHUFFLE_GROUP;
+       case AVRCP_ATTRIBUTE_SCAN:
+               return AVRCP_SCAN_GROUP;
+       }
+
+       return 0;
+}
+
+static const char *battery_status_to_str(enum battery_status status)
+{
+       switch (status) {
+       case BATTERY_STATUS_NORMAL:
+               return "normal";
+       case BATTERY_STATUS_WARNING:
+               return "warning";
+       case BATTERY_STATUS_CRITICAL:
+               return "critical";
+       case BATTERY_STATUS_EXTERNAL:
+               return "external";
+       case BATTERY_STATUS_FULL_CHARGE:
+               return "fullcharge";
+       }
+
+       return NULL;
+}
+
+/*
+ * get_company_id:
+ *
+ * Get three-byte Company_ID from incoming AVRCP message
+ */
+static uint32_t get_company_id(const uint8_t cid[3])
+{
+       return cid[0] << 16 | cid[1] << 8 | cid[2];
+}
+
+/*
+ * set_company_id:
+ *
+ * Set three-byte Company_ID into outgoing AVRCP message
+ */
+static void set_company_id(uint8_t cid[3], const uint32_t cid_in)
+{
+       cid[0] = cid_in >> 16;
+       cid[1] = cid_in >> 8;
+       cid[2] = cid_in;
+}
+
+int avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data)
+{
+       uint8_t buf[AVRCP_HEADER_LENGTH + 9];
+       struct avrcp_header *pdu = (void *) buf;
+       uint16_t size;
+       int err;
+
+       if (player->session == NULL)
+               return -ENOTCONN;
+
+       if (!(player->registered_events & (1 << id)))
+               return 0;
+
+       memset(buf, 0, sizeof(buf));
+
+       set_company_id(pdu->company_id, IEEEID_BTSIG);
+
+       pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION;
+       pdu->params[0] = id;
+
+       DBG("id=%u", id);
+
+       switch (id) {
+       case AVRCP_EVENT_STATUS_CHANGED:
+               size = 2;
+               pdu->params[1] = *((uint8_t *)data);
+
+               break;
+       case AVRCP_EVENT_TRACK_CHANGED:
+               size = 9;
+               memcpy(&pdu->params[1], data, sizeof(uint64_t));
+
+               break;
+       case AVRCP_EVENT_TRACK_REACHED_START:
+               size = 1;
+               break;
+       default:
+               error("Unknown event %u", id);
+               return -EINVAL;
+       }
+
+       pdu->params_len = htons(size);
+
+       err = avctp_send_vendordep(player->session, player->transaction_events[id],
+                                       AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL,
+                                       buf, size + AVRCP_HEADER_LENGTH);
+       if (err < 0)
+               return err;
+
+       /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
+       player->registered_events ^= 1 << id;
+
+       return 0;
+}
+
+static uint16_t player_write_media_attribute(struct avrcp_player *player,
+                                               uint32_t id, uint8_t *buf,
+                                               uint16_t *pos,
+                                               uint16_t *offset)
+{
+       uint16_t len;
+       uint16_t attr_len;
+       char valstr[20];
+       void *value;
+
+       DBG("%u", id);
+
+       value = player->cb->get_metadata(id, player->user_data);
+       if (value == NULL) {
+               *offset = 0;
+               return 0;
+       }
+
+       switch (id) {
+       case AVRCP_MEDIA_ATTRIBUTE_TRACK:
+       case AVRCP_MEDIA_ATTRIBUTE_N_TRACKS:
+       case AVRCP_MEDIA_ATTRIBUTE_DURATION:
+               snprintf(valstr, 20, "%u", GPOINTER_TO_UINT(value));
+               value = valstr;
+               break;
+       }
+
+       attr_len = strlen(value);
+       value = ((char *) value) + *offset;
+       len = attr_len - *offset;
+
+       if (len > AVRCP_PDU_MTU - *pos) {
+               len = AVRCP_PDU_MTU - *pos;
+               *offset += len;
+       } else {
+               *offset = 0;
+       }
+
+       memcpy(&buf[*pos], value, len);
+       *pos += len;
+
+       return attr_len;
+}
+
+static GList *player_fill_media_attribute(struct avrcp_player *player,
+                                       GList *attr_ids, uint8_t *buf,
+                                       uint16_t *pos, uint16_t *offset)
+{
+       struct media_attribute_header {
+               uint32_t id;
+               uint16_t charset;
+               uint16_t len;
+       } *hdr = NULL;
+       GList *l;
+
+       for (l = attr_ids; l != NULL; l = g_list_delete_link(l, l)) {
+               uint32_t attr = GPOINTER_TO_UINT(l->data);
+               uint16_t attr_len;
+
+               if (*offset == 0) {
+                       if (*pos + sizeof(*hdr) >= AVRCP_PDU_MTU)
+                               break;
+
+                       hdr = (void *) &buf[*pos];
+                       hdr->id = htonl(attr);
+                       hdr->charset = htons(0x6A); /* Always use UTF-8 */
+                       *pos += sizeof(*hdr);
+               }
+
+               attr_len = player_write_media_attribute(player, attr, buf,
+                                                               pos, offset);
+
+               if (hdr != NULL)
+                       hdr->len = htons(attr_len);
+
+               if (*offset > 0)
+                       break;
+       }
+
+       return l;
+}
+
+static struct pending_pdu *pending_pdu_new(uint8_t pdu_id, GList *attr_ids,
+                                                       unsigned int offset)
+{
+       struct pending_pdu *pending = g_new(struct pending_pdu, 1);
+
+       pending->pdu_id = pdu_id;
+       pending->attr_ids = attr_ids;
+       pending->offset = offset;
+
+       return pending;
+}
+
+static gboolean player_abort_pending_pdu(struct avrcp_player *player)
+{
+       if (player->pending_pdu == NULL)
+               return FALSE;
+
+       g_list_free(player->pending_pdu->attr_ids);
+       g_free(player->pending_pdu);
+       player->pending_pdu = NULL;
+
+       return TRUE;
+}
+
+static int player_set_attribute(struct avrcp_player *player,
+                                               uint8_t attr, uint8_t val)
+{
+       DBG("Change attribute: %u %u", attr, val);
+
+       return player->cb->set_setting(attr, val, player->user_data);
+}
+
+static int player_get_attribute(struct avrcp_player *player, uint8_t attr)
+{
+       int value;
+
+       DBG("attr %u", attr);
+
+       value = player->cb->get_setting(attr, player->user_data);
+       if (value < 0)
+               DBG("attr %u not supported by player", attr);
+
+       return value;
+}
+
+static uint8_t avrcp_handle_get_capabilities(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       unsigned int i;
+
+       if (len != 1)
+               goto err;
+
+       DBG("id=%u", pdu->params[0]);
+
+       switch (pdu->params[0]) {
+       case CAP_COMPANY_ID:
+               for (i = 0; i < G_N_ELEMENTS(company_ids); i++) {
+                       set_company_id(&pdu->params[2 + i * 3],
+                                                       company_ids[i]);
+               }
+
+               pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
+               pdu->params[1] = G_N_ELEMENTS(company_ids);
+
+               return AVC_CTYPE_STABLE;
+       case CAP_EVENTS_SUPPORTED:
+               pdu->params_len = htons(5);
+               pdu->params[1] = 3;
+               pdu->params[2] = AVRCP_EVENT_STATUS_CHANGED;
+               pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED;
+               pdu->params[4] = AVRCP_EVENT_TRACK_REACHED_START;
+
+               return AVC_CTYPE_STABLE;
+       }
+
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+
+       return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_list_player_attributes(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       unsigned int i;
+
+       if (len != 0) {
+               pdu->params_len = htons(1);
+               pdu->params[0] = E_INVALID_PARAM;
+               return AVC_CTYPE_REJECTED;
+       }
+
+       if (!player)
+               goto done;
+
+       for (i = 1; i <= AVRCP_ATTRIBUTE_SCAN; i++) {
+               if (player_get_attribute(player, i) < 0)
+                       continue;
+
+               len++;
+               pdu->params[len] = i;
+       }
+
+done:
+       pdu->params[0] = len;
+       pdu->params_len = htons(len + 1);
+
+       return AVC_CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_list_player_values(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       unsigned int i;
+
+       if (len != 1 || !player)
+               goto err;
+
+       if (player_get_attribute(player, pdu->params[0]) < 0)
+               goto err;
+
+       len = attr_get_max_val(pdu->params[0]);
+
+       for (i = 1; i <= len; i++)
+               pdu->params[i] = i;
+
+       pdu->params[0] = len;
+       pdu->params_len = htons(len + 1);
+
+       return AVC_CTYPE_STABLE;
+
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+       return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       uint64_t *identifier = (uint64_t *) &pdu->params[0];
+       uint16_t pos;
+       uint8_t nattr;
+       GList *attr_ids;
+       uint16_t offset;
+
+       if (len < 9 || *identifier != 0)
+               goto err;
+
+       nattr = pdu->params[8];
+
+       if (len < nattr * sizeof(uint32_t) + 1)
+               goto err;
+
+       if (!nattr) {
+               /*
+                * Return all available information, at least
+                * title must be returned if there's a track selected.
+                */
+               attr_ids = player->cb->list_metadata(player->user_data);
+               len = g_list_length(attr_ids);
+       } else {
+               unsigned int i;
+               uint32_t *attr = (uint32_t *) &pdu->params[9];
+
+               for (i = 0, len = 0, attr_ids = NULL; i < nattr; i++, attr++) {
+                       uint32_t id = ntohl(bt_get_unaligned(attr));
+
+                       /* Don't add invalid attributes */
+                       if (id == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL ||
+                                       id > AVRCP_MEDIA_ATTRIBUTE_LAST)
+                               continue;
+
+                       len++;
+                       attr_ids = g_list_prepend(attr_ids,
+                                                       GUINT_TO_POINTER(id));
+               }
+
+               attr_ids = g_list_reverse(attr_ids);
+       }
+
+       if (!len)
+               goto err;
+
+       player_abort_pending_pdu(player);
+       pos = 1;
+       offset = 0;
+       attr_ids = player_fill_media_attribute(player, attr_ids, pdu->params,
+                                                               &pos, &offset);
+
+       if (attr_ids != NULL) {
+               player->pending_pdu = pending_pdu_new(pdu->pdu_id, attr_ids,
+                                                               offset);
+               pdu->packet_type = AVRCP_PACKET_TYPE_START;
+       }
+
+       pdu->params[0] = len;
+       pdu->params_len = htons(pos);
+
+       return AVC_CTYPE_STABLE;
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+       return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_get_current_player_value(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       uint8_t *settings;
+       unsigned int i;
+
+       if (player == NULL || len <= 1 || pdu->params[0] != len - 1)
+               goto err;
+
+       /*
+        * Save a copy of requested settings because we can override them
+        * while responding
+        */
+       settings = g_memdup(&pdu->params[1], pdu->params[0]);
+       len = 0;
+
+       /*
+        * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs
+        * and send a response with the existent ones. Only if all IDs are
+        * non-existent we should send an error.
+        */
+       for (i = 0; i < pdu->params[0]; i++) {
+               int val;
+
+               if (settings[i] < AVRCP_ATTRIBUTE_EQUALIZER ||
+                                       settings[i] > AVRCP_ATTRIBUTE_SCAN) {
+                       DBG("Ignoring %u", settings[i]);
+                       continue;
+               }
+
+               val = player_get_attribute(player, settings[i]);
+               if (val < 0)
+                       continue;
+
+               pdu->params[++len] = settings[i];
+               pdu->params[++len] = val;
+       }
+
+       g_free(settings);
+
+       if (len) {
+               pdu->params[0] = len / 2;
+               pdu->params_len = htons(len + 1);
+
+               return AVC_CTYPE_STABLE;
+       }
+
+       error("No valid attributes in request");
+
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+
+       return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_set_player_value(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       unsigned int i;
+       uint8_t *param;
+
+       if (len < 3 || len > 2 * pdu->params[0] + 1U)
+               goto err;
+
+       /*
+        * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs
+        * and set the existent ones. Sec. 5.2.4 is not clear however how to
+        * indicate that a certain ID was not accepted. If at least one
+        * attribute is valid, we respond with no parameters. Otherwise an
+        * E_INVALID_PARAM is sent.
+        */
+       for (len = 0, i = 0, param = &pdu->params[1]; i < pdu->params[0];
+                                                       i++, param += 2) {
+               if (player_set_attribute(player, param[0], param[1]) < 0)
+                       continue;
+
+               len++;
+       }
+
+       if (len) {
+               pdu->params_len = 0;
+
+               return AVC_CTYPE_STABLE;
+       }
+
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+       return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_displayable_charset(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+
+       if (len < 3) {
+               pdu->params_len = htons(1);
+               pdu->params[0] = E_INVALID_PARAM;
+               return AVC_CTYPE_REJECTED;
+       }
+
+       /*
+        * We acknowledge the commands, but we always use UTF-8 for
+        * encoding since CT is obliged to support it.
+        */
+       pdu->params_len = 0;
+       return AVC_CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_ct_battery_status(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       const char *valstr;
+
+       if (len != 1)
+               goto err;
+
+       valstr = battery_status_to_str(pdu->params[0]);
+       if (valstr == NULL)
+               goto err;
+
+       pdu->params_len = 0;
+
+       return AVC_CTYPE_STABLE;
+
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+       return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_get_play_status(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       uint32_t position;
+       uint32_t duration;
+       void *pduration;
+
+       if (len != 0) {
+               pdu->params_len = htons(1);
+               pdu->params[0] = E_INVALID_PARAM;
+               return AVC_CTYPE_REJECTED;
+       }
+
+       position = player->cb->get_position(player->user_data);
+       pduration = player->cb->get_metadata(AVRCP_MEDIA_ATTRIBUTE_DURATION,
+                                                       player->user_data);
+       if (pduration != NULL)
+               duration = htonl(GPOINTER_TO_UINT(pduration));
+       else
+               duration = htonl(UINT32_MAX);
+
+       position = htonl(position);
+
+       memcpy(&pdu->params[0], &duration, 4);
+       memcpy(&pdu->params[4], &position, 4);
+       pdu->params[8] = player->cb->get_status(player->user_data);;
+
+       pdu->params_len = htons(9);
+
+       return AVC_CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_register_notification(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       uint64_t uid;
+
+       /*
+        * 1 byte for EventID, 4 bytes for Playback interval but the latest
+        * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP
+        * 1.3 spec, section 5.4.2.
+        */
+       if (len != 5)
+               goto err;
+
+       switch (pdu->params[0]) {
+       case AVRCP_EVENT_STATUS_CHANGED:
+               len = 2;
+               pdu->params[1] = player->cb->get_status(player->user_data);
+
+               break;
+       case AVRCP_EVENT_TRACK_CHANGED:
+               len = 9;
+               uid = player->cb->get_uid(player->user_data);
+               memcpy(&pdu->params[1], &uid, sizeof(uint64_t));
+
+               break;
+       case AVRCP_EVENT_TRACK_REACHED_START:
+               len = 1;
+               break;
+       default:
+               /* All other events are not supported yet */
+               goto err;
+       }
+
+       /* Register event and save the transaction used */
+       player->registered_events |= (1 << pdu->params[0]);
+       player->transaction_events[pdu->params[0]] = transaction;
+
+       pdu->params_len = htons(len);
+
+       return AVC_CTYPE_INTERIM;
+
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+       return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_request_continuing(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       struct pending_pdu *pending;
+
+       if (len != 1 || player->pending_pdu == NULL)
+               goto err;
+
+       pending = player->pending_pdu;
+
+       if (pending->pdu_id != pdu->params[0])
+               goto err;
+
+
+       len = 0;
+       pending->attr_ids = player_fill_media_attribute(player,
+                                                       pending->attr_ids,
+                                                       pdu->params, &len,
+                                                       &pending->offset);
+       pdu->pdu_id = pending->pdu_id;
+
+       if (pending->attr_ids == NULL) {
+               g_free(player->pending_pdu);
+               player->pending_pdu = NULL;
+               pdu->packet_type = AVRCP_PACKET_TYPE_END;
+       } else {
+               pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING;
+       }
+
+       pdu->params_len = htons(len);
+
+       return AVC_CTYPE_STABLE;
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+       return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_abort_continuing(struct avrcp_player *player,
+                                               struct avrcp_header *pdu,
+                                               uint8_t transaction)
+{
+       uint16_t len = ntohs(pdu->params_len);
+       struct pending_pdu *pending;
+
+       if (len != 1 || player->pending_pdu == NULL)
+               goto err;
+
+       pending = player->pending_pdu;
+
+       if (pending->pdu_id != pdu->params[0])
+               goto err;
+
+       player_abort_pending_pdu(player);
+       pdu->params_len = 0;
+
+       return AVC_CTYPE_STABLE;
+
+err:
+       pdu->params_len = htons(1);
+       pdu->params[0] = E_INVALID_PARAM;
+       return AVC_CTYPE_REJECTED;
+}
+
+
+static struct pdu_handler {
+       uint8_t pdu_id;
+       uint8_t code;
+       uint8_t (*func) (struct avrcp_player *player,
+                                       struct avrcp_header *pdu,
+                                       uint8_t transaction);
+} handlers[] = {
+               { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS,
+                                       avrcp_handle_get_capabilities },
+               { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS,
+                                       avrcp_handle_list_player_attributes },
+               { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS,
+                                       avrcp_handle_list_player_values },
+               { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS,
+                                       avrcp_handle_get_element_attributes },
+               { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS,
+                                       avrcp_handle_get_current_player_value },
+               { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL,
+                                       avrcp_handle_set_player_value },
+               { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS,
+                                       NULL },
+               { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS,
+                                       NULL },
+               { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS,
+                                       avrcp_handle_displayable_charset },
+               { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS,
+                                       avrcp_handle_ct_battery_status },
+               { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS,
+                                       avrcp_handle_get_play_status },
+               { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY,
+                                       avrcp_handle_register_notification },
+               { AVRCP_REQUEST_CONTINUING, AVC_CTYPE_CONTROL,
+                                       avrcp_handle_request_continuing },
+               { AVRCP_ABORT_CONTINUING, AVC_CTYPE_CONTROL,
+                                       avrcp_handle_abort_continuing },
+               { },
+};
+
+/* handle vendordep pdu inside an avctp packet */
+static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
+                                       uint8_t *code, uint8_t *subunit,
+                                       uint8_t *operands, size_t operand_count,
+                                       void *user_data)
+{
+       struct avrcp_player *player = user_data;
+       struct pdu_handler *handler;
+       struct avrcp_header *pdu = (void *) operands;
+       uint32_t company_id = get_company_id(pdu->company_id);
+
+       if (company_id != IEEEID_BTSIG) {
+               *code = AVC_CTYPE_NOT_IMPLEMENTED;
+               return 0;
+       }
+
+       DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X",
+                       pdu->pdu_id, company_id, pdu->params_len);
+
+       pdu->packet_type = 0;
+       pdu->rsvd = 0;
+
+       if (operand_count < AVRCP_HEADER_LENGTH) {
+               pdu->params[0] = E_INVALID_COMMAND;
+               goto err_metadata;
+       }
+
+       for (handler = handlers; handler; handler++) {
+               if (handler->pdu_id == pdu->pdu_id)
+                       break;
+       }
+
+       if (!handler || handler->code != *code) {
+               pdu->params[0] = E_INVALID_COMMAND;
+               goto err_metadata;
+       }
+
+       if (!handler->func) {
+               pdu->params[0] = E_INVALID_PARAM;
+               goto err_metadata;
+       }
+
+       *code = handler->func(player, pdu, transaction);
+
+       if (*code != AVC_CTYPE_REJECTED &&
+                               pdu->pdu_id != AVRCP_GET_ELEMENT_ATTRIBUTES &&
+                               pdu->pdu_id != AVRCP_REQUEST_CONTINUING &&
+                               pdu->pdu_id != AVRCP_ABORT_CONTINUING)
+               player_abort_pending_pdu(player);
+
+       return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
+
+err_metadata:
+       pdu->params_len = htons(1);
+       *code = AVC_CTYPE_REJECTED;
+
+       return AVRCP_HEADER_LENGTH + 1;
+}
+
+static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+       for (; list; list = list->next) {
+               struct avrcp_server *server = list->data;
+
+               if (bacmp(&server->src, src) == 0)
+                       return server;
+       }
+
+       return NULL;
+}
+
+static void state_changed(struct audio_device *dev, avctp_state_t old_state,
+                               avctp_state_t new_state, void *user_data)
+{
+       struct avrcp_server *server;
+       struct avrcp_player *player;
+
+       server = find_server(servers, &dev->src);
+       if (!server)
+               return;
+
+       player = server->active_player;
+       if (!player)
+               return;
+
+       switch (new_state) {
+       case AVCTP_STATE_DISCONNECTED:
+               player->session = NULL;
+               player->registered_events = 0;
+
+               if (player->handler) {
+                       avctp_unregister_pdu_handler(player->handler);
+                       player->handler = 0;
+               }
+
+               break;
+       case AVCTP_STATE_CONNECTING:
+               player->session = avctp_connect(&dev->src, &dev->dst);
+
+               if (!player->handler)
+                       player->handler = avctp_register_pdu_handler(
+                                                       AVC_OP_VENDORDEP,
+                                                       handle_vendordep_pdu,
+                                                       player);
+               break;
+       default:
+               return;
+       }
+}
+
+gboolean avrcp_connect(struct audio_device *dev)
+{
+       struct avctp *session;
+
+       session = avctp_connect(&dev->src, &dev->dst);
+       if (session)
+               return FALSE;
+
+       return TRUE;
+}
+
+void avrcp_disconnect(struct audio_device *dev)
+{
+       struct avctp *session;
+
+       session = avctp_get(&dev->src, &dev->dst);
+       if (!session)
+               return;
+
+       avctp_disconnect(session);
+}
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
+{
+       sdp_record_t *record;
+       gboolean tmp, master = TRUE;
+       GError *err = NULL;
+       struct avrcp_server *server;
+
+       if (config) {
+               tmp = g_key_file_get_boolean(config, "General",
+                                                       "Master", &err);
+               if (err) {
+                       DBG("audio.conf: %s", err->message);
+                       g_error_free(err);
+               } else
+                       master = tmp;
+       }
+
+       server = g_new0(struct avrcp_server, 1);
+       if (!server)
+               return -ENOMEM;
+
+       record = avrcp_tg_record();
+       if (!record) {
+               error("Unable to allocate new service record");
+               g_free(server);
+               return -1;
+       }
+
+       if (add_record_to_server(src, record) < 0) {
+               error("Unable to register AVRCP target service record");
+               g_free(server);
+               sdp_record_free(record);
+               return -1;
+       }
+       server->tg_record_id = record->handle;
+
+       record = avrcp_ct_record();
+       if (!record) {
+               error("Unable to allocate new service record");
+               g_free(server);
+               return -1;
+       }
+
+       if (add_record_to_server(src, record) < 0) {
+               error("Unable to register AVRCP service record");
+               sdp_record_free(record);
+               g_free(server);
+               return -1;
+       }
+       server->ct_record_id = record->handle;
+
+       if (avctp_register(src, master) < 0) {
+               remove_record_from_server(server->ct_record_id);
+               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 void player_destroy(gpointer data)
+{
+       struct avrcp_player *player = data;
+
+       if (player->destroy)
+               player->destroy(player->user_data);
+
+       player_abort_pending_pdu(player);
+
+       if (player->handler)
+               avctp_unregister_pdu_handler(player->handler);
+
+       g_free(player);
+}
+
+void avrcp_unregister(const bdaddr_t *src)
+{
+       struct avrcp_server *server;
+
+       server = find_server(servers, src);
+       if (!server)
+               return;
+
+       g_slist_free_full(server->players, player_destroy);
+
+       servers = g_slist_remove(servers, server);
+
+       remove_record_from_server(server->ct_record_id);
+       remove_record_from_server(server->tg_record_id);
+
+       avctp_unregister(&server->src);
+       g_free(server);
+
+       if (servers)
+               return;
+
+       if (avctp_id) {
+               avctp_remove_state_cb(avctp_id);
+               avctp_id = 0;
+       }
+}
+
+struct avrcp_player *avrcp_register_player(const bdaddr_t *src,
+                                               struct avrcp_player_cb *cb,
+                                               void *user_data,
+                                               GDestroyNotify destroy)
+{
+       struct avrcp_server *server;
+       struct avrcp_player *player;
+
+       server = find_server(servers, src);
+       if (!server)
+               return NULL;
+
+       player = g_new0(struct avrcp_player, 1);
+       player->server = server;
+       player->cb = cb;
+       player->user_data = user_data;
+       player->destroy = destroy;
+
+       if (!server->players)
+               server->active_player = player;
+
+       if (!avctp_id)
+               avctp_id = avctp_add_state_cb(state_changed, NULL);
+
+       server->players = g_slist_append(server->players, player);
+
+       return player;
+}
+
+void avrcp_unregister_player(struct avrcp_player *player)
+{
+       struct avrcp_server *server = player->server;
+
+       server->players = g_slist_remove(server->players, player);
+
+       if (server->active_player == player)
+               server->active_player = g_slist_nth_data(server->players, 0);
+
+       player_destroy(player);
+}
diff --git a/audio/avrcp.h b/audio/avrcp.h
new file mode 100644 (file)
index 0000000..c798658
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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
+ *
+ */
+
+/* player attributes */
+#define AVRCP_ATTRIBUTE_ILEGAL         0x00
+#define AVRCP_ATTRIBUTE_EQUALIZER      0x01
+#define AVRCP_ATTRIBUTE_REPEAT_MODE    0x02
+#define AVRCP_ATTRIBUTE_SHUFFLE                0x03
+#define AVRCP_ATTRIBUTE_SCAN           0x04
+
+/* equalizer values */
+#define AVRCP_EQUALIZER_OFF            0x01
+#define AVRCP_EQUALIZER_ON             0x02
+
+/* repeat mode values */
+#define AVRCP_REPEAT_MODE_OFF          0x01
+#define AVRCP_REPEAT_MODE_SINGLE       0x02
+#define AVRCP_REPEAT_MODE_ALL          0x03
+#define AVRCP_REPEAT_MODE_GROUP                0x04
+
+/* shuffle values */
+#define AVRCP_SHUFFLE_OFF              0x01
+#define AVRCP_SHUFFLE_ALL              0x02
+#define AVRCP_SHUFFLE_GROUP            0x03
+
+/* scan values */
+#define AVRCP_SCAN_OFF                 0x01
+#define AVRCP_SCAN_ALL                 0x02
+#define AVRCP_SCAN_GROUP               0x03
+
+/* media attributes */
+#define AVRCP_MEDIA_ATTRIBUTE_ILLEGAL  0x00
+#define AVRCP_MEDIA_ATTRIBUTE_TITLE    0x01
+#define AVRCP_MEDIA_ATTRIBUTE_ARTIST   0x02
+#define AVRCP_MEDIA_ATTRIBUTE_ALBUM    0x03
+#define AVRCP_MEDIA_ATTRIBUTE_TRACK    0x04
+#define AVRCP_MEDIA_ATTRIBUTE_N_TRACKS 0x05
+#define AVRCP_MEDIA_ATTRIBUTE_GENRE    0x06
+#define AVRCP_MEDIA_ATTRIBUTE_DURATION 0x07
+#define AVRCP_MEDIA_ATTRIBUTE_LAST     AVRCP_MEDIA_ATTRIBUTE_DURATION
+
+/* play status */
+#define AVRCP_PLAY_STATUS_STOPPED      0x00
+#define AVRCP_PLAY_STATUS_PLAYING      0x01
+#define AVRCP_PLAY_STATUS_PAUSED       0x02
+#define AVRCP_PLAY_STATUS_FWD_SEEK     0x03
+#define AVRCP_PLAY_STATUS_REV_SEEK     0x04
+#define AVRCP_PLAY_STATUS_ERROR                0xFF
+
+/* Notification events */
+#define AVRCP_EVENT_STATUS_CHANGED     0x01
+#define AVRCP_EVENT_TRACK_CHANGED      0x02
+#define AVRCP_EVENT_TRACK_REACHED_START        0x04
+#define AVRCP_EVENT_LAST               AVRCP_EVENT_TRACK_REACHED_START
+
+struct avrcp_player_cb {
+       int (*get_setting) (uint8_t attr, void *user_data);
+       int (*set_setting) (uint8_t attr, uint8_t value, void *user_data);
+       uint64_t (*get_uid) (void *user_data);
+       void *(*get_metadata) (uint32_t id, void *user_data);
+       GList *(*list_metadata) (void *user_data);
+       uint8_t (*get_status) (void *user_data);
+       uint32_t (*get_position) (void *user_data);
+};
+
+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 avrcp_player *avrcp_register_player(const bdaddr_t *src,
+                                               struct avrcp_player_cb *cb,
+                                               void *user_data,
+                                               GDestroyNotify destroy);
+void avrcp_unregister_player(struct avrcp_player *player);
+
+int avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data);
diff --git a/audio/bluetooth.conf b/audio/bluetooth.conf
new file mode 100644 (file)
index 0000000..55b51e4
--- /dev/null
@@ -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 (file)
index 0000000..a75e992
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2011  Texas Instruments, 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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "error.h"
+#include "device.h"
+#include "manager.h"
+#include "avctp.h"
+#include "control.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+#include "dbus-common.h"
+
+static unsigned int avctp_id = 0;
+
+struct control {
+       struct audio_device *dev;
+       struct avctp *session;
+
+       gboolean target;
+};
+
+static void state_changed(struct audio_device *dev, avctp_state_t old_state,
+                               avctp_state_t new_state, void *user_data)
+{
+       struct control *control = dev->control;
+       gboolean value;
+
+       switch (new_state) {
+       case AVCTP_STATE_DISCONNECTED:
+               control->session = NULL;
+
+               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:
+               if (control->session)
+                       break;
+
+               control->session = avctp_get(&dev->src, &dev->dst);
+
+               break;
+       case AVCTP_STATE_CONNECTED:
+               value = TRUE;
+               g_dbus_emit_signal(dev->conn, dev->path,
+                               AUDIO_CONTROL_INTERFACE, "Connected",
+                               DBUS_TYPE_INVALID);
+               emit_property_changed(dev->conn, dev->path,
+                               AUDIO_CONTROL_INTERFACE, "Connected",
+                               DBUS_TYPE_BOOLEAN, &value);
+               break;
+       default:
+               return;
+       }
+}
+
+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->session != NULL);
+
+       dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+                                       DBUS_TYPE_INVALID);
+
+       return reply;
+}
+
+static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct audio_device *device = data;
+       struct control *control = device->control;
+       int err;
+
+       if (!control->session)
+               return btd_error_not_connected(msg);
+
+       if (!control->target)
+               return btd_error_not_supported(msg);
+
+       err = avctp_send_passthrough(control->session, VOL_UP_OP);
+       if (err < 0)
+               return btd_error_failed(msg, 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;
+       int err;
+
+       if (!control->session)
+               return btd_error_not_connected(msg);
+
+       if (!control->target)
+               return btd_error_not_supported(msg);
+
+       err = avctp_send_passthrough(control->session, VOL_DOWN_OP);
+       if (err < 0)
+               return btd_error_failed(msg, 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->session != NULL);
+       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;
+
+       DBG("Unregistered interface %s on path %s",
+               AUDIO_CONTROL_INTERFACE, dev->path);
+
+       if (control->session)
+               avctp_disconnect(control->session);
+
+       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 control *control, uint16_t uuid16)
+{
+       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;
+
+       DBG("Registered interface %s on path %s",
+               AUDIO_CONTROL_INTERFACE, dev->path);
+
+       control = g_new0(struct control, 1);
+       control->dev = dev;
+
+       control_update(control, uuid16);
+
+       if (!avctp_id)
+               avctp_id = avctp_add_state_cb(state_changed, NULL);
+
+       return control;
+}
+
+gboolean control_is_active(struct audio_device *dev)
+{
+       struct control *control = dev->control;
+
+       if (control && control->session)
+               return TRUE;
+
+       return FALSE;
+}
diff --git a/audio/control.h b/audio/control.h
new file mode 100644 (file)
index 0000000..2219e5f
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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"
+
+struct control *control_init(struct audio_device *dev, uint16_t uuid16);
+void control_update(struct control *control, 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 (file)
index 0000000..d941421
--- /dev/null
@@ -0,0 +1,382 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <alsa/asoundlib.h>
+#include <alsa/control_external.h>
+
+#include <bluetooth/bluetooth.h>
+
+#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)
+               *value = rsp->key;
+
+       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, &current);
+       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 (file)
index 0000000..a444f7b
--- /dev/null
@@ -0,0 +1,871 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.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 "avctp.h"
+#include "avrcp.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 AVDTP_CONNECT_TIMEOUT_BOOST 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;
+       guint dc_id;
+
+       gboolean disconnecting;
+       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->auths)
+                       audio_device_cancel_authorization(dev, NULL, NULL);
+               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);
+               if (priv->dc_id)
+                       device_remove_disconnect_watch(dev->btd_dev,
+                                                       priv->dc_id);
+               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 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 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 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 disconnect_cb(struct btd_device *btd_dev, gboolean removal,
+                               void *user_data)
+{
+       struct audio_device *dev = user_data;
+       struct dev_priv *priv = dev->priv;
+
+       if (priv->state == AUDIO_STATE_DISCONNECTED)
+               return;
+
+       if (priv->disconnecting)
+               return;
+
+       priv->disconnecting = TRUE;
+
+       device_remove_control_timer(dev);
+       device_remove_avdtp_timer(dev);
+       device_remove_headset_timer(dev);
+
+       if (dev->control)
+               avrcp_disconnect(dev);
+
+       if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED)
+               sink_shutdown(dev->sink);
+       else if (priv->hs_state != HEADSET_STATE_DISCONNECTED)
+               headset_shutdown(dev);
+       else
+               priv->disconnecting = FALSE;
+}
+
+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 (priv->dc_id) {
+                       device_remove_disconnect_watch(dev->btd_dev,
+                                                       priv->dc_id);
+                       priv->dc_id = 0;
+               }
+       } else if (new_state == AUDIO_STATE_CONNECTING)
+               priv->dc_id = device_add_disconnect_watch(dev->btd_dev,
+                                               disconnect_cb, dev, NULL);
+
+       if (dev->priv->state == new_state) {
+               DBG("state change attempted from %s to %s",
+                                                       state_str, state_str);
+               return;
+       }
+
+       dev->priv->state = new_state;
+
+       if (new_state == AUDIO_STATE_DISCONNECTED) {
+               if (priv->dc_req) {
+                       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);
+               }
+               priv->disconnecting = FALSE;
+       }
+
+       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 = btd_error_failed(priv->conn_req,
+                                                       "Connect 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 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;
+       guint timeout = AVDTP_CONNECT_TIMEOUT;
+
+       if (!dev->sink)
+               return FALSE;
+
+       if (priv->avdtp_timer)
+               return FALSE;
+
+       /* If the headset is the HSP/HFP RFCOMM initiator, give the headset
+        * time to initiate AVDTP signalling (and avoid further racing) */
+       if (dev->headset && headset_get_rfcomm_initiator(dev))
+               timeout += AVDTP_CONNECT_TIMEOUT_BOOST;
+
+       priv->avdtp_timer = g_timeout_add_seconds(timeout,
+                                                       avdtp_connect_timeout,
+                                                       dev);
+
+       return TRUE;
+}
+
+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_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 &&
+                               (priv->dc_req || priv->disconnecting)) {
+                       headset_shutdown(dev);
+                       break;
+               }
+               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;
+               if (dev->auto_connect) {
+                       if (!dev->headset)
+                               device_set_state(dev, AUDIO_STATE_CONNECTED);
+                       else if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+                               device_set_headset_timer(dev);
+                       else if (priv->hs_state == HEADSET_STATE_CONNECTED ||
+                                       priv->hs_state == HEADSET_STATE_PLAY_IN_PROGRESS ||
+                                       priv->hs_state == HEADSET_STATE_PLAYING)
+                               device_set_state(dev, AUDIO_STATE_CONNECTED);
+               } else if (priv->hs_state == HEADSET_STATE_DISCONNECTED ||
+                               priv->hs_state == HEADSET_STATE_CONNECTING)
+                       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 || priv->disconnecting)) {
+                       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_CONNECTING &&
+                               (priv->sink_state == SINK_STATE_CONNECTED ||
+                               priv->sink_state == SINK_STATE_PLAYING))
+                       device_set_state(dev, AUDIO_STATE_CONNECTED);
+               break;
+       case HEADSET_STATE_CONNECTING:
+               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 ||
+                                       priv->sink_state == SINK_STATE_PLAYING)
+                               device_set_state(dev, AUDIO_STATE_CONNECTED);
+               } else if (priv->sink_state == SINK_STATE_DISCONNECTED ||
+                               priv->sink_state == SINK_STATE_CONNECTING)
+                       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 btd_error_in_progress(msg);
+       else if (priv->state == AUDIO_STATE_CONNECTED)
+               return btd_error_already_connected(msg);
+
+       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 btd_error_failed(msg,
+                                       "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 btd_error_failed(msg, "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 btd_error_not_connected(msg);
+
+       if (priv->dc_req)
+               return dbus_message_new_method_return(msg);
+
+       priv->dc_req = dbus_message_ref(msg);
+
+       if (dev->control) {
+               device_remove_control_timer(dev);
+               avrcp_disconnect(dev);
+       }
+
+       if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED)
+               sink_shutdown(dev->sink);
+       else if (priv->hs_state != HEADSET_STATE_DISCONNECTED)
+               headset_shutdown(dev);
+       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;
+       }
+
+       DBG("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->hs_preauth_id) {
+               g_source_remove(device->hs_preauth_id);
+               device->hs_preauth_id = 0;
+       }
+
+       if (device->headset)
+               headset_unregister(device);
+
+       if (device->gateway)
+               gateway_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 = l->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;
+}
+
+void audio_device_set_authorized(struct audio_device *dev, gboolean auth)
+{
+       struct dev_priv *priv = dev->priv;
+
+       priv->authorized = auth;
+}
diff --git a/audio/device.h b/audio/device.h
new file mode 100644 (file)
index 0000000..5117fca
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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"
+
+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;
+
+       guint hs_preauth_id;
+
+       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_cancel_authorization(struct audio_device *dev,
+                                       authorization_cb cb, void *user_data);
+
+int audio_device_request_authorization(struct audio_device *dev,
+                                       const char *uuid, authorization_cb cb,
+                                       void *user_data);
+
+void audio_device_set_authorized(struct audio_device *dev, gboolean auth);
diff --git a/audio/gateway.c b/audio/gateway.c
new file mode 100644 (file)
index 0000000..1a1c035
--- /dev/null
@@ -0,0 +1,997 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2008-2009  Leonid Movshovich <event.riga@gmail.org>
+ *  Copyright (C) 2010  ProFUSION embedded systems
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "glib-helper.h"
+#include "device.h"
+#include "gateway.h"
+#include "log.h"
+#include "error.h"
+#include "btio.h"
+#include "dbus-common.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+       char *name;     /* Bus id */
+       char *path;     /* D-Bus path */
+       guint watch;    /* Disconnect watch */
+};
+
+struct connect_cb {
+       unsigned int id;
+       gateway_stream_cb_t cb;
+       void *cb_data;
+};
+
+struct gateway {
+       gateway_state_t state;
+       GIOChannel *rfcomm;
+       GIOChannel *sco;
+       GIOChannel *incoming;
+       GSList *callbacks;
+       struct hf_agent *agent;
+       DBusMessage *msg;
+       int version;
+       gateway_lock_t lock;
+};
+
+struct gateway_state_callback {
+       gateway_state_cb cb;
+       void *user_data;
+       unsigned int id;
+};
+
+static GSList *gateway_callbacks = NULL;
+
+int gateway_close(struct audio_device *device);
+
+GQuark gateway_error_quark(void)
+{
+       return g_quark_from_static_string("gateway-error-quark");
+}
+
+static const char *state2str(gateway_state_t state)
+{
+       switch (state) {
+       case GATEWAY_STATE_DISCONNECTED:
+               return "disconnected";
+       case GATEWAY_STATE_CONNECTING:
+               return "connecting";
+       case GATEWAY_STATE_CONNECTED:
+               return "connected";
+       case GATEWAY_STATE_PLAYING:
+               return "playing";
+       default:
+               return "";
+       }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+       if (!agent)
+               return;
+
+       g_free(agent->name);
+       g_free(agent->path);
+       g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+       struct gateway *gw = dev->gateway;
+       const char *val;
+       GSList *l;
+       gateway_state_t old_state;
+
+       if (gw->state == new_state)
+               return;
+
+       val = state2str(new_state);
+       old_state = gw->state;
+       gw->state = new_state;
+
+       emit_property_changed(dev->conn, dev->path,
+                       AUDIO_GATEWAY_INTERFACE, "State",
+                       DBUS_TYPE_STRING, &val);
+
+       for (l = gateway_callbacks; l != NULL; l = l->next) {
+               struct gateway_state_callback *cb = l->data;
+               cb->cb(dev, old_state, new_state, cb->user_data);
+       }
+}
+
+void gateway_set_state(struct audio_device *dev, gateway_state_t new_state)
+{
+       switch (new_state) {
+       case GATEWAY_STATE_DISCONNECTED:
+               gateway_close(dev);
+               break;
+       case GATEWAY_STATE_CONNECTING:
+       case GATEWAY_STATE_CONNECTED:
+       case GATEWAY_STATE_PLAYING:
+               break;
+       }
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+       DBusMessage *msg;
+
+       msg = dbus_message_new_method_call(agent->name, agent->path,
+                       "org.bluez.HandsfreeAgent", "Release");
+
+       g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+               DBusPendingCallNotifyFunction notify, void *data)
+{
+       struct audio_device *dev = data;
+       struct gateway *gw = dev->gateway;
+       DBusMessage *msg;
+       DBusPendingCall *call;
+
+       msg = dbus_message_new_method_call(agent->name, agent->path,
+                       "org.bluez.HandsfreeAgent", "NewConnection");
+
+       dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+                                       DBUS_TYPE_UINT16, &gw->version,
+                                       DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+               return FALSE;
+
+       dbus_pending_call_set_notify(call, notify, dev, NULL);
+       dbus_pending_call_unref(call);
+       dbus_message_unref(msg);
+
+       return TRUE;
+}
+
+static unsigned int connect_cb_new(struct gateway *gw,
+                                       gateway_stream_cb_t func,
+                                       void *user_data)
+{
+       struct connect_cb *cb;
+       static unsigned int free_cb_id = 1;
+
+       if (!func)
+               return 0;
+
+       cb = g_new(struct connect_cb, 1);
+
+       cb->cb = func;
+       cb->cb_data = user_data;
+       cb->id = free_cb_id++;
+
+       gw->callbacks = g_slist_append(gw->callbacks, cb);
+
+       return cb->id;
+}
+
+static void run_connect_cb(struct audio_device *dev, GError *err)
+{
+       struct gateway *gw = dev->gateway;
+       GSList *l;
+
+       for (l = gw->callbacks; l != NULL; l = l->next) {
+               struct connect_cb *cb = l->data;
+               cb->cb(dev, err, cb->cb_data);
+       }
+
+       g_slist_free_full(gw->callbacks, g_free);
+       gw->callbacks = NULL;
+}
+
+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;
+
+       DBG("sco connection is released");
+       g_io_channel_shutdown(gw->sco, TRUE, NULL);
+       g_io_channel_unref(gw->sco);
+       gw->sco = NULL;
+       change_state(dev, GATEWAY_STATE_CONNECTED);
+
+       return FALSE;
+}
+
+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;
+
+       DBG("at the begin of sco_connect_cb() in gateway.c");
+
+       gw->sco = g_io_channel_ref(chan);
+
+       if (err) {
+               error("sco_connect_cb(): %s", err->message);
+               gateway_close(dev);
+               return;
+       }
+
+       g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                               (GIOFunc) sco_io_cb, dev);
+
+       change_state(dev, GATEWAY_STATE_PLAYING);
+       run_connect_cb(dev, NULL);
+}
+
+static gboolean rfcomm_disconnect_cb(GIOChannel *chan, GIOCondition cond,
+                       struct audio_device *dev)
+{
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       gateway_close(dev);
+
+       return FALSE;
+}
+
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+       struct audio_device *dev = data;
+       struct gateway *gw = dev->gateway;
+       DBusMessage *reply = dbus_pending_call_steal_reply(call);
+       DBusError derr;
+
+       if (!dev->gateway->rfcomm) {
+               DBG("RFCOMM disconnected from server before agent reply");
+               goto done;
+       }
+
+       dbus_error_init(&derr);
+       if (!dbus_set_error_from_message(&derr, reply)) {
+               DBG("Agent reply: file descriptor passed successfully");
+               g_io_add_watch(gw->rfcomm, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                       (GIOFunc) rfcomm_disconnect_cb, dev);
+               change_state(dev, GATEWAY_STATE_CONNECTED);
+               goto done;
+       }
+
+       DBG("Agent reply: %s", derr.message);
+
+       dbus_error_free(&derr);
+       gateway_close(dev);
+
+done:
+       dbus_message_unref(reply);
+}
+
+static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
+                               gpointer user_data)
+{
+       struct audio_device *dev = user_data;
+       struct gateway *gw = dev->gateway;
+       DBusMessage *reply;
+       int sk, ret;
+
+       if (err) {
+               error("connect(): %s", err->message);
+               goto fail;
+       }
+
+       if (!gw->agent) {
+               error("Handsfree Agent not registered");
+               goto fail;
+       }
+
+       sk = g_io_channel_unix_get_fd(chan);
+
+       if (gw->rfcomm == NULL)
+               gw->rfcomm = g_io_channel_ref(chan);
+
+       ret = agent_sendfd(gw->agent, sk, newconnection_reply, dev);
+
+       if (!gw->msg)
+               return;
+
+       if (ret)
+               reply = dbus_message_new_method_return(gw->msg);
+       else
+               reply = btd_error_failed(gw->msg, "Can't pass file descriptor");
+
+       g_dbus_send_message(dev->conn, reply);
+
+       return;
+
+fail:
+       if (gw->msg) {
+               DBusMessage *reply;
+               reply = btd_error_failed(gw->msg, "Connect failed");
+               g_dbus_send_message(dev->conn, reply);
+       }
+
+       gateway_close(dev);
+}
+
+static int get_remote_profile_version(sdp_record_t *rec)
+{
+       uuid_t uuid;
+       sdp_list_t *profiles;
+       sdp_profile_desc_t *desc;
+       int ver = 0;
+
+       sdp_uuid16_create(&uuid, HANDSFREE_PROFILE_ID);
+
+       sdp_get_profile_descs(rec, &profiles);
+       if (profiles == NULL)
+               goto done;
+
+       desc = profiles->data;
+
+       if (sdp_uuid16_cmp(&desc->uuid, &uuid) == 0)
+               ver = desc->version;
+
+       sdp_list_free(profiles, free);
+
+done:
+       return ver;
+}
+
+static void get_incoming_record_cb(sdp_list_t *recs, int err,
+                                       gpointer user_data)
+{
+       struct audio_device *dev = user_data;
+       struct gateway *gw = dev->gateway;
+       GError *gerr = NULL;
+
+       if (err < 0) {
+               error("Unable to get service record: %s (%d)", strerror(-err),
+                                       -err);
+               goto fail;
+       }
+
+       if (!recs || !recs->data) {
+               error("No records found");
+               goto fail;
+       }
+
+       gw->version = get_remote_profile_version(recs->data);
+       if (gw->version == 0)
+               goto fail;
+
+       rfcomm_connect_cb(gw->incoming, gerr, dev);
+       return;
+
+fail:
+       gateway_close(dev);
+}
+
+static void unregister_incoming(gpointer user_data)
+{
+       struct audio_device *dev = user_data;
+       struct gateway *gw = dev->gateway;
+
+       if (gw->incoming) {
+               g_io_channel_unref(gw->incoming);
+               gw->incoming = NULL;
+       }
+}
+
+static void rfcomm_incoming_cb(GIOChannel *chan, GError *err,
+                               gpointer user_data)
+{
+       struct audio_device *dev = user_data;
+       struct gateway *gw = dev->gateway;
+       uuid_t uuid;
+
+       gw->incoming = g_io_channel_ref(chan);
+
+       sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID);
+       if (bt_search_service(&dev->src, &dev->dst, &uuid,
+                                               get_incoming_record_cb, dev,
+                                               unregister_incoming) == 0)
+               return;
+
+       unregister_incoming(dev);
+       gateway_close(dev);
+}
+
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+       struct audio_device *dev = user_data;
+       struct gateway *gw = dev->gateway;
+       int ch;
+       sdp_list_t *protos, *classes;
+       uuid_t uuid;
+       GIOChannel *io;
+       GError *gerr = NULL;
+
+       if (err < 0) {
+               error("Unable to get service record: %s (%d)", strerror(-err),
+                                       -err);
+               goto fail;
+       }
+
+       if (!recs || !recs->data) {
+               error("No records found");
+               err = -EIO;
+               goto fail;
+       }
+
+       if (sdp_get_service_classes(recs->data, &classes) < 0) {
+               error("Unable to get service classes from record");
+               err = -EINVAL;
+               goto fail;
+       }
+
+       if (sdp_get_access_protos(recs->data, &protos) < 0) {
+               error("Unable to get access protocols from record");
+               err = -ENODATA;
+               goto fail;
+       }
+
+       gw->version = get_remote_profile_version(recs->data);
+       if (gw->version == 0) {
+               error("Unable to get profile version from record");
+               err = -EINVAL;
+               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");
+               err = -EIO;
+               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");
+               err = -EIO;
+               goto fail;
+       }
+
+       io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &gerr,
+                               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", gerr->message);
+               goto fail;
+       }
+
+       g_io_channel_unref(io);
+       return;
+
+fail:
+       if (gw->msg) {
+               DBusMessage *reply = btd_error_failed(gw->msg,
+                                       gerr ? gerr->message : strerror(-err));
+               g_dbus_send_message(dev->conn, reply);
+       }
+
+       gateway_close(dev);
+
+       if (gerr)
+               g_error_free(gerr);
+}
+
+static int get_records(struct audio_device *device)
+{
+       uuid_t uuid;
+
+       change_state(device, GATEWAY_STATE_CONNECTING);
+       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;
+       int err;
+
+       if (!gw->agent)
+               return btd_error_agent_not_available(msg);
+
+       err = get_records(au_dev);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       gw->msg = dbus_message_ref(msg);
+
+       return NULL;
+}
+
+int gateway_close(struct audio_device *device)
+{
+       GError *gerr = NULL;
+       struct gateway *gw = device->gateway;
+       int sock;
+
+       if (gw->rfcomm) {
+               sock = g_io_channel_unix_get_fd(gw->rfcomm);
+               shutdown(sock, SHUT_RDWR);
+
+               g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+               g_io_channel_unref(gw->rfcomm);
+               gw->rfcomm = NULL;
+       }
+
+       if (gw->sco) {
+               g_io_channel_shutdown(gw->sco, TRUE, NULL);
+               g_io_channel_unref(gw->sco);
+               gw->sco = NULL;
+       }
+
+       change_state(device, GATEWAY_STATE_DISCONNECTED);
+       g_set_error(&gerr, GATEWAY_ERROR,
+                       GATEWAY_ERROR_DISCONNECTED, "Disconnected");
+       run_connect_cb(device, gerr);
+       g_error_free(gerr);
+
+       return 0;
+}
+
+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];
+
+       if (!device->conn)
+               return NULL;
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       if (!gw->rfcomm)
+               return  btd_error_not_connected(msg);
+
+       gateway_close(device);
+       ba2str(&device->dst, gw_addr);
+       DBG("Disconnected from %s, %s", gw_addr, device->path);
+
+       return reply;
+}
+
+static void agent_exited(DBusConnection *conn, void *data)
+{
+       struct gateway *gateway = data;
+       struct hf_agent *agent = gateway->agent;
+
+       DBG("Agent %s exited", agent->name);
+
+       agent_free(agent);
+       gateway->agent = NULL;
+}
+
+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;
+       const char *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);
+
+       value = state2str(gw->state);
+       dict_append_entry(&dict, "State",
+                       DBUS_TYPE_STRING, &value);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct audio_device *device = data;
+       struct gateway *gw = device->gateway;
+       struct hf_agent *agent;
+       const char *path, *name;
+
+       if (gw->agent)
+               return btd_error_already_exists(msg);
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                               DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       name = dbus_message_get_sender(msg);
+       agent = g_new0(struct hf_agent, 1);
+
+       agent->name = g_strdup(name);
+       agent->path = g_strdup(path);
+
+       agent->watch = g_dbus_add_disconnect_watch(conn, name,
+                                               agent_exited, gw, NULL);
+
+       gw->agent = agent;
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       struct audio_device *device = data;
+       struct gateway *gw = device->gateway;
+       const char *path;
+
+       if (!gw->agent)
+               goto done;
+
+       if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+               return btd_error_not_authorized(msg);
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       if (strcmp(gw->agent->path, path) != 0)
+               return btd_error_does_not_exist(msg);
+
+       g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+       agent_free(gw->agent);
+       gw->agent = NULL;
+
+done:
+       return dbus_message_new_method_return(msg);
+}
+
+static GDBusMethodTable gateway_methods[] = {
+       { "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
+       { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
+       { "GetProperties", "", "a{sv}", ag_get_properties },
+       { "RegisterAgent", "o", "", register_agent },
+       { "UnregisterAgent", "o", "", unregister_agent },
+       { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable gateway_signals[] = {
+       { "PropertyChanged", "sv" },
+       { NULL, NULL }
+};
+
+static void path_unregister(void *data)
+{
+       struct audio_device *dev = data;
+
+       DBG("Unregistered interface %s on path %s",
+               AUDIO_GATEWAY_INTERFACE, dev->path);
+
+       gateway_close(dev);
+
+       g_free(dev->gateway);
+       dev->gateway = NULL;
+}
+
+void gateway_unregister(struct audio_device *dev)
+{
+       if (dev->gateway->agent)
+               agent_disconnect(dev, dev->gateway->agent);
+
+       g_dbus_unregister_interface(dev->conn, dev->path,
+                                               AUDIO_GATEWAY_INTERFACE);
+}
+
+struct gateway *gateway_init(struct audio_device *dev)
+{
+       if (DBUS_TYPE_UNIX_FD < 0)
+               return NULL;
+
+       if (!g_dbus_register_interface(dev->conn, dev->path,
+                                       AUDIO_GATEWAY_INTERFACE,
+                                       gateway_methods, gateway_signals,
+                                       NULL, dev, path_unregister))
+               return NULL;
+
+       return g_new0(struct gateway, 1);
+}
+
+gboolean gateway_is_connected(struct audio_device *dev)
+{
+       struct gateway *gw = dev->gateway;
+
+       if (gw->state == GATEWAY_STATE_CONNECTED)
+               return TRUE;
+
+       return FALSE;
+}
+
+gboolean gateway_is_active(struct audio_device *dev)
+{
+       struct gateway *gw = dev->gateway;
+
+       if (gw->state != GATEWAY_STATE_DISCONNECTED)
+               return TRUE;
+
+       return FALSE;
+}
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
+{
+       if (!io)
+               return -EINVAL;
+
+       dev->gateway->rfcomm = g_io_channel_ref(io);
+
+       change_state(dev, GATEWAY_STATE_CONNECTING);
+
+       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);
+
+       change_state(dev, GATEWAY_STATE_PLAYING);
+
+       return 0;
+}
+
+void gateway_start_service(struct audio_device *dev)
+{
+       struct gateway *gw = dev->gateway;
+       GError *err = NULL;
+
+       if (gw->rfcomm == NULL)
+               return;
+
+       if (!bt_io_accept(gw->rfcomm, rfcomm_incoming_cb, dev, NULL, &err)) {
+               error("bt_io_accept: %s", err->message);
+               g_error_free(err);
+               gateway_close(dev);
+       }
+}
+
+static gboolean request_stream_cb(gpointer data)
+{
+       run_connect_cb(data, NULL);
+       return FALSE;
+}
+
+/* These are functions to be called from unix.c for audio system
+ * ifaces (alsa, gstreamer, etc.) */
+unsigned int gateway_request_stream(struct audio_device *dev,
+                               gateway_stream_cb_t cb, void *user_data)
+{
+       struct gateway *gw = dev->gateway;
+       unsigned int id;
+       GError *err = NULL;
+       GIOChannel *io;
+
+       id = connect_cb_new(gw, cb, user_data);
+
+       if (!gw->rfcomm)
+               get_records(dev);
+       else if (!gw->sco) {
+               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 0;
+               }
+       } else
+               g_idle_add(request_stream_cb, dev);
+
+       return id;
+}
+
+int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t cb,
+                               void *user_data)
+{
+       struct gateway *gw = dev->gateway;
+       unsigned int id;
+
+       id = connect_cb_new(gw, cb, user_data);
+
+       if (!gw->rfcomm)
+               get_records(dev);
+       else if (cb)
+               g_idle_add(request_stream_cb, dev);
+
+       return id;
+}
+
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id)
+{
+       struct gateway *gw = dev->gateway;
+       GSList *l;
+       struct connect_cb *cb = NULL;
+
+       for (l = gw->callbacks; l != NULL; l = l->next) {
+               struct connect_cb *tmp = l->data;
+
+               if (tmp->id == id) {
+                       cb = tmp;
+                       break;
+               }
+       }
+
+       if (!cb)
+               return FALSE;
+
+       gw->callbacks = g_slist_remove(gw->callbacks, cb);
+       g_free(cb);
+
+       gateway_suspend_stream(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)
+{
+       GError *gerr = NULL;
+       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;
+       g_set_error(&gerr, GATEWAY_ERROR, GATEWAY_ERROR_SUSPENDED, "Suspended");
+       run_connect_cb(dev, gerr);
+       g_error_free(gerr);
+       change_state(dev, GATEWAY_STATE_CONNECTED);
+}
+
+unsigned int gateway_add_state_cb(gateway_state_cb cb, void *user_data)
+{
+       struct gateway_state_callback *state_cb;
+       static unsigned int id = 0;
+
+       state_cb = g_new(struct gateway_state_callback, 1);
+       state_cb->cb = cb;
+       state_cb->user_data = user_data;
+       state_cb->id = ++id;
+
+       gateway_callbacks = g_slist_append(gateway_callbacks, state_cb);
+
+       return state_cb->id;
+}
+
+gboolean gateway_remove_state_cb(unsigned int id)
+{
+       GSList *l;
+
+       for (l = gateway_callbacks; l != NULL; l = l->next) {
+               struct gateway_state_callback *cb = l->data;
+               if (cb && cb->id == id) {
+                       gateway_callbacks = g_slist_remove(gateway_callbacks,
+                                                                       cb);
+                       g_free(cb);
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+gateway_lock_t gateway_get_lock(struct audio_device *dev)
+{
+       struct gateway *gw = dev->gateway;
+
+       return gw->lock;
+}
+
+gboolean gateway_lock(struct audio_device *dev, gateway_lock_t lock)
+{
+       struct gateway *gw = dev->gateway;
+
+       if (gw->lock & lock)
+               return FALSE;
+
+       gw->lock |= lock;
+
+       return TRUE;
+}
+
+gboolean gateway_unlock(struct audio_device *dev, gateway_lock_t lock)
+{
+       struct gateway *gw = dev->gateway;
+
+       if (!(gw->lock & lock))
+               return FALSE;
+
+       gw->lock &= ~lock;
+
+       if (gw->lock)
+               return TRUE;
+
+       if (gw->state == GATEWAY_STATE_PLAYING)
+               gateway_suspend_stream(dev);
+
+       return TRUE;
+}
diff --git a/audio/gateway.h b/audio/gateway.h
new file mode 100644 (file)
index 0000000..77f5787
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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.HandsfreeGateway"
+
+#define DEFAULT_HFP_HS_CHANNEL 7
+
+typedef enum {
+       GATEWAY_STATE_DISCONNECTED,
+       GATEWAY_STATE_CONNECTING,
+       GATEWAY_STATE_CONNECTED,
+       GATEWAY_STATE_PLAYING,
+} gateway_state_t;
+
+typedef enum {
+       GATEWAY_LOCK_READ = 1,
+       GATEWAY_LOCK_WRITE = 1 << 1,
+} gateway_lock_t;
+
+typedef enum {
+       GATEWAY_ERROR_DISCONNECTED,
+       GATEWAY_ERROR_SUSPENDED,
+} gateway_error_t;
+
+#define GATEWAY_ERROR gateway_error_quark()
+
+GQuark gateway_error_quark(void);
+
+typedef void (*gateway_state_cb) (struct audio_device *dev,
+                                       gateway_state_t old_state,
+                                       gateway_state_t new_state,
+                                       void *user_data);
+typedef void (*gateway_stream_cb_t) (struct audio_device *dev, GError *err,
+               void *user_data);
+
+void gateway_set_state(struct audio_device *dev, gateway_state_t new_state);
+void gateway_unregister(struct audio_device *dev);
+struct gateway *gateway_init(struct audio_device *device);
+gboolean gateway_is_active(struct audio_device *dev);
+gboolean gateway_is_connected(struct audio_device *dev);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
+int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
+void gateway_start_service(struct audio_device *device);
+unsigned int 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);
+unsigned int gateway_add_state_cb(gateway_state_cb cb, void *user_data);
+gboolean gateway_remove_state_cb(unsigned int id);
+gateway_lock_t gateway_get_lock(struct audio_device *dev);
+gboolean gateway_lock(struct audio_device *dev, gateway_lock_t lock);
+gboolean gateway_unlock(struct audio_device *dev, gateway_lock_t lock);
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c
new file mode 100644 (file)
index 0000000..930d14e
--- /dev/null
@@ -0,0 +1,730 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <pthread.h>
+
+#include "gstpragma.h"
+#include "gsta2dpsink.h"
+
+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,
+       PROP_TRANSPORT
+};
+
+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 <marcel@holtmann.org>");
+
+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), &current, &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:
+       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_TRANSPORT:
+               if (self->sink != NULL)
+                       gst_avdtp_sink_set_transport(self->sink,
+                               g_value_get_string(value));
+
+               if (self->transport != NULL)
+                       g_free(self->transport);
+               self->transport = 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, *transport;
+
+       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;
+       case PROP_TRANSPORT:
+               if (self->sink != NULL) {
+                       transport = gst_avdtp_sink_get_transport(self->sink);
+                       if (transport != NULL)
+                               g_value_take_string(value, transport);
+               }
+               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);
+
+               if (self->transport != NULL)
+                       gst_avdtp_sink_set_transport(self->sink,
+                                       self->transport);
+
+               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));
+
+       g_object_class_install_property(object_class, PROP_TRANSPORT,
+                       g_param_spec_string("transport", "Transport",
+                       "Use configured transport",
+                       NULL, 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);
+       g_object_set(G_OBJECT(self->sink), "transport", self->transport, 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->transport = 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 (file)
index 0000000..5f96a3e
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <gst/gst.h>
+#include <gst/rtp/gstbasertppayload.h>
+#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;
+       gchar *transport;
+       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 (file)
index 0000000..0f3abf3
--- /dev/null
@@ -0,0 +1,2029 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <pthread.h>
+
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <gst/rtp/gstrtpbuffer.h>
+
+#include <dbus/dbus.h>
+
+#include "ipc.h"
+#include "rtp.h"
+#include "a2dp-codecs.h"
+
+#include "gstpragma.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
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct bluetooth_data {
+       struct bt_get_capabilities_rsp *caps; /* Bluetooth device caps */
+       guint link_mtu;
+
+       DBusConnection *conn;
+       guint8 codec; /* Bluetooth transport configuration */
+       gchar *uuid;
+       guint8 *config;
+       gint config_size;
+
+       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,
+       PROP_TRANSPORT
+};
+
+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 <marcel@holtmann.org>");
+
+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 int gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
+                                       const bt_audio_msg_header_t *msg);
+static int 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 void gst_avdtp_sink_transport_release(GstAvdtpSink *self)
+{
+       DBusMessage *msg;
+       const char *access_type = "w";
+
+       msg = dbus_message_new_method_call("org.bluez", self->transport,
+                                               "org.bluez.MediaTransport",
+                                               "Release");
+
+       dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type,
+                                       DBUS_TYPE_INVALID);
+
+       dbus_connection_send(self->data->conn, msg, NULL);
+
+       dbus_message_unref(msg);
+}
+
+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) {
+               if (self->transport)
+                       gst_avdtp_sink_transport_release(self);
+               if (self->data->conn)
+                       dbus_connection_unref(self->data->conn);
+               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);
+
+       if (self->transport)
+               g_free(self->transport);
+
+       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;
+
+       case PROP_TRANSPORT:
+               if (sink->transport)
+                       g_free(sink->transport);
+               sink->transport = g_value_dup_string(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;
+
+       case PROP_TRANSPORT:
+               g_value_set_string(value, sink->transport);
+               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);
+       g_io_channel_set_encoding(sink->stream, NULL, NULL);
+       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;
+       GError *gerr = NULL;
+       GIOStatus status;
+       GIOFlags flags;
+       int fd;
+
+       /* Proceed if stream was already acquired */
+       if (self->stream != NULL)
+               goto proceed;
+
+       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;
+       }
+
+proceed:
+       /* 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");
+       }
+
+       fd = g_io_channel_unix_get_fd(self->stream);
+
+       /* 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) {
+               ssize_t bread = read(fd, data->buffer, data->link_mtu);
+               if (bread <= 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;
+
+       if (sbc == NULL)
+               return NULL;
+
+       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 GstStructure *gst_avdtp_sink_parse_sbc_raw(GstAvdtpSink *self)
+{
+       a2dp_sbc_t *sbc = (a2dp_sbc_t *) self->data->config;
+       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_raw(GstAvdtpSink *self)
+{
+       a2dp_mpeg_t *mpeg = (a2dp_mpeg_t *) self->data->config;
+       GstStructure *structure;
+       GValue *value;
+       GValue *list;
+       gboolean valid_layer = FALSE;
+       gboolean mono, stereo;
+
+       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_config(GstAvdtpSink *self)
+{
+       GstStructure *structure;
+       gchar *tmp;
+
+       switch (self->data->codec) {
+       case A2DP_CODEC_SBC:
+               structure = gst_avdtp_sink_parse_sbc_raw(self);
+               break;
+       case A2DP_CODEC_MPEG12:
+               structure = gst_avdtp_sink_parse_mpeg_raw(self);
+               break;
+       default:
+               GST_ERROR_OBJECT(self, "Unsupported configuration");
+               return FALSE;
+       }
+
+       if (structure == NULL)
+               return FALSE;
+
+       if (self->dev_caps != NULL)
+               gst_caps_unref(self->dev_caps);
+
+       self->dev_caps = gst_caps_new_full(structure, NULL);
+
+       tmp = gst_caps_to_string(self->dev_caps);
+       GST_DEBUG_OBJECT(self, "Transport configuration: %s", tmp);
+       g_free(tmp);
+
+       return TRUE;
+}
+
+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");
+
+       if (self->data->config_size != 0 && self->data->config != NULL)
+               return gst_avdtp_sink_update_config(self);
+
+       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;
+       int err;
+
+       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;
+
+       err = gst_avdtp_sink_audioservice_send(self, &req->h);
+       if (err < 0) {
+               GST_ERROR_OBJECT(self, "Error while asking device caps");
+               return FALSE;
+       }
+
+       rsp->h.length = 0;
+       err = gst_avdtp_sink_audioservice_expect(self,
+                                       &rsp->h, BT_GET_CAPABILITIES);
+       if (err < 0) {
+               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_transport_parse_property(GstAvdtpSink *self,
+                                                       DBusMessageIter *i)
+{
+       const char *key;
+       DBusMessageIter variant_i;
+
+       if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
+               GST_ERROR_OBJECT(self, "Property name not a string.");
+               return FALSE;
+       }
+
+       dbus_message_iter_get_basic(i, &key);
+
+       if (!dbus_message_iter_next(i))  {
+               GST_ERROR_OBJECT(self, "Property value missing");
+               return FALSE;
+       }
+
+       if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
+               GST_ERROR_OBJECT(self, "Property value not a variant.");
+               return FALSE;
+       }
+
+       dbus_message_iter_recurse(i, &variant_i);
+
+       switch (dbus_message_iter_get_arg_type(&variant_i)) {
+       case DBUS_TYPE_BYTE: {
+               uint8_t value;
+               dbus_message_iter_get_basic(&variant_i, &value);
+
+               if (g_str_equal(key, "Codec") == TRUE)
+                       self->data->codec = value;
+
+               break;
+       }
+       case DBUS_TYPE_STRING: {
+               const char *value;
+               dbus_message_iter_get_basic(&variant_i, &value);
+
+               if (g_str_equal(key, "UUID") == TRUE) {
+                       g_free(self->data->uuid);
+                       self->data->uuid = g_strdup(value);
+               }
+
+               break;
+       }
+       case DBUS_TYPE_ARRAY: {
+               DBusMessageIter array_i;
+               char *value;
+               int size;
+
+               dbus_message_iter_recurse(&variant_i, &array_i);
+               dbus_message_iter_get_fixed_array(&array_i, &value, &size);
+
+               if (g_str_equal(key, "Configuration")) {
+                       g_free(self->data->config);
+                       self->data->config = g_new0(guint8, size);
+                       self->data->config_size = size;
+                       memcpy(self->data->config, value, size);
+               }
+
+               break;
+       }
+       }
+
+       return TRUE;
+}
+
+static gboolean gst_avdtp_sink_transport_acquire(GstAvdtpSink *self)
+{
+       DBusMessage *msg, *reply;
+       DBusError err;
+       const char *access_type = "w";
+       int fd;
+       uint16_t imtu, omtu;
+
+       dbus_error_init(&err);
+
+       if (self->data->conn == NULL)
+               self->data->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+
+       msg = dbus_message_new_method_call("org.bluez", self->transport,
+                                               "org.bluez.MediaTransport",
+                                               "Acquire");
+
+       dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type,
+                                       DBUS_TYPE_INVALID);
+
+       reply = dbus_connection_send_with_reply_and_block(self->data->conn,
+                                                       msg, -1, &err);
+
+       if (dbus_error_is_set(&err))
+               goto fail;
+
+       if (dbus_message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd,
+                                               DBUS_TYPE_UINT16, &imtu,
+                                               DBUS_TYPE_UINT16, &omtu,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               goto fail;
+
+       dbus_message_unref(reply);
+
+       self->stream = g_io_channel_unix_new(fd);
+       g_io_channel_set_encoding(self->stream, NULL, NULL);
+       g_io_channel_set_close_on_unref(self->stream, TRUE);
+       self->data->link_mtu = omtu;
+       GST_DEBUG_OBJECT(self, "stream_fd=%d mtu=%d", fd, omtu);
+
+       return TRUE;
+
+fail:
+       GST_ERROR_OBJECT(self, "Failed to acquire transport stream: %s",
+                               err.message);
+
+       dbus_error_free(&err);
+
+       if (reply)
+               dbus_message_unref(msg);
+
+       return FALSE;
+}
+
+static gboolean gst_avdtp_sink_transport_get_properties(GstAvdtpSink *self)
+{
+       DBusMessage *msg, *reply;
+       DBusMessageIter arg_i, ele_i;
+       DBusError err;
+
+       dbus_error_init(&err);
+
+       /* Transport need to be acquire first to make sure the MTUs are
+          available */
+       if (gst_avdtp_sink_transport_acquire(self) == FALSE)
+               return FALSE;
+
+       msg = dbus_message_new_method_call("org.bluez", self->transport,
+                                               "org.bluez.MediaTransport",
+                                               "GetProperties");
+       reply = dbus_connection_send_with_reply_and_block(self->data->conn,
+                                                       msg, -1, &err);
+
+       if (dbus_error_is_set(&err) || reply == NULL) {
+               GST_ERROR_OBJECT(self, "Failed to get transport properties: %s",
+                                       err.message);
+               goto fail;
+       }
+
+       if (!dbus_message_iter_init(reply, &arg_i)) {
+               GST_ERROR_OBJECT(self, "GetProperties reply has no arguments.");
+               goto fail;
+       }
+
+       if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
+               GST_ERROR_OBJECT(self, "GetProperties argument is not an array.");
+               goto fail;
+       }
+
+       dbus_message_iter_recurse(&arg_i, &ele_i);
+       while (dbus_message_iter_get_arg_type(&ele_i) != DBUS_TYPE_INVALID) {
+
+               if (dbus_message_iter_get_arg_type(&ele_i) ==
+                               DBUS_TYPE_DICT_ENTRY) {
+                       DBusMessageIter dict_i;
+
+                       dbus_message_iter_recurse(&ele_i, &dict_i);
+
+                       gst_avdtp_sink_transport_parse_property(self, &dict_i);
+               }
+
+               if (!dbus_message_iter_next(&ele_i))
+                       break;
+       }
+
+       return gst_avdtp_sink_update_caps(self);
+
+fail:
+       dbus_message_unref(msg);
+       dbus_message_unref(reply);
+       return FALSE;
+
+}
+
+static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
+{
+       GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+       gint sk;
+       gint err;
+
+       GST_INFO_OBJECT(self, "start");
+
+       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 (self->transport != NULL)
+               return gst_avdtp_sink_transport_get_properties(self);
+
+       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);
+       g_io_channel_set_encoding(self->server, NULL, NULL);
+       self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR |
+                                       G_IO_NVAL, server_callback, self);
+
+       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;
+       int err;
+
+       if (self->transport != NULL)
+               return gst_avdtp_sink_conf_recv_stream_fd(self);
+
+       memset(req, 0, sizeof(buf));
+       req->h.type = BT_REQUEST;
+       req->h.name = BT_START_STREAM;
+       req->h.length = sizeof(*req);
+
+       err = gst_avdtp_sink_audioservice_send(self, &req->h);
+       if (err < 0) {
+               GST_ERROR_OBJECT(self, "Error occurred while sending "
+                                       "start packet");
+               return FALSE;
+       }
+
+       rsp->h.length = sizeof(*rsp);
+       err = gst_avdtp_sink_audioservice_expect(self, &rsp->h,
+                                                       BT_START_STREAM);
+       if (err < 0) {
+               GST_ERROR_OBJECT(self, "Error while stream "
+                       "start confirmation");
+               return FALSE;
+       }
+
+       ind->h.length = sizeof(*ind);
+       err = gst_avdtp_sink_audioservice_expect(self, &ind->h,
+                                                       BT_NEW_STREAM);
+       if (err < 0) {
+               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;
+       gchar *temp;
+       GstStructure *structure;
+       codec_capabilities_t *codec = NULL;
+       int err;
+
+       temp = gst_caps_to_string(caps);
+       GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp);
+       g_free(temp);
+
+       /* Transport already configured */
+       if (self->transport != NULL)
+               return TRUE;
+
+       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;
+
+       err = gst_avdtp_sink_audioservice_send(self, &open_req->h);
+       if (err < 0) {
+               GST_ERROR_OBJECT(self, "Error occurred while sending "
+                                       "open packet");
+               return FALSE;
+       }
+
+       open_rsp->h.length = sizeof(*open_rsp);
+       err = gst_avdtp_sink_audioservice_expect(self, &open_rsp->h,
+                                                               BT_OPEN);
+       if (err < 0) {
+               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);
+       memcpy(&req->codec, codec, sizeof(req->codec));
+
+       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);
+       err = gst_avdtp_sink_audioservice_send(self, &req->h);
+       if (err < 0) {
+               GST_ERROR_OBJECT(self, "Error occurred while sending "
+                                       "configurarion packet");
+               return FALSE;
+       }
+
+       rsp->h.length = sizeof(*rsp);
+       err = gst_avdtp_sink_audioservice_expect(self, &rsp->h,
+                                                       BT_SET_CONFIGURATION);
+       if (err < 0) {
+               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);
+       ssize_t ret;
+       int fd;
+
+       fd = g_io_channel_unix_get_fd(self->stream);
+
+       ret = write(fd, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
+       if (ret < 0) {
+               GST_ERROR_OBJECT(self, "Error while writting to socket: %s",
+                                                       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));
+
+       g_object_class_install_property(object_class, PROP_TRANSPORT,
+                                       g_param_spec_string("transport",
+                                       "Transport",
+                                       "Use configured transport",
+                                       NULL, 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->transport = 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 int gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
+                                       const bt_audio_msg_header_t *msg)
+{
+       ssize_t written;
+       const char *type, *name;
+       uint16_t length;
+       int fd;
+
+       length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+       fd = g_io_channel_unix_get_fd(self->server);
+
+       written = write(fd, msg, length);
+       if (written < 0) {
+               GST_ERROR_OBJECT(self, "Error sending data to audio service:"
+                       " %s", strerror(errno));
+               return -errno;
+       }
+
+       type = bt_audio_strtype(msg->type);
+       name = bt_audio_strname(msg->name);
+
+       GST_DEBUG_OBJECT(self, "sent: %s -> %s", type, name);
+
+       return 0;
+}
+
+static int gst_avdtp_sink_audioservice_recv(GstAvdtpSink *self,
+                                               bt_audio_msg_header_t *inmsg)
+{
+       ssize_t bytes_read;
+       const char *type, *name;
+       uint16_t length;
+       int fd, err = 0;
+
+       length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+       fd = g_io_channel_unix_get_fd(self->server);
+
+       bytes_read = read(fd, inmsg, length);
+       if (bytes_read < 0) {
+               GST_ERROR_OBJECT(self, "Error receiving data from "
+                               "audio service: %s", strerror(errno));
+               return -errno;
+       }
+
+       type = bt_audio_strtype(inmsg->type);
+       if (!type) {
+               err = -EINVAL;
+               GST_ERROR_OBJECT(self, "Bogus message type %d "
+                               "received from audio service",
+                               inmsg->type);
+       }
+
+       name = bt_audio_strname(inmsg->name);
+       if (!name) {
+               err = -EINVAL;
+               GST_ERROR_OBJECT(self, "Bogus message name %d "
+                               "received from audio service",
+                               inmsg->name);
+       }
+
+       if (inmsg->type == BT_ERROR) {
+               bt_audio_error_t *msg = (void *) inmsg;
+               err = -EINVAL;
+               GST_ERROR_OBJECT(self, "%s failed : "
+                                       "%s(%d)",
+                                       name,
+                                       strerror(msg->posix_errno),
+                                       msg->posix_errno);
+       }
+
+       GST_DEBUG_OBJECT(self, "received: %s <- %s", type, name);
+
+       return err;
+}
+
+static int gst_avdtp_sink_audioservice_expect(GstAvdtpSink *self,
+                                               bt_audio_msg_header_t *outmsg,
+                                               guint8 expected_name)
+{
+       int err;
+
+       err = gst_avdtp_sink_audioservice_recv(self, outmsg);
+       if (err < 0)
+               return err;
+
+       if (outmsg->name != expected_name)
+               return -EINVAL;
+
+       return 0;
+}
+
+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);
+}
+
+void gst_avdtp_sink_set_transport(GstAvdtpSink *self, const gchar *trans)
+{
+       if (self->transport != NULL)
+               g_free(self->transport);
+
+       GST_LOG_OBJECT(self, "Setting transport: %s", trans);
+       self->transport = g_strdup(trans);
+}
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self)
+{
+       return g_strdup(self->device);
+}
+
+gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *self)
+{
+       return g_strdup(self->transport);
+}
+
+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 (file)
index 0000000..c4e5645
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+
+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;
+       gchar *transport;
+       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);
+
+void gst_avdtp_sink_set_transport(GstAvdtpSink *sink,
+               const gchar *transport);
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink);
+
+gchar *gst_avdtp_sink_get_transport(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 (file)
index 0000000..9930820
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <gst/gst.h>
+
+#include "gstsbcutil.h"
+#include <sbc.h>
+
+#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 (data == NULL)
+               return;
+
+       if (sbc_init(&sbc, 0) < 0)
+               return;
+
+       aux = g_new(guint8, 32);
+       memcpy(aux, data, 32);
+       if (sbc_parse(&sbc, aux, 32) < 0)
+               goto done;
+
+       caps = gst_sbc_parse_caps_from_sbc(&sbc);
+       gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, caps);
+       gst_caps_unref(caps);
+
+done:
+       g_free(aux);
+       sbc_finish(&sbc);
+}
+
+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/gstpragma.h b/audio/gstpragma.h
new file mode 100644 (file)
index 0000000..0cb08d2
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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
+ *
+ */
+
+/* #pragma GCC diagnostic warning "-Wmissing-declarations" */
diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c
new file mode 100644 (file)
index 0000000..1159bfe
--- /dev/null
@@ -0,0 +1,352 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gstpragma.h"
+#include "gstrtpsbcpay.h"
+#include <math.h>
+#include <string.h>
+
+#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 "
+                               "<thiagoss@lcc.ufcg.edu.br>");
+
+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 (file)
index 0000000..398de82
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <gst/gst.h>
+#include <gst/rtp/gstbasertppayload.h>
+#include <gst/base/gstadapter.h>
+#include <gst/rtp/gstrtpbuffer.h>
+
+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 (file)
index 0000000..2e5cb0a
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "gstpragma.h"
+#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 <marcel@holtmann.org>");
+
+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 (file)
index 0000000..c77feae
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <gst/gst.h>
+
+#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 (file)
index 0000000..d2de4ee
--- /dev/null
@@ -0,0 +1,603 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "gstpragma.h"
+#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 <marcel@holtmann.org>");
+
+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 (file)
index 0000000..0329351
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <gst/gst.h>
+#include <gst/base/gstadapter.h>
+
+#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 (file)
index 0000000..a44b52c
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "gstpragma.h"
+#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 <marcel@holtmann.org>");
+
+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 (file)
index 0000000..ecb8be4
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <gst/gst.h>
+
+#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 (file)
index 0000000..ac5fcbf
--- /dev/null
@@ -0,0 +1,521 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#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 successful 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 (file)
index 0000000..a7f84d5
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <gst/gst.h>
+
+#include "sbc.h"
+#include <string.h>
+
+#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 (file)
index 0000000..4e41a49
--- /dev/null
@@ -0,0 +1,3339 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <assert.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.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 = BTRH_NOT_SUPPORTED,
+       .number = NULL,
+       .number_type = 0,
+       .ring_timer = 0,
+};
+
+static gboolean sco_hci = TRUE;
+static gboolean fast_connectable = FALSE;
+
+static GSList *active_devices = NULL;
+
+static char *str_state[] = {
+       "HEADSET_STATE_DISCONNECTED",
+       "HEADSET_STATE_CONNECTING",
+       "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 headset_nrec_callback {
+       unsigned int id;
+       headset_nrec_cb cb;
+       void *user_data;
+};
+
+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_slc {
+       char buf[BUF_SIZE];
+       int data_start;
+       int data_length;
+
+       gboolean cli_active;
+       gboolean cme_enabled;
+       gboolean cwa_enabled;
+       gboolean pending_ring;
+       gboolean inband_ring;
+       gboolean nrec;
+       gboolean nrec_req;
+
+       int sp_gain;
+       int mic_gain;
+
+       unsigned int hf_features;
+};
+
+struct headset {
+       uint32_t hsp_handle;
+       uint32_t hfp_handle;
+
+       int rfcomm_ch;
+
+       GIOChannel *rfcomm;
+       GIOChannel *tmp_rfcomm;
+       GIOChannel *sco;
+       guint sco_id;
+
+       gboolean auto_dc;
+
+       guint dc_timer;
+
+#ifdef __TIZEN_PATCH__
+       guint rfcomm_io_id;
+#endif
+       gboolean hfp_active;
+       gboolean search_hfp;
+       gboolean rfcomm_initiator;
+
+       headset_state_t state;
+       struct pending_connect *pending;
+
+       headset_lock_t lock;
+       struct headset_slc *slc;
+       GSList *nrec_cbs;
+};
+
+struct event {
+       const char *cmd;
+       int (*callback) (struct audio_device *device, const char *buf);
+};
+
+static GSList *headset_callbacks = NULL;
+
+static void error_connect_failed(DBusConnection *conn, DBusMessage *msg,
+                                                               int err)
+{
+       DBusMessage *reply = btd_error_failed(msg,
+                       err < 0 ? strerror(-err) : "Connect failed");
+       g_dbus_send_message(conn, reply);
+}
+
+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) {
+               DBG("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);
+
+       DBG("%s", str);
+
+       g_free(str);
+}
+
+static void print_hf_features(uint32_t features)
+{
+       GString *gstr;
+       char *str;
+
+       if (features == 0) {
+               DBG("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);
+
+       DBG("%s", str);
+
+       g_free(str);
+}
+
+static const char *state2str(headset_state_t state)
+{
+       switch (state) {
+       case HEADSET_STATE_DISCONNECTED:
+               return "disconnected";
+       case HEADSET_STATE_CONNECTING:
+               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 __attribute__((format(printf, 2, 3)))
+                       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;
+       struct headset_slc *slc = hs->slc;
+       int err;
+
+       if (strlen(buf) < 9)
+               return -EINVAL;
+
+       slc->hf_features = strtoul(&buf[8], NULL, 10);
+
+       print_hf_features(slc->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, "%s", 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 < 0)
+               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_free_full(p->callbacks, g_free);
+
+       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;
+       static 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 __attribute__((format(printf, 3, 4)))
+               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)
+{
+       struct headset_slc *slc = hs->slc;
+
+       if (!hs->hfp_active)
+               return -1;
+
+       if (slc->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 headset_slc *slc = hs->slc;
+       struct pending_connect *p = hs->pending;
+
+       if (err) {
+               error("%s", err->message);
+
+               if (p != NULL) {
+                       p->err = -errno;
+                       if (p->msg)
+                               error_connect_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;
+       }
+
+       DBG("SCO socket opened for headset %s", dev->path);
+
+       sk = g_io_channel_unix_get_fd(chan);
+
+       DBG("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 (slc->pending_ring) {
+               ring_timer_cb(NULL);
+               ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL,
+                                               ring_timer_cb,
+                                               NULL);
+               slc->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;
+
+       DBG("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_connect_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;
+       struct headset_slc *slc = hs->slc;
+
+       if ((err != CME_ERROR_NONE) && slc->cme_enabled)
+               return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+
+       switch (err) {
+       case CME_ERROR_NONE:
+               return headset_send(hs, "\r\nOK\r\n");
+       case CME_ERROR_NO_NETWORK_SERVICE:
+               return headset_send(hs, "\r\nNO CARRIER\r\n");
+       default:
+               return headset_send(hs, "\r\nERROR\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;
+       struct headset_slc *slc = hs->slc;
+       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_CONNECTING)
+               return 0;
+
+       if (slc->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; /* <mode>, <keyp>, <disp>, <ind>, <bfr> */
+
+       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;
+
+       DBG("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_CONNECTING)
+               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;
+       struct headset_slc *slc = hs->slc;
+
+       if (strlen(buf) < 9)
+               return -EINVAL;
+
+       slc->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 (ag.rh == BTRH_NOT_SUPPORTED)
+               return telephony_generic_rsp(device, CME_ERROR_NOT_SUPPORTED);
+
+       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");
+}
+
+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] != ';') {
+               DBG("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 headset_set_gain(struct audio_device *device, uint16_t gain, char type)
+{
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+       const char *name, *property;
+
+       if (gain > 15) {
+               error("Invalid gain value: %u", gain);
+               return -EINVAL;
+       }
+
+       switch (type) {
+       case HEADSET_GAIN_SPEAKER:
+               if (slc->sp_gain == gain) {
+                       DBG("Ignoring no-change in speaker gain");
+                       return -EALREADY;
+               }
+               name = "SpeakerGainChanged";
+               property = "SpeakerGain";
+               slc->sp_gain = gain;
+               break;
+       case HEADSET_GAIN_MICROPHONE:
+               if (slc->mic_gain == gain) {
+                       DBG("Ignoring no-change in microphone gain");
+                       return -EALREADY;
+               }
+               name = "MicrophoneGainChanged";
+               property = "MicrophoneGain";
+               slc->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);
+
+       return 0;
+}
+
+static int signal_gain_setting(struct audio_device *device, const char *buf)
+{
+       struct headset *hs = device->headset;
+       dbus_uint16_t gain;
+       int err;
+
+       if (strlen(buf) < 8) {
+               error("Too short string for Gain setting");
+               return -EINVAL;
+       }
+
+       gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10);
+
+       err = headset_set_gain(device, gain, buf[5]);
+       if (err < 0 && err != -EALREADY)
+               return err;
+
+       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)
+{
+       char tone;
+
+       if (strlen(buf) < 8) {
+               error("Too short string for DTMF tone");
+               return -EINVAL;
+       }
+
+       tone = buf[7];
+       if (tone >= '#' && tone <= 'D')
+               telephony_transmit_dtmf_req(device, tone);
+       else
+               return -EINVAL;
+
+       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;
+       struct headset_slc *slc = hs->slc;
+
+       if (strlen(buf) < 9)
+               return -EINVAL;
+
+       if (buf[8] == '1') {
+               slc->cme_enabled = TRUE;
+               DBG("CME errors enabled for headset %p", hs);
+       } else {
+               slc->cme_enabled = FALSE;
+               DBG("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;
+       struct headset_slc *slc = hs->slc;
+
+       if (strlen(buf) < 9)
+               return -EINVAL;
+
+       if (buf[8] == '1') {
+               slc->cwa_enabled = TRUE;
+               DBG("Call waiting notification enabled for headset %p", hs);
+       } else {
+               slc->cwa_enabled = FALSE;
+               DBG("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;
+       struct headset_slc *slc = hs->slc;
+
+       if (err == CME_ERROR_NONE) {
+               GSList *l;
+
+               for (l = hs->nrec_cbs; l; l = l->next) {
+                       struct headset_nrec_callback *nrec_cb = l->data;
+
+                       nrec_cb->cb(device, slc->nrec_req, nrec_cb->user_data);
+               }
+
+               slc->nrec = hs->slc->nrec_req;
+       }
+
+       return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err)
+{
+       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;
+       struct headset_slc *slc = hs->slc;
+
+       if (strlen(buf) < 9)
+               return -EINVAL;
+
+       if (buf[8] == '0')
+               slc->nrec_req = FALSE;
+       else
+               slc->nrec_req = TRUE;
+
+       telephony_nr_and_ec_req(device, slc->nrec_req);
+
+       return 0;
+}
+
+static int voice_dial(struct audio_device *device, const char *buf)
+{
+       gboolean enable;
+
+       if (strlen(buf) < 9)
+               return -EINVAL;
+
+       if (buf[8] == '0')
+               enable = FALSE;
+       else
+               enable = TRUE;
+
+       telephony_voice_dial_req(device, enable);
+
+       return 0;
+}
+
+int telephony_list_phonebook_store_rsp(void *telephony_device, const char *buf, cme_error_t err)
+{
+       struct audio_device *device = telephony_device;
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+
+       if ( (err != CME_ERROR_NONE) || (NULL == buf) ) {
+               if (slc->cme_enabled)
+                       return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+               else
+                       return headset_send(hs, "\r\nERROR\r\n");
+       }
+
+       if (NULL != buf) {
+               if (!active_devices)
+                       return -ENODEV;
+
+               send_foreach_headset(active_devices, hfp_cmp,
+                                       "\r\n+CPBS: %s\r\n",buf);
+
+       }
+       return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_read_phonebook_store_rsp(void *telephony_device, char* pb_store, uint32_t total, uint32_t used, cme_error_t err)
+{
+       struct audio_device *device = telephony_device;
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+
+       if (err != CME_ERROR_NONE){
+               if (slc->cme_enabled)
+                       return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+               else
+                       return headset_send(hs, "\r\nERROR\r\n");
+       }
+
+       if( 0 != total && 0 != used) {
+               if (!active_devices)
+                       return -ENODEV;
+
+               send_foreach_headset(active_devices, hfp_cmp,
+                                       "\r\n+CPBS: \"%s\",%d,%d\r\n",pb_store,used,total);
+       }
+       return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int select_phonebook_memory(struct audio_device *device, const char *buf)
+{
+       struct headset *hs = device->headset;
+       int err;
+
+       if( NULL != buf) {
+               if (strlen(buf) < 9)
+                       return -EINVAL;
+
+               if (buf[7] == '?') {
+                       telephony_read_phonebook_store(device);
+                       return 0;
+               }
+
+               if (buf[8] == '=') {
+                       if(buf[9] == '?') {
+                               telephony_list_phonebook_store(device);
+                       }
+                       else {
+                               telephony_set_phonebook_store(device,&buf[9]);
+                               return headset_send(hs, "\r\nOK\r\n");
+                       }
+               }
+       }
+       return 0;
+}
+
+int telephony_read_phonebook_attributes_rsp(void *telephony_device,
+                                                                               uint32_t total, uint32_t number_length,
+                                                                               uint32_t name_length,cme_error_t err)
+{
+       struct audio_device *device = telephony_device;
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+
+       if (err != CME_ERROR_NONE) {
+               if (slc->cme_enabled)
+                       return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+               else
+                       return headset_send(hs, "\r\nERROR\r\n");
+       }
+
+       if( total != 0 && name_length !=0 && number_length != 0 ) {
+               if (!active_devices)
+                       return -ENODEV;
+
+               send_foreach_headset(active_devices, hfp_cmp,
+                                       "\r\n+CPBR: (1-%d),%d,%d\r\n",total,number_length,name_length);
+       }
+       return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_read_phonebook_rsp(void *telephony_device, const char *data, cme_error_t err)
+{
+       struct audio_device *device = telephony_device;
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+
+       if (err != CME_ERROR_NONE) {
+               if (slc->cme_enabled)
+                       return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+               else
+                       return headset_send(hs, "\r\nERROR\r\n");
+       }
+
+       if( NULL != data) {
+               if (!active_devices)
+                       return -ENODEV;
+
+               send_foreach_headset(active_devices, hfp_cmp,
+                                       "\r\n+CPBR:  %s\r\n",data);
+       }
+}
+
+static int read_phonebook_entries(struct audio_device *device, const char *buf)
+{
+       struct headset *hs = device->headset;
+       int err;
+
+       if( NULL != buf) {
+               if (strlen(buf) < 8)
+                       return -EINVAL;
+
+               if (buf[9] == '?') {
+                       telephony_read_phonebook_attributes(device);
+               }
+               else {
+                       telephony_read_phonebook(device,&buf[9]);
+               }
+       }
+       return 0;
+}
+
+int telephony_find_phonebook_entry_properties_rsp(void *telephony_device,
+                                                                               uint32_t max_number_length,
+                                                                               uint32_t max_name_length, cme_error_t err )
+{
+       struct audio_device *device = telephony_device;
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+
+       if (err != CME_ERROR_NONE) {
+               if (slc->cme_enabled)
+                       return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+               else
+                       return headset_send(hs, "\r\nERROR\r\n");
+       }
+
+       if( max_name_length !=0 && max_number_length != 0 ) {
+               if (!active_devices)
+                       return -ENODEV;
+
+               send_foreach_headset(active_devices, hfp_cmp,
+                                       "\r\n+CPBF: %d,%d\r\n",max_number_length,max_name_length);
+       }
+       return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int find_phonebook_entires(struct audio_device *device, const char *buf)
+{
+       struct headset *hs = device->headset;
+       int err;
+
+       if( NULL != buf )  {
+               if (strlen(buf) < 8)
+                       return -EINVAL;
+
+               if (buf[9] == '?') {
+                       telephony_find_phonebook_entry_properties(device);
+               }
+               else {
+                       telephony_find_phonebook_entry(device,&buf[9]);
+               }
+       }
+       return 0;
+}
+
+int telephony_list_preffered_store_rsp(void *telephony_device, char* prefrd_list, cme_error_t err )
+{
+       struct audio_device *device = telephony_device;
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+
+       if (err != CME_ERROR_NONE) {
+               if (slc->cme_enabled)
+                       return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+               else
+                       return headset_send(hs, "\r\nERROR\r\n");
+       }
+
+       if( NULL != prefrd_list ) {
+               if (!active_devices)
+                       return -ENODEV;
+
+               send_foreach_headset(active_devices, hfp_cmp,
+                                       "\r\n+CPMS: %s\r\n",prefrd_list);
+       }
+       return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_get_preffered_store_capacity_rsp(void *telephony_device, uint32_t store_capacity, cme_error_t err )
+{
+       struct audio_device *device = telephony_device;
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+
+       if (err != CME_ERROR_NONE) {
+               if (slc->cme_enabled)
+                       return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+               else
+                       return headset_send(hs, "\r\nERROR\r\n");
+       }
+
+       if( 0 != store_capacity ) {
+               if (!active_devices)
+                       return -ENODEV;
+
+               send_foreach_headset(active_devices, hfp_cmp,
+                                       "\r\n+CPMS: %d\r\n",store_capacity);
+       }
+       return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int preffered_message_storage(struct audio_device *device, const char *buf)
+{
+       struct headset *hs = device->headset;
+       int err;
+
+       if( NULL != buf ) {
+               if (strlen(buf) < 9)
+                       return -EINVAL;
+
+               if (buf[7] == '?') {
+                       telephony_get_preffered_store_capacity(device);
+                       return 0;
+               }
+
+               if (buf[8] == '=') {
+                       if(buf[9] == '?') {
+                               telephony_list_preffered_store(device);
+                       }
+                       else {
+                               //telephony_set_preffered_store_capcity(device,&buf[9]);
+                               return headset_send(hs, "\r\nOK\r\n");
+                       }
+               }
+       }
+       return 0;
+}
+
+int telephony_supported_character_generic_rsp(void *telephony_device, char* character_set_list, cme_error_t err )
+{
+       struct audio_device *device = telephony_device;
+       struct headset *hs = device->headset;
+       struct headset_slc *slc = hs->slc;
+
+       if (err != CME_ERROR_NONE) {
+               if (slc->cme_enabled)
+                       return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+               else
+                       return headset_send(hs, "\r\nERROR\r\n");
+       }
+
+       if( NULL != character_set_list ) {
+               if (!active_devices)
+                       return -ENODEV;
+
+               send_foreach_headset(active_devices, hfp_cmp,
+                                       "\r\n+CPMS: %s\r\n",character_set_list);
+       }
+       return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int select_character_set(struct audio_device *device, const char *buf)
+{
+       struct headset *hs = device->headset;
+       int err;
+       if( NULL != buf ) {
+               if (strlen(buf) < 9)
+                       return -EINVAL;
+
+               if (buf[7] == '?') {
+                       telephony_get_character_set(device);
+                       return 0;
+               }
+
+               if (buf[8] == '=') {
+                       if(buf[9] == '?') {
+                               telephony_list_supported_character(device);
+                       }
+                       else {
+                               //telephony_set_characterset(device,&buf[9]);
+                               return headset_send(hs, "\r\nOK\r\n");
+                       }
+               }
+       }
+       return 0;
+
+}
+static int apple_command(struct audio_device *device, const char *buf)
+{
+       DBG("Got Apple command: %s", buf);
+
+       return telephony_generic_rsp(device, CME_ERROR_NONE);
+}
+
+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 },
+       { "AT+BVRA", voice_dial },
+       { "AT+XAPL", apple_command },
+       { "AT+IPHONEACCEV", apple_command },
+
+       /*TIZEN PATCH Starts here*/
+       { "AT+CPBS", select_phonebook_memory },
+       { "AT+CPBR", read_phonebook_entries},
+       { "AT+CPBF", find_phonebook_entires },
+       { "AT+CPMS", preffered_message_storage },
+       { "AT+CSCS", select_character_set },
+
+       { 0 }
+};
+
+static int handle_event(struct audio_device *device, const char *buf)
+{
+       struct event *ev;
+
+       DBG("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;
+       struct headset_slc *slc;
+       unsigned char buf[BUF_SIZE];
+       ssize_t bytes_read;
+       size_t free_space;
+       int fd;
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       hs = device->headset;
+#ifdef __TIZEN_PATCH__
+       if(!hs)
+               return FALSE;
+#endif
+       slc = hs->slc;
+
+       if (cond & (G_IO_ERR | G_IO_HUP)) {
+               DBG("ERR or HUP on RFCOMM socket");
+               goto failed;
+       }
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       bytes_read = read(fd, buf, sizeof(buf) - 1);
+       if (bytes_read < 0)
+               return TRUE;
+
+       free_space = sizeof(slc->buf) - slc->data_start -
+                       slc->data_length - 1;
+
+       if (free_space < (size_t) 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(&slc->buf[slc->data_start], buf, bytes_read);
+       slc->data_length += bytes_read;
+
+       /* Make sure the data is null terminated so we can use string
+        * functions */
+       slc->buf[slc->data_start + slc->data_length] = '\0';
+
+       while (slc->data_length > 0) {
+               char *cr;
+               int err;
+               off_t cmd_len;
+
+               cr = strchr(&slc->buf[slc->data_start], '\r');
+               if (!cr)
+                       break;
+
+               cmd_len = 1 + (off_t) cr - (off_t) &slc->buf[slc->data_start];
+               *cr = '\0';
+
+               if (cmd_len > 1)
+                       err = handle_event(device, &slc->buf[slc->data_start]);
+               else
+                       /* Silently skip empty commands */
+                       err = 0;
+
+               if (err == -EINVAL) {
+                       error("Badly formated or unrecognized command: %s",
+                                       &slc->buf[slc->data_start]);
+#ifdef __TIZEN_PATCH__
+                       if (slc->cme_enabled)
+                               err = headset_send(hs, "\r\n+CME ERROR: %d\r\n",
+                                                       CME_ERROR_AG_FAILURE);
+                       else
+                               err = headset_send(hs, "\r\nERROR\r\n");
+#else
+                       err = headset_send(hs, "\r\nERROR\r\n");
+#endif
+                       if (err < 0)
+                               goto failed;
+               } else if (err < 0)
+                       error("Error handling command %s: %s (%d)",
+                                               &slc->buf[slc->data_start],
+                                               strerror(-err), -err);
+
+               slc->data_start += cmd_len;
+               slc->data_length -= cmd_len;
+
+               if (!slc->data_length)
+                       slc->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");
+
+       pending_connect_finalize(device);
+       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;
+
+#ifdef __TIZEN_PATCH__
+       hs->rfcomm_io_id = g_io_add_watch(chan,
+                               G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL,
+                               (GIOFunc) rfcomm_io_cb, dev);
+#else
+       g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL,
+                       (GIOFunc) rfcomm_io_cb, dev);
+#endif
+
+       DBG("%s: Connected to %s", dev->path, hs_address);
+
+       hs->slc = g_new0(struct headset_slc, 1);
+       hs->slc->sp_gain = 15;
+       hs->slc->mic_gain = 15;
+       hs->slc->nrec = TRUE;
+
+       /* 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_connect_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;
+               DBG("Discovered Handsfree service on channel %d", ch);
+       } else {
+               headset->hsp_handle = record->handle;
+               DBG("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;
+       uuid_t uuid;
+
+       assert(hs->pending != NULL);
+
+       if (err < 0) {
+               error("Unable to get service record: %s (%d)",
+                                                       strerror(-err), -err);
+               p->err = -err;
+               if (p->msg)
+                       error_connect_failed(dev->conn, p->msg, p->err);
+               goto failed;
+       }
+
+       if (!recs || !recs->data) {
+               error("No records found");
+               goto failed_not_supported;
+       }
+
+       sdp_uuid16_create(&uuid, p->svclass);
+
+       for (r = recs; r != NULL; r = r->next) {
+               sdp_list_t *classes;
+               uuid_t class;
+
+               record = r->data;
+
+               if (sdp_get_service_classes(record, &classes) < 0) {
+                       error("Unable to get service classes from record");
+                       continue;
+               }
+
+               memcpy(&class, classes->data, sizeof(uuid));
+
+               sdp_list_free(classes, free);
+
+               if (sdp_uuid_cmp(&class, &uuid) == 0)
+                       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;
+               if (p->msg != NULL)
+                       error_connect_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) {
+               DBusMessage *reply = btd_error_not_supported(p->msg);
+               g_dbus_send_message(dev->conn, reply);
+       }
+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_CONNECTING);
+
+       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);
+
+       DBG("%s: Connecting to %s channel %d", dev->path, address,
+               hs->rfcomm_ch);
+
+#ifdef __TIZEN_PATCH__
+       if(hs->rfcomm_io_id) {
+               g_source_remove(hs->rfcomm_io_id);
+               hs->rfcomm_io_id = 0;
+       }
+#endif
+       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;
+       hs->rfcomm_initiator = FALSE;
+
+       headset_set_state(dev, HEADSET_STATE_CONNECTING);
+
+       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 btd_error_not_connected(msg);
+
+       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 btd_error_not_connected(msg);
+
+       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_CONNECTING)
+               return btd_error_in_progress(msg);
+       else if (hs->state > HEADSET_STATE_CONNECTING)
+               return btd_error_already_connected(msg);
+
+       if (hs->hfp_handle && !ag.telephony_ready)
+               return btd_error_not_ready(msg);
+
+       device->auto_connect = FALSE;
+
+       err = rfcomm_connect(device, NULL, NULL, NULL);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       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 btd_error_not_connected(msg);
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       if (ag.ring_timer) {
+               DBG("IndicateCall received when already indicating");
+               return reply;
+       }
+
+       err = headset_send(hs, "\r\nRING\r\n");
+       if (err < 0) {
+               dbus_message_unref(reply);
+               return btd_error_failed(msg, strerror(-err));
+       }
+
+       ring_timer_cb(NULL);
+       ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
+                                               NULL);
+
+       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 btd_error_not_connected(msg);
+
+       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
+               DBG("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 btd_error_not_available(msg);
+       }
+
+       switch (hs->state) {
+       case HEADSET_STATE_DISCONNECTED:
+       case HEADSET_STATE_CONNECTING:
+               return btd_error_not_connected(msg);
+       case HEADSET_STATE_PLAY_IN_PROGRESS:
+               if (hs->pending && hs->pending->msg == NULL) {
+                       hs->pending->msg = dbus_message_ref(msg);
+                       return NULL;
+               }
+               return btd_error_busy(msg);
+       case HEADSET_STATE_PLAYING:
+               return btd_error_already_connected(msg);
+       case HEADSET_STATE_CONNECTED:
+       default:
+               break;
+       }
+
+       err = sco_connect(device, NULL, NULL, NULL);
+       if (err < 0)
+               return btd_error_failed(msg, 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;
+       struct headset_slc *slc = hs->slc;
+       DBusMessage *reply;
+       dbus_uint16_t gain;
+
+       if (hs->state < HEADSET_STATE_CONNECTED)
+               return btd_error_not_available(msg);
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       gain = (dbus_uint16_t) slc->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;
+       struct headset_slc *slc = hs->slc;
+       DBusMessage *reply;
+       dbus_uint16_t gain;
+
+       if (hs->state < HEADSET_STATE_CONNECTED || slc == NULL)
+               return btd_error_not_available(msg);
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       gain = (dbus_uint16_t) slc->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 btd_error_not_connected(msg);
+
+       err = headset_set_gain(device, gain, type);
+       if (err < 0)
+               return btd_error_invalid_args(msg);
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       if (hs->state == HEADSET_STATE_PLAYING) {
+               err = headset_send(hs, "\r\n+VG%c=%u\r\n", type, gain);
+               if (err < 0) {
+                       dbus_message_unref(reply);
+                       return btd_error_failed(msg, strerror(-err));
+               }
+       }
+
+       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->slc->sp_gain);
+
+       /* MicrophoneGain */
+       dict_append_entry(&dict, "MicrophoneGain",
+                               DBUS_TYPE_UINT16,
+                               &device->headset->slc->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 btd_error_invalid_args(msg);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+               return btd_error_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 btd_error_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 btd_error_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 btd_error_invalid_args(msg);
+
+               dbus_message_iter_get_basic(&sub, &gain);
+               return hs_set_gain(conn, msg, data, gain,
+                                       HEADSET_GAIN_MICROPHONE);
+       }
+
+       return btd_error_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 |
+                                               G_DBUS_METHOD_FLAG_DEPRECATED },
+       { "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:
+               DBG("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;
+       }
+
+#ifdef __TIZEN_PATCH__
+       if(hs->rfcomm_io_id) {
+               g_source_remove(hs->rfcomm_io_id);
+               hs->rfcomm_io_id = 0;
+       }
+#endif
+       g_free(hs->slc);
+       hs->slc = NULL;
+
+       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;
+       }
+
+       close_sco(dev);
+
+       headset_close_rfcomm(dev);
+
+       g_slist_free_full(hs->nrec_cbs, g_free);
+
+       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) {
+               DBG("Headset unregistered while device was connected!");
+               headset_shutdown(dev);
+       }
+
+       DBG("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->search_hfp = server_is_enabled(&dev->src, HANDSFREE_SVCLASS_ID);
+
+       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:
+               DBG("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;
+       }
+
+       DBG("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) {
+               DBG("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);
+       }
+
+       /* Init fast connectable option */
+       str = g_key_file_get_string(config, "Headset", "FastConnectable",
+                                       &err);
+       if (err) {
+               DBG("audio.conf: %s", err->message);
+               g_clear_error(&err);
+       } else {
+               fast_connectable = strcmp(str, "true") == 0;
+               if (fast_connectable)
+                       manager_set_fast_connectable(FALSE);
+               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_CONNECTING ||
+                       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_CONNECTING)
+               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;
+       int sock;
+
+       if (hs->state == HEADSET_STATE_DISCONNECTED ||
+                               hs->state == HEADSET_STATE_CONNECTING)
+               return 0;
+
+       if (hs->dc_timer) {
+               g_source_remove(hs->dc_timer);
+               hs->dc_timer = 0;
+       }
+
+       if (hs->sco) {
+               sock = g_io_channel_unix_get_fd(hs->sco);
+
+               /* shutdown but leave the socket open and wait for hup */
+               shutdown(sock, SHUT_RDWR);
+       } else {
+               headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+               g_idle_add((GSourceFunc) dummy_connect_complete, dev);
+       }
+
+       id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data);
+
+       return id;
+}
+
+gboolean headset_get_hfp_active(struct audio_device *dev)
+{
+       struct headset *hs = dev->headset;
+
+       return hs->hfp_active;
+}
+
+void headset_set_hfp_active(struct audio_device *dev, gboolean active)
+{
+       struct headset *hs = dev->headset;
+
+       hs->hfp_active = active;
+}
+
+gboolean headset_get_rfcomm_initiator(struct audio_device *dev)
+{
+       struct headset *hs = dev->headset;
+
+       return hs->rfcomm_initiator;
+}
+
+void headset_set_rfcomm_initiator(struct audio_device *dev,
+                                       gboolean initiator)
+{
+       struct headset *hs = dev->headset;
+
+       hs->rfcomm_initiator = initiator;
+}
+
+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;
+       struct headset_slc *slc = hs->slc;
+
+       if (hs->sco)
+               return -EISCONN;
+
+       hs->sco = g_io_channel_ref(io);
+
+       if (slc->pending_ring) {
+               ring_timer_cb(NULL);
+               ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL,
+                                               ring_timer_cb,
+                                               NULL);
+               slc->pending_ring = FALSE;
+       }
+
+       return 0;
+}
+
+void headset_set_state(struct audio_device *dev, headset_state_t state)
+{
+       struct headset *hs = dev->headset;
+       struct headset_slc *slc = hs->slc;
+       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_CONNECTING) {
+                       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);
+               break;
+       case HEADSET_STATE_CONNECTING:
+               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)
+                               slc->inband_ring = TRUE;
+                       else
+                               slc->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);
+               } 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);
+
+               /* Do not watch HUP since we need to know when the link is
+                  really disconnected */
+               hs->sco_id = g_io_add_watch(hs->sco,
+                                       G_IO_ERR | 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 (slc->sp_gain >= 0)
+                       headset_send(hs, "\r\n+VGS=%u\r\n", slc->sp_gain);
+               if (slc->mic_gain >= 0)
+                       headset_send(hs, "\r\n+VGM=%u\r\n", slc->mic_gain);
+               break;
+       }
+
+       hs->state = state;
+
+       DBG("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;
+
+       if (!hs->slc)
+               return TRUE;
+
+       return hs->slc->nrec;
+}
+
+unsigned int headset_add_nrec_cb(struct audio_device *dev,
+                                       headset_nrec_cb cb, void *user_data)
+{
+       struct headset *hs = dev->headset;
+       struct headset_nrec_callback *nrec_cb;
+       static unsigned int id = 0;
+
+       nrec_cb = g_new(struct headset_nrec_callback, 1);
+       nrec_cb->cb = cb;
+       nrec_cb->user_data = user_data;
+       nrec_cb->id = ++id;
+
+       hs->nrec_cbs = g_slist_prepend(hs->nrec_cbs, nrec_cb);
+
+       return nrec_cb->id;
+}
+
+gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id)
+{
+       struct headset *hs = dev->headset;
+       GSList *l;
+
+       for (l = hs->nrec_cbs; l != NULL; l = l->next) {
+               struct headset_nrec_callback *cb = l->data;
+               if (cb && cb->id == id) {
+                       hs->nrec_cbs = g_slist_remove(hs->nrec_cbs, cb);
+                       g_free(cb);
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+gboolean headset_get_inband(struct audio_device *dev)
+{
+       struct headset *hs = dev->headset;
+
+       if (!hs->slc)
+               return TRUE;
+
+       return hs->slc->inband_ring;
+}
+
+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_connect_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) {
+               DBG("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;
+       struct headset_slc *slc;
+
+       if (fast_connectable)
+               manager_set_fast_connectable(TRUE);
+
+       if (!active_devices)
+               return -ENODEV;
+
+       /* Get the latest connected device */
+       dev = active_devices->data;
+       hs = dev->headset;
+       slc = hs->slc;
+
+       if (ag.ring_timer) {
+               DBG("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 && slc->inband_ring)
+               return 0;
+
+       g_free(ag.number);
+       ag.number = g_strdup(number);
+       ag.number_type = type;
+
+       if (slc->inband_ring && hs->hfp_active &&
+                                       hs->state != HEADSET_STATE_PLAYING) {
+               slc->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 (fast_connectable)
+               manager_set_fast_connectable(FALSE);
+
+       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->slc->pending_ring && !ag.ring_timer)
+               return -EINVAL;
+
+       dev->headset->slc->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 = chld;
+
+       DBG("Telephony plugin initialized");
+
+       print_ag_features(ag.features);
+
+       return 0;
+}
+
+int telephony_deinit(void)
+{
+       g_free(ag.number);
+
+       memset(&ag, 0, sizeof(ag));
+
+       ag.er_mode = 3;
+       ag.rh = BTRH_NOT_SUPPORTED;
+
+       DBG("Telephony deinitialized");
+
+       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->slc->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 (file)
index 0000000..99eeca8
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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_CONNECTING,
+       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);
+typedef void (*headset_nrec_cb) (struct audio_device *dev,
+                                       gboolean nrec,
+                                       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 headset_get_hfp_active(struct audio_device *dev);
+void headset_set_hfp_active(struct audio_device *dev, gboolean active);
+
+gboolean headset_get_rfcomm_initiator(struct audio_device *dev);
+void headset_set_rfcomm_initiator(struct audio_device *dev,
+                                                       gboolean initiator);
+
+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);
+unsigned int headset_add_nrec_cb(struct audio_device *dev,
+                                       headset_nrec_cb cb, void *user_data);
+gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id);
+gboolean headset_get_inband(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 (file)
index 0000000..669eeef
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 (file)
index 0000000..61ae019
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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
+
+  <Moves to streaming state>
+  BT_START_STREAM_RSP-->
+
+  BT_NEW_STREAM_IND -->
+
+                               <  streams data >
+                               ..........
+
+                               on snd_pcm_drop/snd_pcm_drain
+
+                               <--BT_STOP_STREAM_REQ
+
+  <Moves to open state>
+  BT_STOP_STREAM_RSP-->
+
+                               on IPC close or appl crash
+  <Moves to idle>
+
+ */
+
+#ifndef BT_AUDIOCLIENT_H
+#define BT_AUDIOCLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+
+#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_DELAY_REPORT                        8
+
+#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 ancillary 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));
+
+struct bt_delay_report_req {
+       bt_audio_msg_header_t   h;
+       uint16_t                delay;
+} __attribute__ ((packed));
+
+struct bt_delay_report_ind {
+       bt_audio_msg_header_t   h;
+       uint16_t                delay;
+} __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/main.c b/audio/main.c
new file mode 100644 (file)
index 0000000..5c751af
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "glib-helper.h"
+#include "btio.h"
+#include "plugin.h"
+#include "log.h"
+#include "device.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) {
+                       DBG("Refusing SCO from non-connected headset");
+                       goto drop;
+               }
+
+               if (!headset_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)) {
+                       DBG("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);
+
+       DBG("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 (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();
+
+       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();
+
+       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 (file)
index 0000000..b8ade89
--- /dev/null
@@ -0,0 +1,1419 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <signal.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "glib-helper.h"
+#include "btio.h"
+#include "../src/adapter.h"
+#include "../src/manager.h"
+#include "../src/device.h"
+
+#include "log.h"
+#include "textfile.h"
+#include "ipc.h"
+#include "device.h"
+#include "error.h"
+#include "avdtp.h"
+#include "media.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "gateway.h"
+#include "sink.h"
+#include "source.h"
+#include "avrcp.h"
+#include "control.h"
+#include "manager.h"
+#include "sdpd.h"
+#include "telephony.h"
+#include "unix.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+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;
+       gboolean powered;
+       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,
+       .socket         = TRUE,
+       .media          = FALSE,
+};
+
+static struct audio_adapter *find_adapter(GSList *list,
+                                       struct btd_adapter *btd_adapter)
+{
+       for (; list; list = list->next) {
+               struct audio_adapter *adapter = list->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)) {
+               DBG("server not enabled for %s (0x%04x)", uuidstr, uuid16);
+               return;
+       }
+
+       switch (uuid16) {
+       case HEADSET_SVCLASS_ID:
+               DBG("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:
+               DBG("Found Headset AG record");
+               break;
+       case HANDSFREE_SVCLASS_ID:
+               DBG("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:
+               DBG("Found Handsfree AG record");
+               if (enabled.gateway && (device->gateway == NULL))
+                       device->gateway = gateway_init(device);
+               break;
+       case AUDIO_SINK_SVCLASS_ID:
+               DBG("Found Audio Sink");
+               if (device->sink == NULL)
+                       device->sink = sink_init(device);
+               break;
+       case AUDIO_SOURCE_SVCLASS_ID:
+               DBG("Found Audio Source");
+               if (device->source == NULL)
+                       device->source = source_init(device);
+               break;
+       case AV_REMOTE_SVCLASS_ID:
+       case AV_REMOTE_TARGET_SVCLASS_ID:
+               DBG("Found AV %s", uuid16 == AV_REMOTE_SVCLASS_ID ?
+                                                       "Remote" : "Target");
+               if (device->control)
+                       control_update(device->control, uuid16);
+               else
+                       device->control = control_init(device, uuid16);
+
+               if (device->sink && sink_is_active(device))
+                       avrcp_connect(device);
+               break;
+       default:
+               DBG("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 = 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]);
+
+       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;
+
+       record = sdp_record_alloc();
+       if (!record)
+               return NULL;
+
+       network = sdp_data_alloc(SDP_UINT8, &netid);
+       if (!network) {
+               sdp_record_free(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 (device->hs_preauth_id) {
+               g_source_remove(device->hs_preauth_id);
+               device->hs_preauth_id = 0;
+       }
+
+       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 gboolean hs_preauth_cb(GIOChannel *chan, GIOCondition cond,
+                                                       gpointer user_data)
+{
+       struct audio_device *device = user_data;
+
+       DBG("Headset disconnected during authorization");
+
+       audio_device_cancel_authorization(device, headset_auth_cb, device);
+
+       headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+
+       device->hs_preauth_id = 0;
+
+       return FALSE;
+}
+
+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)) {
+               DBG("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) {
+               DBG("Refusing new connection since one already exists");
+               goto drop;
+       }
+
+       headset_set_hfp_active(device, hfp_active);
+       headset_set_rfcomm_initiator(device, TRUE);
+
+       if (headset_connect_rfcomm(device, chan) < 0) {
+               error("headset_connect_rfcomm failed");
+               goto drop;
+       }
+
+       headset_set_state(device, HEADSET_STATE_CONNECTING);
+
+       perr = audio_device_request_authorization(device, server_uuid,
+                                               headset_auth_cb, device);
+       if (perr < 0) {
+               DBG("Authorization denied: %s", strerror(-perr));
+               headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+               return;
+       }
+
+       device->hs_preauth_id = g_io_add_watch(chan,
+                                       G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+                                       hs_preauth_cb, device);
+
+       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);
+               gateway_set_state(device, GATEWAY_STATE_DISCONNECTED);
+       } else {
+               char ag_address[18];
+
+               ba2str(&device->dst, ag_address);
+               DBG("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;
+       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_AG_UUID;
+       remote_uuid = HFP_HS_UUID;
+
+       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_active(device)) {
+               DBG("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) {
+               DBG("Authorization denied!");
+               gateway_set_state(device, GATEWAY_STATE_DISCONNECTED);
+       }
+
+       return;
+
+drop:
+       g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+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) {
+                       DBG("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:
+       if (err) {
+               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) {
+                       DBG("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) {
+               DBG("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++;
+
+       DBG("%p: ref=%d", adp, adp->ref);
+
+       return adp;
+}
+
+static void audio_adapter_unref(struct audio_adapter *adp)
+{
+       adp->ref--;
+
+       DBG("%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 void state_changed(struct btd_adapter *adapter, gboolean powered)
+{
+       struct audio_adapter *adp;
+       static gboolean telephony = FALSE;
+       GSList *l;
+
+       DBG("%s powered %s", adapter_get_path(adapter),
+                                               powered ? "on" : "off");
+
+       /* ignore powered change, adapter is powering down */
+       if (powered && adapter_powering_down(adapter))
+               return;
+
+       adp = find_adapter(adapters, adapter);
+       if (!adp)
+               return;
+
+       adp->powered = powered;
+
+       if (powered) {
+               /* telephony driver already initialized*/
+               if (telephony == TRUE)
+                       return;
+               telephony_init();
+               telephony = TRUE;
+               return;
+       }
+
+       /* telephony not initialized just ignore power down */
+       if (telephony == FALSE)
+               return;
+
+       for (l = adapters; l; l = l->next) {
+               adp = l->data;
+
+               if (adp->powered == TRUE)
+                       return;
+       }
+
+       telephony_exit();
+       telephony = FALSE;
+}
+
+static int headset_server_probe(struct btd_adapter *adapter)
+{
+       struct audio_adapter *adp;
+       const gchar *path = adapter_get_path(adapter);
+       int err;
+
+       DBG("path %s", path);
+
+       adp = audio_adapter_get(adapter);
+       if (!adp)
+               return -EINVAL;
+
+       btd_adapter_register_powered_callback(adapter, state_changed);
+       state_changed(adapter, TRUE);
+
+       err = headset_server_init(adp);
+       if (err < 0) {
+               audio_adapter_unref(adp);
+               return err;
+       }
+
+       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;
+       }
+
+       btd_adapter_unregister_powered_callback(adapter, state_changed);
+
+       audio_adapter_unref(adp);
+}
+
+static int gateway_server_probe(struct btd_adapter *adapter)
+{
+       struct audio_adapter *adp;
+
+       adp = audio_adapter_get(adapter);
+       if (!adp)
+               return -EINVAL;
+
+       return gateway_server_init(adp);
+}
+
+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_unref(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 err;
+
+       DBG("path %s", path);
+
+       adp = audio_adapter_get(adapter);
+       if (!adp)
+               return -EINVAL;
+
+       adapter_get_address(adapter, &src);
+
+       err = a2dp_register(connection, &src, config);
+       if (err < 0)
+               audio_adapter_unref(adp);
+
+       return err;
+}
+
+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 int media_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 media_register(connection, path, &src);
+}
+
+static void media_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;
+
+       media_unregister(path);
+       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,
+};
+
+static struct btd_adapter_driver media_server_driver = {
+       .name   = "media",
+       .probe  = media_server_probe,
+       .remove = media_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;
+               else if (g_str_equal(list[i], "Socket"))
+                       enabled.socket = TRUE;
+               else if (g_str_equal(list[i], "Media"))
+                       enabled.media = 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;
+               else if (g_str_equal(list[i], "Socket"))
+                       enabled.socket = FALSE;
+               else if (g_str_equal(list[i], "Media"))
+                       enabled.media = FALSE;
+       }
+       g_strfreev(list);
+
+       b = g_key_file_get_boolean(config, "General", "AutoConnect", &err);
+       if (err) {
+               DBG("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) {
+               DBG("audio.conf: %s", err->message);
+               g_clear_error(&err);
+       } else
+               max_connected_headsets = i;
+
+proceed:
+       if (enabled.socket)
+               unix_init();
+
+       if (enabled.media)
+               btd_register_adapter_driver(&media_server_driver);
+
+       if (enabled.headset)
+               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.socket)
+               unix_exit();
+
+       if (enabled.media)
+               btd_unregister_adapter_driver(&media_server_driver);
+
+       if (enabled.headset)
+               btd_unregister_adapter_driver(&headset_server_driver);
+
+       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;
+}
+
+void manager_set_fast_connectable(gboolean enable)
+{
+       GSList *l;
+
+       for (l = adapters; l != NULL; l = l->next) {
+               struct audio_adapter *adapter = l->data;
+
+               if (btd_adapter_set_fast_connectable(adapter->btd_adapter,
+                                                               enable))
+                       error("Changing fast connectable for hci%d failed",
+                               adapter_get_dev_id(adapter->btd_adapter));
+       }
+}
diff --git a/audio/manager.h b/audio/manager.h
new file mode 100644 (file)
index 0000000..cfc646c
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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;
+       gboolean socket;
+       gboolean media;
+       gboolean media_player;
+};
+
+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);
+
+/* TRUE to enable fast connectable and FALSE to disable fast connectable for all
+ * audio adapters. */
+void manager_set_fast_connectable(gboolean enable);
diff --git a/audio/media.c b/audio/media.c
new file mode 100644 (file)
index 0000000..cf157d7
--- /dev/null
@@ -0,0 +1,1769 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/adapter.h"
+#include "../src/dbus-common.h"
+
+#include "glib-helper.h"
+#include "log.h"
+#include "error.h"
+#include "device.h"
+#include "avdtp.h"
+#include "media.h"
+#include "transport.h"
+#include "a2dp.h"
+#include "avrcp.h"
+#include "headset.h"
+#include "gateway.h"
+#include "manager.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+#define MEDIA_INTERFACE "org.bluez.Media"
+#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint"
+#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer"
+
+#define REQUEST_TIMEOUT (3 * 1000)             /* 3 seconds */
+
+struct media_adapter {
+       bdaddr_t                src;            /* Adapter address */
+       char                    *path;          /* Adapter path */
+       DBusConnection          *conn;          /* Adapter connection */
+       GSList                  *endpoints;     /* Endpoints list */
+       GSList                  *players;       /* Players list */
+};
+
+struct endpoint_request {
+       DBusMessage             *msg;
+       DBusPendingCall         *call;
+       media_endpoint_cb_t     cb;
+       GDestroyNotify          destroy;
+       void                    *user_data;
+};
+
+struct media_endpoint {
+       struct a2dp_sep         *sep;
+       char                    *sender;        /* Endpoint DBus bus id */
+       char                    *path;          /* Endpoint object path */
+       char                    *uuid;          /* Endpoint property UUID */
+       uint8_t                 codec;          /* Endpoint codec */
+       uint8_t                 *capabilities;  /* Endpoint property capabilities */
+       size_t                  size;           /* Endpoint capabilities size */
+       guint                   hs_watch;
+       guint                   ag_watch;
+       guint                   watch;
+       struct endpoint_request *request;
+       struct media_transport  *transport;
+       struct media_adapter    *adapter;
+};
+
+struct media_player {
+       struct media_adapter    *adapter;
+       struct avrcp_player     *player;
+       char                    *sender;        /* Player DBus bus id */
+       char                    *path;          /* Player object path */
+       GHashTable              *settings;      /* Player settings */
+       GHashTable              *track;         /* Player current track */
+       guint                   watch;
+       guint                   property_watch;
+       guint                   track_watch;
+       uint8_t                 status;
+       uint32_t                position;
+       GTimer                  *timer;
+};
+
+struct metadata_value {
+       int                     type;
+       union {
+               char            *str;
+               uint32_t        num;
+       } value;
+};
+
+static GSList *adapters = NULL;
+
+static void endpoint_request_free(struct endpoint_request *request)
+{
+       if (request->call)
+               dbus_pending_call_unref(request->call);
+
+       if (request->destroy)
+               request->destroy(request->user_data);
+
+       dbus_message_unref(request->msg);
+       g_free(request);
+}
+
+static void media_endpoint_cancel(struct media_endpoint *endpoint)
+{
+       struct endpoint_request *request = endpoint->request;
+
+       if (request->call)
+               dbus_pending_call_cancel(request->call);
+
+       endpoint_request_free(request);
+       endpoint->request = NULL;
+}
+
+static void media_endpoint_destroy(struct media_endpoint *endpoint)
+{
+       struct media_adapter *adapter = endpoint->adapter;
+
+       DBG("sender=%s path=%s", endpoint->sender, endpoint->path);
+
+       if (endpoint->hs_watch)
+               headset_remove_state_cb(endpoint->hs_watch);
+
+       if (endpoint->ag_watch)
+               gateway_remove_state_cb(endpoint->ag_watch);
+
+       if (endpoint->request)
+               media_endpoint_cancel(endpoint);
+
+       if (endpoint->transport)
+               media_transport_destroy(endpoint->transport);
+
+       g_dbus_remove_watch(adapter->conn, endpoint->watch);
+       g_free(endpoint->capabilities);
+       g_free(endpoint->sender);
+       g_free(endpoint->path);
+       g_free(endpoint->uuid);
+       g_free(endpoint);
+}
+
+static void media_endpoint_remove(struct media_endpoint *endpoint)
+{
+       struct media_adapter *adapter = endpoint->adapter;
+
+       if (endpoint->sep) {
+               a2dp_remove_sep(endpoint->sep);
+               return;
+       }
+
+       info("Endpoint unregistered: sender=%s path=%s", endpoint->sender,
+                       endpoint->path);
+
+       adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint);
+
+       media_endpoint_destroy(endpoint);
+}
+
+static void media_endpoint_exit(DBusConnection *connection, void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+
+       endpoint->watch = 0;
+       media_endpoint_remove(endpoint);
+}
+
+static void headset_setconf_cb(struct media_endpoint *endpoint, void *ret,
+                                               int size, void *user_data)
+{
+       struct audio_device *dev = user_data;
+
+       if (ret != NULL)
+               return;
+
+       headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+static void clear_configuration(struct media_endpoint *endpoint)
+{
+       DBusConnection *conn;
+       DBusMessage *msg;
+       const char *path;
+       struct media_transport *transport = endpoint->transport;
+
+       if (endpoint->transport == NULL)
+               return;
+
+       if (endpoint->request)
+               media_endpoint_cancel(endpoint);
+
+       conn = endpoint->adapter->conn;
+
+       msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+                                               MEDIA_ENDPOINT_INTERFACE,
+                                               "ClearConfiguration");
+       if (msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               goto done;
+       }
+
+       path = media_transport_get_path(endpoint->transport);
+       dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
+                                                       DBUS_TYPE_INVALID);
+       g_dbus_send_message(conn, msg);
+done:
+       endpoint->transport = NULL;
+       media_transport_destroy(transport);
+}
+
+static void endpoint_reply(DBusPendingCall *call, void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+       struct endpoint_request *request = endpoint->request;
+       DBusMessage *reply;
+       DBusError err;
+       gboolean value;
+       void *ret = NULL;
+       int size = -1;
+
+       /* steal_reply will always return non-NULL since the callback
+        * is only called after a reply has been received */
+       reply = dbus_pending_call_steal_reply(call);
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, reply)) {
+               error("Endpoint replied with an error: %s",
+                               err.name);
+
+               /* Clear endpoint configuration in case of NO_REPLY error */
+               if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) {
+                       if (request->cb)
+                               request->cb(endpoint, NULL, size,
+                                                       request->user_data);
+                       clear_configuration(endpoint);
+                       dbus_message_unref(reply);
+                       dbus_error_free(&err);
+                       return;
+               }
+
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       dbus_error_init(&err);
+       if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE,
+                               "SelectConfiguration")) {
+               DBusMessageIter args, array;
+               uint8_t *configuration;
+
+               dbus_message_iter_init(reply, &args);
+
+               dbus_message_iter_recurse(&args, &array);
+
+               dbus_message_iter_get_fixed_array(&array, &configuration, &size);
+
+               ret = configuration;
+               goto done;
+       } else  if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) {
+               error("Wrong reply signature: %s", err.message);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       size = 1;
+       value = TRUE;
+       ret = &value;
+
+done:
+       dbus_message_unref(reply);
+
+       if (request->cb)
+               request->cb(endpoint, ret, size, request->user_data);
+
+       if (endpoint->request)
+               endpoint_request_free(endpoint->request);
+       endpoint->request = NULL;
+}
+
+static gboolean media_endpoint_async_call(DBusConnection *conn,
+                                       DBusMessage *msg,
+                                       struct media_endpoint *endpoint,
+                                       media_endpoint_cb_t cb,
+                                       void *user_data,
+                                       GDestroyNotify destroy)
+{
+       struct endpoint_request *request;
+
+       if (endpoint->request)
+               return FALSE;
+
+       request = g_new0(struct endpoint_request, 1);
+
+       /* Timeout should be less than avdtp request timeout (4 seconds) */
+       if (dbus_connection_send_with_reply(conn, msg, &request->call,
+                                               REQUEST_TIMEOUT) == FALSE) {
+               error("D-Bus send failed");
+               g_free(request);
+               return FALSE;
+       }
+
+       dbus_pending_call_set_notify(request->call, endpoint_reply, endpoint, NULL);
+
+       request->msg = msg;
+       request->cb = cb;
+       request->destroy = destroy;
+       request->user_data = user_data;
+       endpoint->request = request;
+
+       DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg),
+                       dbus_message_get_destination(msg),
+                       dbus_message_get_path(msg));
+
+       return TRUE;
+}
+
+static gboolean select_configuration(struct media_endpoint *endpoint,
+                                               uint8_t *capabilities,
+                                               size_t length,
+                                               media_endpoint_cb_t cb,
+                                               void *user_data,
+                                               GDestroyNotify destroy)
+{
+       DBusConnection *conn;
+       DBusMessage *msg;
+
+       if (endpoint->request != NULL)
+               return FALSE;
+
+       conn = endpoint->adapter->conn;
+
+       msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+                                               MEDIA_ENDPOINT_INTERFACE,
+                                               "SelectConfiguration");
+       if (msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return FALSE;
+       }
+
+       dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+                                       &capabilities, length,
+                                       DBUS_TYPE_INVALID);
+
+       return media_endpoint_async_call(conn, msg, endpoint, cb, user_data,
+                                                               destroy);
+}
+
+static gboolean set_configuration(struct media_endpoint *endpoint,
+                                       struct audio_device *device,
+                                       uint8_t *configuration, size_t size,
+                                       media_endpoint_cb_t cb,
+                                       void *user_data,
+                                       GDestroyNotify destroy)
+{
+       DBusConnection *conn;
+       DBusMessage *msg;
+       const char *path;
+       DBusMessageIter iter;
+
+       if (endpoint->transport != NULL || endpoint->request != NULL)
+               return FALSE;
+
+       conn = endpoint->adapter->conn;
+
+       endpoint->transport = media_transport_create(conn, endpoint, device,
+                                               configuration, size);
+       if (endpoint->transport == NULL)
+               return FALSE;
+
+       msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+                                               MEDIA_ENDPOINT_INTERFACE,
+                                               "SetConfiguration");
+       if (msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return FALSE;
+       }
+
+       dbus_message_iter_init_append(msg, &iter);
+
+       path = media_transport_get_path(endpoint->transport);
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
+
+       transport_get_properties(endpoint->transport, &iter);
+
+       return media_endpoint_async_call(conn, msg, endpoint, cb, user_data,
+                                                               destroy);
+}
+
+static void release_endpoint(struct media_endpoint *endpoint)
+{
+       DBusMessage *msg;
+
+       DBG("sender=%s path=%s", endpoint->sender, endpoint->path);
+
+       /* already exit */
+       if (endpoint->watch == 0)
+               goto done;
+
+       msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+                                               MEDIA_ENDPOINT_INTERFACE,
+                                               "Release");
+       if (msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return;
+       }
+
+       g_dbus_send_message(endpoint->adapter->conn, msg);
+
+done:
+       media_endpoint_remove(endpoint);
+}
+
+static void headset_state_changed(struct audio_device *dev,
+                                       headset_state_t old_state,
+                                       headset_state_t new_state,
+                                       void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+
+       DBG("");
+
+       switch (new_state) {
+       case HEADSET_STATE_DISCONNECTED:
+               if (endpoint->transport &&
+                       media_transport_get_dev(endpoint->transport) == dev) {
+
+                       DBG("Clear endpoint %p", endpoint);
+                       clear_configuration(endpoint);
+               }
+               break;
+       case HEADSET_STATE_CONNECTING:
+               set_configuration(endpoint, dev, NULL, 0, headset_setconf_cb,
+                                                               dev, NULL);
+               break;
+       case HEADSET_STATE_CONNECTED:
+               break;
+       case HEADSET_STATE_PLAY_IN_PROGRESS:
+               break;
+       case HEADSET_STATE_PLAYING:
+               break;
+       }
+}
+
+static const char *get_name(struct a2dp_sep *sep, void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+
+       return endpoint->sender;
+}
+
+static size_t get_capabilities(struct a2dp_sep *sep, uint8_t **capabilities,
+                                                       void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+
+       *capabilities = endpoint->capabilities;
+       return endpoint->size;
+}
+
+struct a2dp_config_data {
+       guint setup_id;
+       a2dp_endpoint_config_t cb;
+};
+
+struct a2dp_select_data {
+       guint setup_id;
+       a2dp_endpoint_select_t cb;
+};
+
+static void select_cb(struct media_endpoint *endpoint, void *ret, int size,
+                                                       void *user_data)
+{
+       struct a2dp_select_data *data = user_data;
+
+       data->cb(endpoint->sep, data->setup_id, ret, size);
+}
+
+static int select_config(struct a2dp_sep *sep, uint8_t *capabilities,
+                               size_t length, guint setup_id,
+                               a2dp_endpoint_select_t cb, void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+       struct a2dp_select_data *data;
+
+       data = g_new0(struct a2dp_select_data, 1);
+       data->setup_id = setup_id;
+       data->cb = cb;
+
+       if (select_configuration(endpoint, capabilities, length,
+                                       select_cb, data, g_free) == TRUE)
+               return 0;
+
+       g_free(data);
+       return -ENOMEM;
+}
+
+static void config_cb(struct media_endpoint *endpoint, void *ret, int size,
+                                                       void *user_data)
+{
+       struct a2dp_config_data *data = user_data;
+
+       data->cb(endpoint->sep, data->setup_id, ret ? TRUE : FALSE);
+}
+
+static int set_config(struct a2dp_sep *sep, struct audio_device *dev,
+                               uint8_t *configuration, size_t length,
+                               guint setup_id, a2dp_endpoint_config_t cb,
+                               void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+       struct a2dp_config_data *data;
+
+       data = g_new0(struct a2dp_config_data, 1);
+       data->setup_id = setup_id;
+       data->cb = cb;
+
+       if (set_configuration(endpoint, dev, configuration, length,
+                                       config_cb, data, g_free) == TRUE)
+               return 0;
+
+       g_free(data);
+       return -ENOMEM;
+}
+
+static void clear_config(struct a2dp_sep *sep, void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+
+       clear_configuration(endpoint);
+}
+
+static void set_delay(struct a2dp_sep *sep, uint16_t delay, void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+
+       if (endpoint->transport == NULL)
+               return;
+
+       media_transport_update_delay(endpoint->transport, delay);
+}
+
+static struct a2dp_endpoint a2dp_endpoint = {
+       .get_name = get_name,
+       .get_capabilities = get_capabilities,
+       .select_configuration = select_config,
+       .set_configuration = set_config,
+       .clear_configuration = clear_config,
+       .set_delay = set_delay
+};
+
+static void a2dp_destroy_endpoint(void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+
+       if (endpoint->transport) {
+               media_transport_destroy(endpoint->transport);
+               endpoint->transport = NULL;
+       }
+
+       endpoint->sep = NULL;
+       release_endpoint(endpoint);
+}
+
+static void gateway_setconf_cb(struct media_endpoint *endpoint, void *ret,
+                                               int size, void *user_data)
+{
+       struct audio_device *dev = user_data;
+
+       if (ret != NULL)
+               return;
+
+       gateway_set_state(dev, GATEWAY_STATE_DISCONNECTED);
+}
+
+static void gateway_state_changed(struct audio_device *dev,
+                                       gateway_state_t old_state,
+                                       gateway_state_t new_state,
+                                       void *user_data)
+{
+       struct media_endpoint *endpoint = user_data;
+
+       DBG("");
+
+       switch (new_state) {
+       case GATEWAY_STATE_DISCONNECTED:
+               if (endpoint->transport &&
+                       media_transport_get_dev(endpoint->transport) == dev) {
+
+                       DBG("Clear endpoint %p", endpoint);
+                       clear_configuration(endpoint);
+               }
+               break;
+       case GATEWAY_STATE_CONNECTING:
+               set_configuration(endpoint, dev, NULL, 0,
+                                       gateway_setconf_cb, dev, NULL);
+               break;
+       case GATEWAY_STATE_CONNECTED:
+               break;
+       case GATEWAY_STATE_PLAYING:
+               break;
+       }
+}
+
+static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter,
+                                               const char *sender,
+                                               const char *path,
+                                               const char *uuid,
+                                               gboolean delay_reporting,
+                                               uint8_t codec,
+                                               uint8_t *capabilities,
+                                               int size,
+                                               int *err)
+{
+       struct media_endpoint *endpoint;
+
+       endpoint = g_new0(struct media_endpoint, 1);
+       endpoint->sender = g_strdup(sender);
+       endpoint->path = g_strdup(path);
+       endpoint->uuid = g_strdup(uuid);
+       endpoint->codec = codec;
+
+       if (size > 0) {
+               endpoint->capabilities = g_new(uint8_t, size);
+               memcpy(endpoint->capabilities, capabilities, size);
+               endpoint->size = size;
+       }
+
+       endpoint->adapter = adapter;
+
+       if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) {
+               endpoint->sep = a2dp_add_sep(&adapter->src,
+                                       AVDTP_SEP_TYPE_SOURCE, codec,
+                                       delay_reporting, &a2dp_endpoint,
+                                       endpoint, a2dp_destroy_endpoint, err);
+               if (endpoint->sep == NULL)
+                       goto failed;
+       } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) {
+               endpoint->sep = a2dp_add_sep(&adapter->src,
+                                       AVDTP_SEP_TYPE_SOURCE, codec,
+                                       delay_reporting, &a2dp_endpoint,
+                                       endpoint, a2dp_destroy_endpoint, err);
+               if (endpoint->sep == NULL)
+                       goto failed;
+       } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 ||
+                                       strcasecmp(uuid, HSP_AG_UUID) == 0) {
+               struct audio_device *dev;
+
+               endpoint->hs_watch = headset_add_state_cb(headset_state_changed,
+                                                               endpoint);
+               dev = manager_find_device(NULL, &adapter->src, BDADDR_ANY,
+                                               AUDIO_HEADSET_INTERFACE, TRUE);
+               if (dev)
+                       set_configuration(endpoint, dev, NULL, 0,
+                                               headset_setconf_cb, dev, NULL);
+       } else if (strcasecmp(uuid, HFP_HS_UUID) == 0 ||
+                                       strcasecmp(uuid, HSP_HS_UUID) == 0) {
+               struct audio_device *dev;
+
+               endpoint->ag_watch = gateway_add_state_cb(gateway_state_changed,
+                                                               endpoint);
+               dev = manager_find_device(NULL, &adapter->src, BDADDR_ANY,
+                                               AUDIO_GATEWAY_INTERFACE, TRUE);
+               if (dev)
+                       set_configuration(endpoint, dev, NULL, 0,
+                                               gateway_setconf_cb, dev, NULL);
+       } else {
+               if (err)
+                       *err = -EINVAL;
+               goto failed;
+       }
+
+       endpoint->watch = g_dbus_add_disconnect_watch(adapter->conn, sender,
+                                               media_endpoint_exit, endpoint,
+                                               NULL);
+
+       adapter->endpoints = g_slist_append(adapter->endpoints, endpoint);
+       info("Endpoint registered: sender=%s path=%s", sender, path);
+
+       if (err)
+               *err = 0;
+       return endpoint;
+
+failed:
+       g_free(endpoint);
+       return NULL;
+}
+
+static struct media_endpoint *media_adapter_find_endpoint(
+                                               struct media_adapter *adapter,
+                                               const char *sender,
+                                               const char *path,
+                                               const char *uuid)
+{
+       GSList *l;
+
+       for (l = adapter->endpoints; l; l = l->next) {
+               struct media_endpoint *endpoint = l->data;
+
+               if (sender && g_strcmp0(endpoint->sender, sender) != 0)
+                       continue;
+
+               if (path && g_strcmp0(endpoint->path, path) != 0)
+                       continue;
+
+               if (uuid && g_strcmp0(endpoint->uuid, uuid) != 0)
+                       continue;
+
+               return endpoint;
+       }
+
+       return NULL;
+}
+
+static int parse_properties(DBusMessageIter *props, const char **uuid,
+                               gboolean *delay_reporting, uint8_t *codec,
+                               uint8_t **capabilities, int *size)
+{
+       gboolean has_uuid = FALSE;
+       gboolean has_codec = FALSE;
+
+       while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
+               const char *key;
+               DBusMessageIter value, entry;
+               int var;
+
+               dbus_message_iter_recurse(props, &entry);
+               dbus_message_iter_get_basic(&entry, &key);
+
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_recurse(&entry, &value);
+
+               var = dbus_message_iter_get_arg_type(&value);
+               if (strcasecmp(key, "UUID") == 0) {
+                       if (var != DBUS_TYPE_STRING)
+                               return -EINVAL;
+                       dbus_message_iter_get_basic(&value, uuid);
+                       has_uuid = TRUE;
+               } else if (strcasecmp(key, "Codec") == 0) {
+                       if (var != DBUS_TYPE_BYTE)
+                               return -EINVAL;
+                       dbus_message_iter_get_basic(&value, codec);
+                       has_codec = TRUE;
+               } else if (strcasecmp(key, "DelayReporting") == 0) {
+                       if (var != DBUS_TYPE_BOOLEAN)
+                               return -EINVAL;
+                       dbus_message_iter_get_basic(&value, delay_reporting);
+               } else if (strcasecmp(key, "Capabilities") == 0) {
+                       DBusMessageIter array;
+
+                       if (var != DBUS_TYPE_ARRAY)
+                               return -EINVAL;
+
+                       dbus_message_iter_recurse(&value, &array);
+                       dbus_message_iter_get_fixed_array(&array, capabilities,
+                                                       size);
+               }
+
+               dbus_message_iter_next(props);
+       }
+
+       return (has_uuid && has_codec) ? 0 : -EINVAL;
+}
+
+static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg,
+                                       void *data)
+{
+       struct media_adapter *adapter = data;
+       DBusMessageIter args, props;
+       const char *sender, *path, *uuid;
+       gboolean delay_reporting = FALSE;
+       uint8_t codec;
+       uint8_t *capabilities;
+       int size = 0;
+       int err;
+
+       sender = dbus_message_get_sender(msg);
+
+       dbus_message_iter_init(msg, &args);
+
+       dbus_message_iter_get_basic(&args, &path);
+       dbus_message_iter_next(&args);
+
+       if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL)
+               return btd_error_already_exists(msg);
+
+       dbus_message_iter_recurse(&args, &props);
+       if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+               return btd_error_invalid_args(msg);
+
+       if (parse_properties(&props, &uuid, &delay_reporting, &codec,
+                                               &capabilities, &size) < 0)
+               return btd_error_invalid_args(msg);
+
+       if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting,
+                               codec, capabilities, size, &err) == NULL) {
+               if (err == -EPROTONOSUPPORT)
+                       return btd_error_not_supported(msg);
+               else
+                       return btd_error_invalid_args(msg);
+       }
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg,
+                                       void *data)
+{
+       struct media_adapter *adapter = data;
+       struct media_endpoint *endpoint;
+       const char *sender, *path;
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID))
+               return NULL;
+
+       sender = dbus_message_get_sender(msg);
+
+       endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL);
+       if (endpoint == NULL)
+               return btd_error_does_not_exist(msg);
+
+       media_endpoint_remove(endpoint);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static struct media_player *media_adapter_find_player(
+                                               struct media_adapter *adapter,
+                                               const char *sender,
+                                               const char *path)
+{
+       GSList *l;
+
+       for (l = adapter->players; l; l = l->next) {
+               struct media_player *mp = l->data;
+
+               if (sender && g_strcmp0(mp->sender, sender) != 0)
+                       continue;
+
+               if (path && g_strcmp0(mp->path, path) != 0)
+                       continue;
+
+               return mp;
+       }
+
+       return NULL;
+}
+
+static void release_player(struct media_player *mp)
+{
+       DBusMessage *msg;
+
+       DBG("sender=%s path=%s", mp->sender, mp->path);
+
+       msg = dbus_message_new_method_call(mp->sender, mp->path,
+                                               MEDIA_PLAYER_INTERFACE,
+                                               "Release");
+       if (msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return;
+       }
+
+       g_dbus_send_message(mp->adapter->conn, msg);
+}
+
+static void media_player_free(gpointer data)
+{
+       struct media_player *mp = data;
+       struct media_adapter *adapter = mp->adapter;
+
+       if (mp->player) {
+               adapter->players = g_slist_remove(adapter->players, mp);
+               release_player(mp);
+       }
+
+       g_dbus_remove_watch(adapter->conn, mp->watch);
+       g_dbus_remove_watch(adapter->conn, mp->property_watch);
+       g_dbus_remove_watch(adapter->conn, mp->track_watch);
+
+       if (mp->track)
+               g_hash_table_unref(mp->track);
+
+       if (mp->settings)
+               g_hash_table_unref(mp->settings);
+
+       g_timer_destroy(mp->timer);
+       g_free(mp->sender);
+       g_free(mp->path);
+       g_free(mp);
+}
+
+static void media_player_destroy(struct media_player *mp)
+{
+       struct media_adapter *adapter = mp->adapter;
+
+       DBG("sender=%s path=%s", mp->sender, mp->path);
+
+       if (mp->player) {
+               struct avrcp_player *player = mp->player;
+               mp->player = NULL;
+               adapter->players = g_slist_remove(adapter->players, mp);
+               avrcp_unregister_player(player);
+               return;
+       }
+
+       media_player_free(mp);
+}
+
+static void media_player_remove(struct media_player *mp)
+{
+       info("Player unregistered: sender=%s path=%s", mp->sender, mp->path);
+
+       media_player_destroy(mp);
+}
+
+static const char *attrval_to_str(uint8_t attr, uint8_t value)
+{
+       switch (attr) {
+       case AVRCP_ATTRIBUTE_EQUALIZER:
+               switch (value) {
+               case AVRCP_EQUALIZER_ON:
+                       return "on";
+               case AVRCP_EQUALIZER_OFF:
+                       return "off";
+               }
+
+               break;
+       case AVRCP_ATTRIBUTE_REPEAT_MODE:
+               switch (value) {
+               case AVRCP_REPEAT_MODE_OFF:
+                       return "off";
+               case AVRCP_REPEAT_MODE_SINGLE:
+                       return "singletrack";
+               case AVRCP_REPEAT_MODE_ALL:
+                       return "alltracks";
+               case AVRCP_REPEAT_MODE_GROUP:
+                       return "group";
+               }
+
+               break;
+       /* Shuffle and scan have the same values */
+       case AVRCP_ATTRIBUTE_SHUFFLE:
+       case AVRCP_ATTRIBUTE_SCAN:
+               switch (value) {
+               case AVRCP_SCAN_OFF:
+                       return "off";
+               case AVRCP_SCAN_ALL:
+                       return "alltracks";
+               case AVRCP_SCAN_GROUP:
+                       return "group";
+               }
+
+               break;
+       }
+
+       return NULL;
+}
+
+static int attrval_to_val(uint8_t attr, const char *value)
+{
+       int ret;
+
+       switch (attr) {
+       case AVRCP_ATTRIBUTE_EQUALIZER:
+               if (!strcmp(value, "off"))
+                       ret = AVRCP_EQUALIZER_OFF;
+               else if (!strcmp(value, "on"))
+                       ret = AVRCP_EQUALIZER_ON;
+               else
+                       ret = -EINVAL;
+
+               return ret;
+       case AVRCP_ATTRIBUTE_REPEAT_MODE:
+               if (!strcmp(value, "off"))
+                       ret = AVRCP_REPEAT_MODE_OFF;
+               else if (!strcmp(value, "singletrack"))
+                       ret = AVRCP_REPEAT_MODE_SINGLE;
+               else if (!strcmp(value, "alltracks"))
+                       ret = AVRCP_REPEAT_MODE_ALL;
+               else if (!strcmp(value, "group"))
+                       ret = AVRCP_REPEAT_MODE_GROUP;
+               else
+                       ret = -EINVAL;
+
+               return ret;
+       case AVRCP_ATTRIBUTE_SHUFFLE:
+               if (!strcmp(value, "off"))
+                       ret = AVRCP_SHUFFLE_OFF;
+               else if (!strcmp(value, "alltracks"))
+                       ret = AVRCP_SHUFFLE_ALL;
+               else if (!strcmp(value, "group"))
+                       ret = AVRCP_SHUFFLE_GROUP;
+               else
+                       ret = -EINVAL;
+
+               return ret;
+       case AVRCP_ATTRIBUTE_SCAN:
+               if (!strcmp(value, "off"))
+                       ret = AVRCP_SCAN_OFF;
+               else if (!strcmp(value, "alltracks"))
+                       ret = AVRCP_SCAN_ALL;
+               else if (!strcmp(value, "group"))
+                       ret = AVRCP_SCAN_GROUP;
+               else
+                       ret = -EINVAL;
+
+               return ret;
+       }
+
+       return -EINVAL;
+}
+
+static const char *attr_to_str(uint8_t attr)
+{
+       switch (attr) {
+       case AVRCP_ATTRIBUTE_EQUALIZER:
+               return "Equalizer";
+       case AVRCP_ATTRIBUTE_REPEAT_MODE:
+               return "Repeat";
+       case AVRCP_ATTRIBUTE_SHUFFLE:
+               return "Shuffle";
+       case AVRCP_ATTRIBUTE_SCAN:
+               return "Scan";
+       }
+
+       return NULL;
+}
+
+static int attr_to_val(const char *str)
+{
+       if (!strcasecmp(str, "Equalizer"))
+               return AVRCP_ATTRIBUTE_EQUALIZER;
+       else if (!strcasecmp(str, "Repeat"))
+               return AVRCP_ATTRIBUTE_REPEAT_MODE;
+       else if (!strcasecmp(str, "Shuffle"))
+               return AVRCP_ATTRIBUTE_SHUFFLE;
+       else if (!strcasecmp(str, "Scan"))
+               return AVRCP_ATTRIBUTE_SCAN;
+
+       return -EINVAL;
+}
+
+static int play_status_to_val(const char *status)
+{
+       if (!strcasecmp(status, "stopped"))
+               return AVRCP_PLAY_STATUS_STOPPED;
+       else if (!strcasecmp(status, "playing"))
+               return AVRCP_PLAY_STATUS_PLAYING;
+       else if (!strcasecmp(status, "paused"))
+               return AVRCP_PLAY_STATUS_PAUSED;
+       else if (!strcasecmp(status, "forward-seek"))
+               return AVRCP_PLAY_STATUS_FWD_SEEK;
+       else if (!strcasecmp(status, "reverse-seek"))
+               return AVRCP_PLAY_STATUS_REV_SEEK;
+       else if (!strcasecmp(status, "error"))
+               return AVRCP_PLAY_STATUS_ERROR;
+
+       return -EINVAL;
+}
+
+static int metadata_to_val(const char *str)
+{
+       if (!strcasecmp(str, "Title"))
+               return AVRCP_MEDIA_ATTRIBUTE_TITLE;
+       else if (!strcasecmp(str, "Artist"))
+               return AVRCP_MEDIA_ATTRIBUTE_ARTIST;
+       else if (!strcasecmp(str, "Album"))
+               return AVRCP_MEDIA_ATTRIBUTE_ALBUM;
+       else if (!strcasecmp(str, "Genre"))
+               return AVRCP_MEDIA_ATTRIBUTE_GENRE;
+       else if (!strcasecmp(str, "NumberOfTracks"))
+               return AVRCP_MEDIA_ATTRIBUTE_N_TRACKS;
+       else if (!strcasecmp(str, "Number"))
+               return AVRCP_MEDIA_ATTRIBUTE_TRACK;
+       else if (!strcasecmp(str, "Duration"))
+               return AVRCP_MEDIA_ATTRIBUTE_DURATION;
+
+       return -EINVAL;
+}
+
+static const char *metadata_to_str(uint32_t id)
+{
+       switch (id) {
+       case AVRCP_MEDIA_ATTRIBUTE_TITLE:
+               return "Title";
+       case AVRCP_MEDIA_ATTRIBUTE_ARTIST:
+               return "Artist";
+       case AVRCP_MEDIA_ATTRIBUTE_ALBUM:
+               return "Album";
+       case AVRCP_MEDIA_ATTRIBUTE_GENRE:
+               return "Genre";
+       case AVRCP_MEDIA_ATTRIBUTE_TRACK:
+               return "Track";
+       case AVRCP_MEDIA_ATTRIBUTE_N_TRACKS:
+               return "NumberOfTracks";
+       case AVRCP_MEDIA_ATTRIBUTE_DURATION:
+               return "Duration";
+       }
+
+       return NULL;
+}
+
+static int get_setting(uint8_t attr, void *user_data)
+{
+       struct media_player *mp = user_data;
+       guint attr_uint = attr;
+       void *value;
+
+       DBG("%s", attr_to_str(attr));
+
+       value = g_hash_table_lookup(mp->settings, GUINT_TO_POINTER(attr_uint));
+       if (!value)
+               return -EINVAL;
+
+       return GPOINTER_TO_UINT(value);
+}
+
+static int set_setting(uint8_t attr, uint8_t val, void *user_data)
+{
+       struct media_player *mp = user_data;
+       struct media_adapter *adapter = mp->adapter;
+       const char *property, *value;
+       guint attr_uint = attr;
+       DBusMessage *msg;
+       DBusMessageIter iter, var;
+
+       property = attr_to_str(attr);
+       value = attrval_to_str(attr, val);
+
+       DBG("%s = %s", property, value);
+
+       if (property == NULL || value == NULL)
+               return -EINVAL;
+
+       if (!g_hash_table_lookup(mp->settings, GUINT_TO_POINTER(attr_uint)))
+               return -EINVAL;
+
+       msg = dbus_message_new_method_call(mp->sender, mp->path,
+                                               MEDIA_PLAYER_INTERFACE,
+                                               "SetProperty");
+       if (msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return -ENOMEM;
+       }
+
+       dbus_message_iter_init_append(msg, &iter);
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &property);
+
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
+                                               DBUS_TYPE_STRING_AS_STRING,
+                                               &var);
+       dbus_message_iter_append_basic(&var, DBUS_TYPE_STRING, &value);
+       dbus_message_iter_close_container(&iter, &var);
+
+       g_dbus_send_message(adapter->conn, msg);
+
+       return 0;
+}
+
+static GList *list_metadata(void *user_data)
+{
+       struct media_player *mp = user_data;
+
+       DBG("");
+
+       if (mp->track == NULL)
+               return NULL;
+
+       return g_hash_table_get_keys(mp->track);
+}
+
+static uint64_t get_uid(void *user_data)
+{
+       struct media_player *mp = user_data;
+
+       DBG("%p", mp->track);
+
+       if (mp->track == NULL)
+               return UINT64_MAX;
+
+       return 0;
+}
+
+static void *get_metadata(uint32_t id, void *user_data)
+{
+       struct media_player *mp = user_data;
+       struct metadata_value *value;
+
+       DBG("%s", metadata_to_str(id));
+
+       if (mp->track == NULL)
+               return NULL;
+
+       value = g_hash_table_lookup(mp->track, GUINT_TO_POINTER(id));
+       if (!value)
+               return NULL;
+
+       switch (value->type) {
+       case DBUS_TYPE_STRING:
+               return value->value.str;
+       case DBUS_TYPE_UINT32:
+               return GUINT_TO_POINTER(value->value.num);
+       }
+
+       return NULL;
+}
+
+static uint8_t get_status(void *user_data)
+{
+       struct media_player *mp = user_data;
+
+       return mp->status;
+}
+
+static uint32_t get_position(void *user_data)
+{
+       struct media_player *mp = user_data;
+       double timedelta;
+       uint32_t sec, msec;
+
+       if (mp->status != AVRCP_PLAY_STATUS_PLAYING)
+               return mp->position;
+
+       timedelta = g_timer_elapsed(mp->timer, NULL);
+
+       sec = (uint32_t) timedelta;
+       msec = (uint32_t) ((timedelta - sec) * 1000);
+
+       return mp->position + sec * 1000 + msec;
+}
+
+static struct avrcp_player_cb player_cb = {
+       .get_setting = get_setting,
+       .set_setting = set_setting,
+       .list_metadata = list_metadata,
+       .get_uid = get_uid,
+       .get_metadata = get_metadata,
+       .get_position = get_position,
+       .get_status = get_status
+};
+
+static void media_player_exit(DBusConnection *connection, void *user_data)
+{
+       struct media_player *mp = user_data;
+
+       mp->watch = 0;
+       media_player_remove(mp);
+}
+
+static gboolean set_status(struct media_player *mp, DBusMessageIter *iter)
+{
+       const char *value;
+       int val;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+               return FALSE;
+
+       dbus_message_iter_get_basic(iter, &value);
+       DBG("Status=%s", value);
+
+       val = play_status_to_val(value);
+       if (val < 0) {
+               error("Invalid status");
+               return FALSE;
+       }
+
+       if (mp->status == val)
+               return TRUE;
+
+       mp->position = get_position(mp);
+       g_timer_start(mp->timer);
+
+       mp->status = val;
+
+       avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, &val);
+
+       return TRUE;
+}
+
+static gboolean set_position(struct media_player *mp, DBusMessageIter *iter)
+{
+       uint32_t value;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
+                       return FALSE;
+
+       dbus_message_iter_get_basic(iter, &value);
+       DBG("Position=%u", value);
+
+       mp->position = value;
+       g_timer_start(mp->timer);
+
+       if (!mp->position) {
+               avrcp_player_event(mp->player,
+                                       AVRCP_EVENT_TRACK_REACHED_START, NULL);
+       }
+
+       return TRUE;
+}
+
+static gboolean set_property(struct media_player *mp, const char *key,
+                                                       DBusMessageIter *entry)
+{
+       DBusMessageIter var;
+       const char *value;
+       int attr, val;
+
+       if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT)
+               return FALSE;
+
+       dbus_message_iter_recurse(entry, &var);
+
+       if (strcasecmp(key, "Status") == 0)
+               return set_status(mp, &var);
+
+       if (strcasecmp(key, "Position") == 0)
+               return set_position(mp, &var);
+
+       attr = attr_to_val(key);
+       if (attr < 0)
+               return FALSE;
+
+       if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING)
+               return FALSE;
+
+       dbus_message_iter_get_basic(&var, &value);
+
+       val = attrval_to_val(attr, value);
+       if (val < 0)
+               return FALSE;
+
+       DBG("%s=%s", key, value);
+
+       if (!mp->settings)
+               mp->settings = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+       g_hash_table_replace(mp->settings, GUINT_TO_POINTER(attr),
+                                               GUINT_TO_POINTER(val));
+
+       return TRUE;
+}
+
+static gboolean property_changed(DBusConnection *connection, DBusMessage *msg,
+                                                       void *user_data)
+{
+       struct media_player *mp = user_data;
+       DBusMessageIter iter;
+       const char *property;
+
+       DBG("sender=%s path=%s", mp->sender, mp->path);
+
+       dbus_message_iter_init(msg, &iter);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+               error("Unexpected signature in %s.%s signal",
+                                       dbus_message_get_interface(msg),
+                                       dbus_message_get_member(msg));
+               return TRUE;
+       }
+
+       dbus_message_iter_get_basic(&iter, &property);
+
+       dbus_message_iter_next(&iter);
+
+       set_property(mp, property, &iter);
+
+       return TRUE;
+}
+
+static void metadata_value_free(gpointer data)
+{
+       struct metadata_value *value = data;
+
+       switch (value->type) {
+       case DBUS_TYPE_STRING:
+               g_free(value->value.str);
+               break;
+       }
+
+       g_free(value);
+}
+
+static gboolean parse_player_metadata(struct media_player *mp,
+                                                       DBusMessageIter *iter)
+{
+       DBusMessageIter dict;
+       DBusMessageIter var;
+       GHashTable *track;
+       int ctype;
+       gboolean title = FALSE;
+       uint64_t uid;
+
+       ctype = dbus_message_iter_get_arg_type(iter);
+       if (ctype != DBUS_TYPE_ARRAY)
+               return FALSE;
+
+       dbus_message_iter_recurse(iter, &dict);
+
+       track = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+                                                       metadata_value_free);
+
+       while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
+                                                       DBUS_TYPE_INVALID) {
+               DBusMessageIter entry;
+               const char *key;
+               struct metadata_value *value;
+               int id;
+
+               if (ctype != DBUS_TYPE_DICT_ENTRY)
+                       goto parse_error;
+
+               dbus_message_iter_recurse(&dict, &entry);
+               if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+                       goto parse_error;
+
+               dbus_message_iter_get_basic(&entry, &key);
+               dbus_message_iter_next(&entry);
+
+               id = metadata_to_val(key);
+               if (id < 0)
+                       goto parse_error;
+
+               if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
+                       goto parse_error;
+
+               dbus_message_iter_recurse(&entry, &var);
+
+               value = g_new0(struct metadata_value, 1);
+               value->type = dbus_message_iter_get_arg_type(&var);
+
+               switch (id) {
+               case AVRCP_MEDIA_ATTRIBUTE_TITLE:
+                       title = TRUE;
+               case AVRCP_MEDIA_ATTRIBUTE_ARTIST:
+               case AVRCP_MEDIA_ATTRIBUTE_ALBUM:
+               case AVRCP_MEDIA_ATTRIBUTE_GENRE:
+                       if (value->type != DBUS_TYPE_STRING) {
+                               g_free(value);
+                               goto parse_error;
+                       }
+
+                       dbus_message_iter_get_basic(&var, &value->value.str);
+                       break;
+               case AVRCP_MEDIA_ATTRIBUTE_TRACK:
+               case AVRCP_MEDIA_ATTRIBUTE_N_TRACKS:
+               case AVRCP_MEDIA_ATTRIBUTE_DURATION:
+                       if (value->type != DBUS_TYPE_UINT32) {
+                               g_free(value);
+                               goto parse_error;
+                       }
+
+                       dbus_message_iter_get_basic(&var, &value->value.num);
+                       break;
+               default:
+                       goto parse_error;
+               }
+
+               switch (value->type) {
+               case DBUS_TYPE_STRING:
+                       value->value.str = g_strdup(value->value.str);
+                       DBG("%s=%s", key, value->value.str);
+                       break;
+               default:
+                       DBG("%s=%u", key, value->value.num);
+               }
+
+               g_hash_table_replace(track, GUINT_TO_POINTER(id), value);
+               dbus_message_iter_next(&dict);
+       }
+
+       if (g_hash_table_size(track) == 0) {
+               g_hash_table_unref(track);
+               track = NULL;
+       } else if (title == FALSE) {
+               struct metadata_value *value = g_new(struct metadata_value, 1);
+               uint32_t id = AVRCP_MEDIA_ATTRIBUTE_TITLE;
+
+               value->type = DBUS_TYPE_STRING;
+               value->value.str = g_strdup("");
+               g_hash_table_insert(track, GUINT_TO_POINTER(id), value);
+       }
+
+       if (mp->track != NULL)
+               g_hash_table_unref(mp->track);
+
+       mp->track = track;
+       mp->position = 0;
+       g_timer_start(mp->timer);
+       uid = get_uid(mp);
+
+       avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_CHANGED, &uid);
+       avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_START,
+                                                               NULL);
+
+       return TRUE;
+
+parse_error:
+       if (track)
+               g_hash_table_unref(track);
+
+       return FALSE;
+}
+
+static gboolean track_changed(DBusConnection *connection, DBusMessage *msg,
+                                                       void *user_data)
+{
+       struct media_player *mp = user_data;
+       DBusMessageIter iter;
+
+       DBG("sender=%s path=%s", mp->sender, mp->path);
+
+       dbus_message_iter_init(msg, &iter);
+
+       if (parse_player_metadata(mp, &iter) == FALSE) {
+               error("Unexpected signature in %s.%s signal",
+                                       dbus_message_get_interface(msg),
+                                       dbus_message_get_member(msg));
+       }
+
+       return TRUE;
+}
+
+static struct media_player *media_player_create(struct media_adapter *adapter,
+                                               const char *sender,
+                                               const char *path,
+                                               int *err)
+{
+       struct media_player *mp;
+
+       mp = g_new0(struct media_player, 1);
+       mp->adapter = adapter;
+       mp->sender = g_strdup(sender);
+       mp->path = g_strdup(path);
+       mp->timer = g_timer_new();
+
+       mp->watch = g_dbus_add_disconnect_watch(adapter->conn, sender,
+                                               media_player_exit, mp,
+                                               NULL);
+       mp->property_watch = g_dbus_add_signal_watch(adapter->conn, sender,
+                                               path, MEDIA_PLAYER_INTERFACE,
+                                               "PropertyChanged",
+                                               property_changed,
+                                               mp, NULL);
+       mp->track_watch = g_dbus_add_signal_watch(adapter->conn, sender,
+                                               path, MEDIA_PLAYER_INTERFACE,
+                                               "TrackChanged",
+                                               track_changed,
+                                               mp, NULL);
+       mp->player = avrcp_register_player(&adapter->src, &player_cb, mp,
+                                               media_player_free);
+       if (!mp->player) {
+               if (err)
+                       *err = -EPROTONOSUPPORT;
+               media_player_destroy(mp);
+               return NULL;
+       }
+
+       adapter->players = g_slist_append(adapter->players, mp);
+
+       info("Player registered: sender=%s path=%s", sender, path);
+
+       if (err)
+               *err = 0;
+
+       return mp;
+}
+
+static gboolean parse_player_properties(struct media_player *mp,
+                                                       DBusMessageIter *iter)
+{
+       DBusMessageIter dict;
+       int ctype;
+
+       ctype = dbus_message_iter_get_arg_type(iter);
+       if (ctype != DBUS_TYPE_ARRAY)
+               return FALSE;
+
+       dbus_message_iter_recurse(iter, &dict);
+
+       while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
+                                                       DBUS_TYPE_INVALID) {
+               DBusMessageIter entry;
+               const char *key;
+
+               if (ctype != DBUS_TYPE_DICT_ENTRY)
+                       return FALSE;
+
+               dbus_message_iter_recurse(&dict, &entry);
+               if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+                       return FALSE;
+
+               dbus_message_iter_get_basic(&entry, &key);
+               dbus_message_iter_next(&entry);
+
+               if (set_property(mp, key, &entry) == FALSE)
+                       return FALSE;
+
+               dbus_message_iter_next(&dict);
+       }
+
+       return TRUE;
+}
+
+static DBusMessage *register_player(DBusConnection *conn, DBusMessage *msg,
+                                       void *data)
+{
+       struct media_adapter *adapter = data;
+       struct media_player *mp;
+       DBusMessageIter args;
+       const char *sender, *path;
+       int err;
+
+       sender = dbus_message_get_sender(msg);
+
+       dbus_message_iter_init(msg, &args);
+
+       dbus_message_iter_get_basic(&args, &path);
+       dbus_message_iter_next(&args);
+
+       if (media_adapter_find_player(adapter, sender, path) != NULL)
+               return btd_error_already_exists(msg);
+
+       mp = media_player_create(adapter, sender, path, &err);
+       if (mp == NULL) {
+               if (err == -EPROTONOSUPPORT)
+                       return btd_error_not_supported(msg);
+               else
+                       return btd_error_invalid_args(msg);
+       }
+
+       if (parse_player_properties(mp, &args) == FALSE) {
+               media_player_destroy(mp);
+               return btd_error_invalid_args(msg);
+       }
+
+       dbus_message_iter_next(&args);
+
+       if (parse_player_metadata(mp, &args) == FALSE) {
+               media_player_destroy(mp);
+               return btd_error_invalid_args(msg);
+       }
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *unregister_player(DBusConnection *conn, DBusMessage *msg,
+                                       void *data)
+{
+       struct media_adapter *adapter = data;
+       struct media_player *player;
+       const char *sender, *path;
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID))
+               return NULL;
+
+       sender = dbus_message_get_sender(msg);
+
+       player = media_adapter_find_player(adapter, sender, path);
+       if (player == NULL)
+               return btd_error_does_not_exist(msg);
+
+       media_player_remove(player);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static GDBusMethodTable media_methods[] = {
+       { "RegisterEndpoint",   "oa{sv}",       "",     register_endpoint },
+       { "UnregisterEndpoint", "o",            "",     unregister_endpoint },
+       { "RegisterPlayer",     "oa{sv}a{sv}","",       register_player },
+       { "UnregisterPlayer",   "o",            "",     unregister_player },
+       { },
+};
+
+static void path_free(void *data)
+{
+       struct media_adapter *adapter = data;
+
+       while (adapter->endpoints)
+               release_endpoint(adapter->endpoints->data);
+
+       dbus_connection_unref(adapter->conn);
+
+       adapters = g_slist_remove(adapters, adapter);
+
+       g_free(adapter->path);
+       g_free(adapter);
+}
+
+int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src)
+{
+       struct media_adapter *adapter;
+
+       if (DBUS_TYPE_UNIX_FD < 0)
+               return -EPERM;
+
+       adapter = g_new0(struct media_adapter, 1);
+       adapter->conn = dbus_connection_ref(conn);
+       bacpy(&adapter->src, src);
+       adapter->path = g_strdup(path);
+
+       if (!g_dbus_register_interface(conn, path, MEDIA_INTERFACE,
+                                       media_methods, NULL, NULL,
+                                       adapter, path_free)) {
+               error("D-Bus failed to register %s path", path);
+               path_free(adapter);
+               return -1;
+       }
+
+       adapters = g_slist_append(adapters, adapter);
+
+       return 0;
+}
+
+void media_unregister(const char *path)
+{
+       GSList *l;
+
+       for (l = adapters; l; l = l->next) {
+               struct media_adapter *adapter = l->data;
+
+               if (g_strcmp0(path, adapter->path) == 0) {
+                       g_dbus_unregister_interface(adapter->conn, path,
+                                                       MEDIA_INTERFACE);
+                       return;
+               }
+       }
+}
+
+struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint)
+{
+       return endpoint->sep;
+}
+
+const char *media_endpoint_get_uuid(struct media_endpoint *endpoint)
+{
+       return endpoint->uuid;
+}
+
+uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint)
+{
+       return endpoint->codec;
+}
diff --git a/audio/media.h b/audio/media.h
new file mode 100644 (file)
index 0000000..ee9a51e
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 media_endpoint;
+
+typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint,
+                                       void *ret, int size, void *user_data);
+
+int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src);
+void media_unregister(const char *path);
+
+struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint);
+const char *media_endpoint_get_uuid(struct media_endpoint *endpoint);
+uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint);
+struct media_transport *media_endpoint_get_transport(
+                                       struct media_endpoint *endpoint);
diff --git a/audio/module-bluetooth-sink.c b/audio/module-bluetooth-sink.c
new file mode 100644 (file)
index 0000000..a0117c0
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if 0
+#include <pulsecore/module.h>
+
+PA_MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>")
+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 (file)
index 0000000..e633d1a
--- /dev/null
@@ -0,0 +1,1784 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <time.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <signal.h>
+#include <limits.h>
+
+#include <netinet/in.h>
+
+#include <alsa/asoundlib.h>
+#include <alsa/pcm_external.h>
+
+#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) {
+                       if (errno != EINTR) {
+                               SNDERR("poll error: %s (%d)", strerror(errno),
+                                                               errno);
+                               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];
+
+       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)
+               if (read(pfds[0].fd, buf, 1) < 0)
+                       SYSERR("read error: %s (%d)", strerror(errno), errno);
+
+       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;
+       ssize_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) {
+               unsigned int additional_bytes_needed =
+                                               a2dp->codesize - data->count;
+               if (additional_bytes_needed > bytes_left)
+                       goto out;
+
+               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);
+               }
+       }
+
+out:
+       /* 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 (file)
index 0000000..45fddcf
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 (file)
index 0000000..2d5db18
--- /dev/null
@@ -0,0 +1,743 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+
+#include "device.h"
+#include "avdtp.h"
+#include "media.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 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 char *str_state[] = {
+       "SINK_STATE_DISCONNECTED",
+       "SINK_STATE_CONNECTING",
+       "SINK_STATE_CONNECTED",
+       "SINK_STATE_PLAYING",
+};
+
+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);
+
+       DBG("State changed %s: %s -> %s", dev->path, str_state[old_state],
+               str_state[new_state]);
+
+       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);
+               }
+               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 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->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);
+               } 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 void error_failed(DBusConnection *conn, DBusMessage *msg,
+                                                       const char *desc)
+{
+       DBusMessage *reply = btd_error_failed(msg, desc);
+       g_dbus_send_message(conn, reply);
+}
+
+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) {
+               DBG("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 {
+               DBG("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) {
+               DBG("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_category(err) == AVDTP_ERRNO
+                       && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+               DBG("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);
+               DBG("Stream setup failed : %s", avdtp_strerror(err));
+       }
+}
+
+static void select_complete(struct avdtp *session, struct a2dp_sep *sep,
+                       GSList *caps, void *user_data)
+{
+       struct sink *sink = user_data;
+       struct pending_request *pending;
+       int id;
+
+       pending = sink->connect;
+       pending->id = 0;
+
+       id = a2dp_config(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;
+}
+
+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;
+       int id;
+
+       if (!sink->connect) {
+               avdtp_unref(sink->session);
+               sink->session = NULL;
+               return;
+       }
+
+       pending = sink->connect;
+
+       if (err) {
+               avdtp_unref(sink->session);
+               sink->session = NULL;
+               if (avdtp_error_category(err) == AVDTP_ERRNO
+                               && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+                       DBG("connect:connect XCASE detected");
+                       sink->retry_id =
+                               g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+                                                       stream_setup_retry,
+                                                       sink);
+               } else
+                       goto failed;
+               return;
+       }
+
+       DBG("Discovery complete");
+
+       id = a2dp_select_capabilities(sink->session, AVDTP_SEP_TYPE_SINK, NULL,
+                                               select_complete, 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 btd_error_failed(msg, "Unable to get a session");
+
+       if (sink->connect || sink->disconnect)
+               return btd_error_busy(msg);
+
+       if (sink->stream_state >= AVDTP_STATE_OPEN)
+               return btd_error_already_connected(msg);
+
+       if (!sink_setup_stream(sink, NULL))
+               return btd_error_failed(msg, "Failed to create a stream");
+
+       dev->auto_connect = FALSE;
+
+       pending = sink->connect;
+
+       pending->conn = dbus_connection_ref(conn);
+       pending->msg = dbus_message_ref(msg);
+
+       DBG("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 btd_error_not_connected(msg);
+
+       if (sink->connect || sink->disconnect)
+               return btd_error_busy(msg);
+
+       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, FALSE);
+       if (err < 0)
+               return btd_error_failed(msg, 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_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 },
+       { "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->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;
+
+       DBG("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;
+
+       DBG("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->session)
+               return FALSE;
+
+       avdtp_set_device_disconnect(sink->session, TRUE);
+
+       /* cancel pending connect */
+       if (sink->connect) {
+               struct pending_request *pending = sink->connect;
+
+               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;
+
+               return TRUE;
+       }
+
+       /* disconnect already ongoing */
+       if (sink->disconnect)
+               return TRUE;
+
+       if (!sink->stream)
+               return FALSE;
+
+       if (avdtp_close(sink->session, sink->stream, FALSE) < 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 (file)
index 0000000..7b1902b
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 (file)
index 0000000..6d266f2
--- /dev/null
@@ -0,0 +1,633 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  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 <config.h>
+#endif
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+
+#include "device.h"
+#include "avdtp.h"
+#include "media.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 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:
+               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 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->session) {
+                       avdtp_unref(source->session);
+                       source->session = NULL;
+               }
+               source->stream = NULL;
+               source->cb_id = 0;
+               break;
+       case AVDTP_STATE_OPEN:
+               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 void error_failed(DBusConnection *conn, DBusMessage *msg,
+                                                       const char *desc)
+{
+       DBusMessage *reply = btd_error_failed(msg, desc);
+       g_dbus_send_message(conn, reply);
+}
+
+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) {
+               DBG("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 {
+               DBG("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) {
+               DBG("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_category(err) == AVDTP_ERRNO
+                       && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+               DBG("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);
+               DBG("Stream setup failed : %s", avdtp_strerror(err));
+       }
+}
+
+static void select_complete(struct avdtp *session, struct a2dp_sep *sep,
+                       GSList *caps, void *user_data)
+{
+       struct source *source = user_data;
+       struct pending_request *pending;
+       int id;
+
+       pending = source->connect;
+
+       pending->id = 0;
+
+       if (caps == NULL)
+               goto failed;
+
+       id = a2dp_config(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;
+}
+
+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;
+       int id;
+
+       pending = source->connect;
+
+       if (err) {
+               avdtp_unref(source->session);
+               source->session = NULL;
+               if (avdtp_error_category(err) == AVDTP_ERRNO
+                               && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+                       DBG("connect:connect XCASE detected");
+                       source->retry_id =
+                               g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+                                                       stream_setup_retry,
+                                                       source);
+               } else
+                       goto failed;
+               return;
+       }
+
+       DBG("Discovery complete");
+
+       id = a2dp_select_capabilities(source->session, AVDTP_SEP_TYPE_SOURCE, NULL,
+                                               select_complete, 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 btd_error_failed(msg, "Unable to get a session");
+
+       if (source->connect || source->disconnect)
+               return btd_error_busy(msg);
+
+       if (source->stream_state >= AVDTP_STATE_OPEN)
+               return btd_error_already_connected(msg);
+
+       if (!source_setup_stream(source, NULL))
+               return btd_error_failed(msg, "Failed to create a stream");
+
+       dev->auto_connect = FALSE;
+
+       pending = source->connect;
+
+       pending->conn = dbus_connection_ref(conn);
+       pending->msg = dbus_message_ref(msg);
+
+       DBG("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 btd_error_not_connected(msg);
+
+       if (source->connect || source->disconnect)
+               return btd_error_busy(msg);
+
+       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, FALSE);
+       if (err < 0)
+               return btd_error_failed(msg, 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->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;
+
+       DBG("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;
+
+       DBG("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, FALSE) < 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 (file)
index 0000000..7837284
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  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 (file)
index 0000000..1f89079
--- /dev/null
@@ -0,0 +1,433 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "telephony.h"
+#include "error.h"
+
+#define TELEPHONY_DUMMY_IFACE "org.bluez.TelephonyTest"
+#define TELEPHONY_DUMMY_PATH "/org/bluez/test"
+
+static DBusConnection *connection = NULL;
+
+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;
+
+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 }
+};
+
+void telephony_device_connected(void *telephony_device)
+{
+       DBG("telephony-dummy: device %p connected", telephony_device);
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+       DBG("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)
+{
+       telephony_response_and_hold_rsp(telephony_device,
+                                               CME_ERROR_NOT_SUPPORTED);
+}
+
+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);
+
+       DBG("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)
+{
+       DBG("telephony-dummy: transmit dtmf: %c", tone);
+       telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+       DBG("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)
+{
+       DBG("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)
+{
+       DBG("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)
+{
+       DBG("telephony-dummy: got %s NR and EC request",
+                       enable ? "enable" : "disable");
+
+       telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+       DBG("telephony-dummy: got %s voice dial request",
+                       enable ? "enable" : "disable");
+
+       g_dbus_emit_signal(connection, TELEPHONY_DUMMY_PATH,
+                       TELEPHONY_DUMMY_IFACE, "VoiceDial",
+                       DBUS_TYPE_INVALID);
+
+       telephony_voice_dial_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+       DBG("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 btd_error_invalid_args(msg);
+
+       DBG("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 btd_error_invalid_args(msg);
+
+       DBG("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)
+{
+       DBG("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 btd_error_invalid_args(msg);
+
+       if (strength > 5)
+               return btd_error_invalid_args(msg);
+
+       telephony_update_indicator(dummy_indicators, "signal", strength);
+
+       DBG("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 btd_error_invalid_args(msg);
+
+       if (level > 5)
+               return btd_error_invalid_args(msg);
+
+       telephony_update_indicator(dummy_indicators, "battchg", level);
+
+       DBG("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 btd_error_invalid_args(msg);
+
+       val = roaming ? EV_ROAM_ACTIVE : EV_ROAM_INACTIVE;
+
+       telephony_update_indicator(dummy_indicators, "roam", val);
+
+       DBG("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, &registration,
+                                               DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       val = registration ? EV_SERVICE_PRESENT : EV_SERVICE_NONE;
+
+       telephony_update_indicator(dummy_indicators, "service", val);
+
+       DBG("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 btd_error_invalid_args(msg);
+
+       g_free(subscriber_number);
+       subscriber_number = g_strdup(number);
+
+       DBG("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 GDBusSignalTable dummy_signals[] = {
+       { "VoiceDial",  "" },
+       { }
+};
+
+int telephony_init(void)
+{
+       uint32_t features = AG_FEATURE_REJECT_A_CALL |
+                               AG_FEATURE_ENHANCED_CALL_STATUS |
+                               AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;
+
+       DBG("");
+
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+       if (g_dbus_register_interface(connection, TELEPHONY_DUMMY_PATH,
+                                       TELEPHONY_DUMMY_IFACE,
+                                       dummy_methods, dummy_signals,
+                                       NULL, NULL, NULL) == FALSE) {
+               error("telephony-dummy interface %s init failed on path %s",
+                       TELEPHONY_DUMMY_IFACE, TELEPHONY_DUMMY_PATH);
+               return -1;
+       }
+
+       telephony_ready_ind(features, dummy_indicators, BTRH_NOT_SUPPORTED,
+                                                               chld_str);
+
+       return 0;
+}
+
+void telephony_exit(void)
+{
+       DBG("");
+
+       g_dbus_unregister_interface(connection, TELEPHONY_DUMMY_PATH,
+                                               TELEPHONY_DUMMY_IFACE);
+       dbus_connection_unref(connection);
+       connection = NULL;
+
+       telephony_deinit();
+}
diff --git a/audio/telephony-maemo5.c b/audio/telephony-maemo5.c
new file mode 100644 (file)
index 0000000..2832062
--- /dev/null
@@ -0,0 +1,2104 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "telephony.h"
+#include "error.h"
+
+/* SSC D-Bus definitions */
+#define SSC_DBUS_NAME  "com.nokia.phone.SSC"
+#define SSC_DBUS_IFACE "com.nokia.phone.SSC"
+#define SSC_DBUS_PATH  "/com/nokia/phone/SSC"
+
+/* 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"
+#define CSD_CALL_CONFERENCE_PATH "/com/nokia/csd/call/conference"
+
+/* 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 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 get_calls_active = FALSE;
+
+static gboolean events_enabled = FALSE;
+
+/* Supported set of call hold operations */
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+
+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",  0,      TRUE },
+       { "service",    "0,1",  0,      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_conference(void)
+{
+       DBusMessage *msg;
+
+       DBG("telephony-maemo: releasing conference call");
+
+       msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+                                               CSD_CALL_CONFERENCE_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 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;
+
+       DBG("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)
+{
+       DBG("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)
+{
+       telephony_response_and_hold_rsp(telephony_device,
+                                               CME_ERROR_NOT_SUPPORTED);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+       DBG("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;
+       int err;
+
+       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 (call->conference)
+               err = release_conference();
+       else
+               err = release_call(call);
+
+       if (err < 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;
+
+       DBG("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;
+
+       DBG("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)
+{
+       DBG("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;
+
+       DBG("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;
+
+       DBG("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:
+               DBG("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)
+{
+       DBG("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;
+
+       DBG("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);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+       DBG("telephony-maemo: got %s voice dial request",
+                       enable ? "enable" : "disable");
+
+       telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
+}
+
+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;
+       }
+
+       DBG("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;
+       }
+
+       DBG("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)
+{
+       DBG("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;
+       }
+
+       DBG("Call %s changed from %s to %s", call_path,
+               call_status_str[call->status], call_status_str[status]);
+
+       if (call->status == (int) status) {
+               DBG("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;
+       }
+
+       DBG("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);
+
+       DBG("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) {
+               DBG("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;
+
+       DBG("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_error_init(&err);
+       if (dbus_message_get_args(reply, &err,
+                               DBUS_TYPE_INT32, &level,
+                               DBUS_TYPE_INVALID) == FALSE) {
+               error("Unable to parse GetPropertyInteger reply: %s, %s",
+                                                       err.name, err.message);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       *value = (int) level;
+
+       if (value == &battchg_last)
+               DBG("telephony-maemo: battery.charge_level.last_full is %d",
+                               *value);
+       else if (value == &battchg_design)
+               DBG("telephony-maemo: battery.charge_level.design is %d",
+                               *value);
+       else
+               DBG("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 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);
+                       DBG("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, &err,
+                                       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);
+               goto done;
+       }
+
+       if (net_err != 0) {
+               error("get_signal_strength failed with code %d", net_err);
+               goto done;
+       }
+
+       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;
+
+       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, &err,
+                                       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);
+               goto done;
+       }
+
+       if (net_err != 0) {
+               error("get_registration_status failed with code %d", net_err);
+               goto done;
+       }
+
+       update_registration_status(status, lac, cell_id, operator_code,
+                                       country_code, network_type,
+                                       supported_services);
+
+       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;
+
+       get_calls_active = FALSE;
+
+       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 FindDeviceByCapability 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);
+
+       DBG("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);
+       if (dbus_message_get_args(reply, &derr,
+                               DBUS_TYPE_STRING, &name,
+                               DBUS_TYPE_STRING, &number,
+                               DBUS_TYPE_INT32, &current_location,
+                               DBUS_TYPE_INT32, &err,
+                               DBUS_TYPE_INVALID) == FALSE) {
+               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);
+               DBG("Got MSISDN %s (%s)", number, name);
+       } else {
+               g_free(vmbx);
+               vmbx = g_strdup(number);
+               DBG("Got voice mailbox number %s (%s)", number, name);
+       }
+
+done:
+       dbus_message_unref(reply);
+}
+
+static void csd_init(void)
+{
+       dbus_uint32_t location;
+       uint8_t pb_type, location_type;
+       int ret;
+
+       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;
+       }
+
+       get_calls_active = TRUE;
+
+       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;
+       }
+
+       pb_type = SIM_PHONEBOOK_TYPE_MBDN;
+       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;
+       }
+}
+
+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 btd_error_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);
+               DBG("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 btd_error_invalid_args(msg);
+}
+
+static GDBusMethodTable telephony_maemo_methods[] = {
+       {"SetCallerId",         "s",    "",     set_callerid,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { }
+};
+
+static void handle_modem_state(DBusMessage *msg)
+{
+       const char *state;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &state,
+                                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected modem state parameters");
+               return;
+       }
+
+       DBG("SSC modem state: %s", state);
+
+       if (calls != NULL || get_calls_active)
+               return;
+
+       if (g_str_equal(state, "cmt_ready") || g_str_equal(state, "online"))
+               csd_init();
+}
+
+static void modem_state_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusMessage *reply = dbus_pending_call_steal_reply(call);
+       DBusError err;
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, reply)) {
+               error("get_modem_status: %s, %s", err.name, err.message);
+               dbus_error_free(&err);
+       } else
+               handle_modem_state(reply);
+
+       dbus_message_unref(reply);
+}
+
+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);
+       else if (dbus_message_is_signal(msg, SSC_DBUS_IFACE,
+                                               "modem_state_changed_ind"))
+               handle_modem_state(msg);
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int telephony_init(void)
+{
+       const char *battery_cap = "battery";
+       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;
+
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+       if (!dbus_connection_add_filter(connection, signal_filter,
+                                               NULL, NULL))
+               error("Can't add signal filter");
+
+       dbus_bus_add_match(connection,
+                       "type=signal,interface=" CSD_CALL_INTERFACE, NULL);
+       dbus_bus_add_match(connection,
+                       "type=signal,interface=" CSD_CALL_INSTANCE, NULL);
+       dbus_bus_add_match(connection,
+                       "type=signal,interface=" CSD_CALL_CONFERENCE, NULL);
+       dbus_bus_add_match(connection,
+                       "type=signal,interface=" NETWORK_INTERFACE, NULL);
+       dbus_bus_add_match(connection,
+                               "type=signal,interface=" SSC_DBUS_IFACE
+                               ",member=modem_state_changed_ind", NULL);
+
+       if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE,
+                                       "get_modem_state", modem_state_reply,
+                                       NULL, DBUS_TYPE_INVALID) < 0)
+               error("Unable to send " SSC_DBUS_IFACE ".get_modem_state()");
+
+       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);
+       }
+
+       DBG("telephony-maemo registering %s interface on path %s",
+                       TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
+
+       telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED,
+                                                               chld_str);
+       if (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) < 0)
+               error("Unable to send HAL method call");
+
+       return 0;
+}
+
+void telephony_exit(void)
+{
+       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;
+
+       telephony_deinit();
+}
diff --git a/audio/telephony-maemo6.c b/audio/telephony-maemo6.c
new file mode 100644 (file)
index 0000000..7b716aa
--- /dev/null
@@ -0,0 +1,2201 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/sdp.h>
+
+#include "glib-helper.h"
+#include "log.h"
+#include "telephony.h"
+#include "error.h"
+
+/* SSC D-Bus definitions */
+#define SSC_DBUS_NAME  "com.nokia.phone.SSC"
+#define SSC_DBUS_IFACE "com.nokia.phone.SSC"
+#define SSC_DBUS_PATH  "/com/nokia/phone/SSC"
+
+/* libcsnet D-Bus definitions */
+#define CSD_CSNET_BUS_NAME     "com.nokia.csd.CSNet"
+#define CSD_CSNET_PATH         "/com/nokia/csd/csnet"
+#define CSD_CSNET_IFACE                "com.nokia.csd.CSNet"
+#define CSD_CSNET_REGISTRATION "com.nokia.csd.CSNet.NetworkRegistration"
+#define CSD_CSNET_OPERATOR     "com.nokia.csd.CSNet.NetworkOperator"
+#define CSD_CSNET_SIGNAL       "com.nokia.csd.CSNet.SignalStrength"
+
+enum net_registration_status {
+       NETWORK_REG_STATUS_HOME,
+       NETWORK_REG_STATUS_ROAMING,
+       NETWORK_REG_STATUS_OFFLINE,
+       NETWORK_REG_STATUS_SEARCHING,
+       NETWORK_REG_STATUS_NO_SIM,
+       NETWORK_REG_STATUS_POWEROFF,
+       NETWORK_REG_STATUS_POWERSAFE,
+       NETWORK_REG_STATUS_NO_COVERAGE,
+       NETWORK_REG_STATUS_REJECTED,
+       NETWORK_REG_STATUS_UNKOWN
+};
+
+/* 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"
+#define CSD_CALL_CONFERENCE_PATH "/com/nokia/csd/call/conference"
+
+/* 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 CSD_SIMPB_BUS_NAME                     "com.nokia.csd.SIM"
+#define CSD_SIMPB_INTERFACE                    "com.nokia.csd.SIM.Phonebook"
+#define CSD_SIMPB_PATH                         "/com/nokia/csd/sim/phonebook"
+
+#define CSD_SIMPB_TYPE_ADN                     "ADN"
+#define CSD_SIMPB_TYPE_FDN                     "FDN"
+#define CSD_SIMPB_TYPE_SDN                     "SDN"
+#define CSD_SIMPB_TYPE_VMBX                    "VMBX"
+#define CSD_SIMPB_TYPE_MBDN                    "MBDN"
+#define CSD_SIMPB_TYPE_EN                      "EN"
+#define CSD_SIMPB_TYPE_MSISDN                  "MSISDN"
+
+/* OHM plugin D-Bus definitions */
+#define OHM_BUS_NAME           "com.nokia.NonGraphicFeedback1"
+#define OHM_INTERFACE          "com.nokia.NonGraphicFeedback1"
+#define OHM_PATH               "/com/nokia/NonGraphicFeedback1"
+
+/* tone-genenerator D-Bus definitions */
+#define TONEGEN_BUS_NAME       "com.Nokia.Telephony.Tones"
+#define TONEGEN_INTERFACE      "com.Nokia.Telephony.Tones"
+#define TONEGEN_PATH           "/com/Nokia/Telephony/Tones"
+
+/* tone-generator DTMF definitions */
+#define DTMF_ASTERISK   10
+#define DTMF_HASHMARK   11
+#define DTMF_A          12
+#define DTMF_B          13
+#define DTMF_C          14
+#define DTMF_D          15
+
+#define FEEDBACK_TONE_DURATION                 200
+
+struct csd_call {
+       char *object_path;
+       int status;
+       gboolean originating;
+       gboolean emergency;
+       gboolean on_hold;
+       gboolean conference;
+       char *number;
+       gboolean setup;
+};
+
+static struct {
+       char *operator_name;
+       uint8_t status;
+       int32_t signal_bars;
+} net = {
+       .operator_name = NULL,
+       .status = NETWORK_REG_STATUS_UNKOWN,
+       /* Init as 0 meaning inactive mode. In modem power off state
+        * can be be -1, but we treat all values as 0s regardless
+        * inactive or power off. */
+       .signal_bars = 0,
+};
+
+struct pending_req {
+       DBusPendingCall *call;
+       void *user_data;
+};
+
+static int get_property(const char *iface, const char *prop);
+
+static DBusConnection *connection = NULL;
+
+static GSList *calls = NULL;
+static GSList *watches = NULL;
+static GSList *pending = NULL;
+
+/* Reference count for determining the call indicator status */
+static GSList *active_calls = NULL;
+
+/* Queue of DTMF tones to play */
+static GSList *tones = NULL;
+static guint create_tones_timer = 0;
+
+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 get_calls_active = FALSE;
+
+static gboolean events_enabled = FALSE;
+
+/* Supported set of call hold operations */
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+
+/* Timer for tracking call creation requests */
+static guint create_request_timer = 0;
+
+static struct indicator maemo_indicators[] =
+{
+       { "battchg",    "0-5",  5,      TRUE },
+       /* signal strength in terms of bars */
+       { "signal",     "0-5",  0,      TRUE },
+       { "service",    "0,1",  0,      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 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;
+       struct pending_req *req;
+
+       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);
+
+       req = g_new0(struct pending_req, 1);
+       req->call = call;
+       req->user_data = user_data;
+
+       pending = g_slist_prepend(pending, req);
+       dbus_message_unref(msg);
+
+       return 0;
+}
+
+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_conference(void)
+{
+       DBusMessage *msg;
+
+       DBG("telephony-maemo6: releasing conference call");
+
+       msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+                                               CSD_CALL_CONFERENCE_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 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 struct pending_req *find_request(const DBusPendingCall *call)
+{
+       GSList *l;
+
+       for (l = pending; l; l = l->next) {
+               struct pending_req *req = l->data;
+
+               if (req->call == call)
+                       return req;
+       }
+
+       return NULL;
+}
+
+static void pending_req_finalize(void *data)
+{
+       struct pending_req *req = data;
+
+       if (!dbus_pending_call_get_completed(req->call))
+               dbus_pending_call_cancel(req->call);
+
+       dbus_pending_call_unref(req->call);
+       g_free(req);
+}
+
+static void remove_pending(DBusPendingCall *call)
+{
+       struct pending_req *req = find_request(call);
+
+       pending = g_slist_remove(pending, req);
+       pending_req_finalize(req);
+}
+
+static void stop_ringtone_reply(DBusPendingCall *call, void *user_data)
+{
+       struct csd_call *coming = user_data;
+
+       remove_pending(call);
+       answer_call(coming);
+}
+
+static int stop_ringtone_and_answer(struct csd_call *call)
+{
+       int ret;
+
+       ret = send_method_call(OHM_BUS_NAME, OHM_PATH,
+                               OHM_INTERFACE, "StopRingtone",
+                               stop_ringtone_reply, call,
+                               DBUS_TYPE_INVALID);
+       if (ret < 0)
+               return answer_call(call);
+
+       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;
+
+       DBG("telephony-maemo6: 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));
+       }
+}
+
+static void remove_pending_by_data(gpointer data, gpointer user_data)
+{
+       struct pending_req *req = data;
+
+       if (req->user_data == user_data) {
+               pending = g_slist_remove(pending, req);
+               pending_req_finalize(req);
+       }
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+       DBG("telephony-maemo6: device %p disconnected", telephony_device);
+       events_enabled = FALSE;
+
+       g_slist_foreach(pending, remove_pending_by_data, telephony_device);
+}
+
+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)
+{
+       telephony_response_and_hold_rsp(telephony_device,
+                                               CME_ERROR_NOT_SUPPORTED);
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+       struct csd_call *call;
+       struct csd_call *alerting;
+       int err;
+
+       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;
+       }
+
+       alerting = find_call_with_status(CSD_CALL_STATUS_MO_ALERTING);
+       if (call->on_hold && alerting)
+               err = release_call(alerting);
+       else if (call->conference)
+               err = release_conference();
+       else
+               err = release_call(call);
+
+       if (err < 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 (stop_ringtone_and_answer(call) < 0)
+               telephony_answer_call_rsp(telephony_device,
+                                               CME_ERROR_AG_FAILURE);
+       else
+               telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static void create_call_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusError err;
+       DBusMessage *reply;
+       void *telephony_device = user_data;
+
+       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);
+               if (g_strcmp0(err.name,
+                               "com.nokia.csd.Call.Error.CSInactive") == 0)
+                       telephony_dial_number_rsp(telephony_device,
+                                               CME_ERROR_NO_NETWORK_SERVICE);
+               else
+                       telephony_dial_number_rsp(telephony_device,
+                                                       CME_ERROR_AG_FAILURE);
+               dbus_error_free(&err);
+       } else
+               telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+       int ret;
+
+       DBG("telephony-maemo6: last dialed number request");
+
+       ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+                               CSD_CALL_INTERFACE, "CreateFromLast",
+                               create_call_reply, telephony_device,
+                               DBUS_TYPE_INVALID);
+       if (ret < 0)
+               telephony_dial_number_rsp(telephony_device,
+                                               CME_ERROR_AG_FAILURE);
+}
+
+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)
+{
+       int ret;
+
+       DBG("telephony-maemo6: dial request to %s", number);
+
+       if (strncmp(number, "*31#", 4) == 0)
+               number += 4;
+       else if (strncmp(number, "#31#", 4) == 0)
+               number += 4;
+       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, "Create",
+                               create_call_reply, telephony_device,
+                               DBUS_TYPE_STRING, &number,
+                               DBUS_TYPE_INVALID);
+       if (ret < 0)
+               telephony_dial_number_rsp(telephony_device,
+                                               CME_ERROR_AG_FAILURE);
+}
+
+static void start_dtmf_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusError err;
+       DBusMessage *reply;
+
+       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);
+       } else
+               send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+                               CSD_CALL_INTERFACE, "StopDTMF",
+                               NULL, NULL,
+                               DBUS_TYPE_INVALID);
+
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+static void start_dtmf(void *telephony_device, char tone)
+{
+       int ret;
+
+       /*
+        * Stop tone immediately, modem will place it in queue and play
+        * required time.
+        */
+       ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+                               CSD_CALL_INTERFACE, "StartDTMF",
+                               start_dtmf_reply, NULL,
+                               DBUS_TYPE_BYTE, &tone,
+                               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);
+}
+
+static int tonegen_startevent(char tone)
+{
+       int ret;
+       dbus_uint32_t event_tone;
+       dbus_int32_t dbm0 = -15;
+       dbus_uint32_t duration = 150;
+
+       switch (tone) {
+       case '*':
+               event_tone = DTMF_ASTERISK;
+               break;
+       case '#':
+               event_tone = DTMF_HASHMARK;
+               break;
+       case 'A':
+               event_tone = DTMF_A;
+               break;
+       case 'B':
+               event_tone = DTMF_B;
+               break;
+       case 'C':
+               event_tone = DTMF_C;
+               break;
+       case 'D':
+               event_tone = DTMF_D;
+               break;
+       default:
+               ret = g_ascii_digit_value(tone);
+               if (ret < 0)
+                       return -EINVAL;
+               event_tone = ret;
+       }
+
+       ret = send_method_call(TONEGEN_BUS_NAME, TONEGEN_PATH,
+                               TONEGEN_INTERFACE, "StartEventTone",
+                               NULL, NULL,
+                               DBUS_TYPE_UINT32, &event_tone,
+                               DBUS_TYPE_INT32, &dbm0,
+                               DBUS_TYPE_UINT32, &duration,
+                               DBUS_TYPE_INVALID);
+       return ret;
+}
+
+static gboolean stop_feedback_tone(gpointer user_data)
+{
+       if (g_slist_length(tones) > 0) {
+               gpointer ptone;
+               int ret;
+
+               send_method_call(TONEGEN_BUS_NAME, TONEGEN_PATH,
+                               TONEGEN_INTERFACE, "StopTone",
+                               NULL, NULL,
+                               DBUS_TYPE_INVALID);
+
+               ptone = g_slist_nth_data(tones, 0);
+               tones = g_slist_remove(tones, ptone);
+
+               ret = tonegen_startevent(GPOINTER_TO_UINT(ptone));
+               if (ret < 0)
+                       goto done;
+
+               return TRUE;
+       }
+done:
+       return FALSE;
+}
+
+static void tones_timer_notify(gpointer data)
+{
+       send_method_call(TONEGEN_BUS_NAME, TONEGEN_PATH,
+                               TONEGEN_INTERFACE, "StopTone",
+                               NULL, NULL,
+                               DBUS_TYPE_INVALID);
+       g_slist_free(tones);
+       tones = NULL;
+
+       create_tones_timer = 0;
+}
+
+static void start_feedback_tone(char tone)
+{
+       if (!create_tones_timer) {
+               int ret;
+
+               ret = tonegen_startevent(tone);
+               if (ret < 0)
+                       return;
+
+               create_tones_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
+                                               FEEDBACK_TONE_DURATION,
+                                               stop_feedback_tone,
+                                               NULL,
+                                               tones_timer_notify);
+       } else {
+               glong dtmf_tone = tone;
+
+               DBG("add %c to queue", tone);
+               tones = g_slist_append(tones, GUINT_TO_POINTER(dtmf_tone));
+       }
+}
+
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+       DBG("telephony-maemo6: transmit dtmf: %c", tone);
+
+       start_dtmf(telephony_device, tone);
+
+       if (!find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+               error("No active call");
+       else
+               start_feedback_tone(tone);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+       DBG("telephony-maemo6: 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;
+
+               /*
+                * PROCEEDING is followed by WAITING CSD status, therefore
+                * second incoming call status indication is set immediately
+                * to waiting.
+                */
+               if (g_slist_length(active_calls) > 0)
+                       return CALL_STATUS_WAITING;
+
+               return CALL_STATUS_INCOMING;
+       case CSD_CALL_STATUS_COMING:
+               if (g_slist_length(active_calls) > 0)
+                       return CALL_STATUS_WAITING;
+
+               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;
+
+       DBG("telephony-maemo6: 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;
+
+       DBG("telephony-maemo6: 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':
+               if (find_call_with_status(CSD_CALL_STATUS_WAITING))
+                       foreach_call_with_status(CSD_CALL_STATUS_WAITING,
+                                                               release_call);
+               else
+                       foreach_call_with_status(CSD_CALL_STATUS_HOLD,
+                                                               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:
+               DBG("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)
+{
+       DBG("telephony-maemo6: 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;
+
+       DBG("telephony-maemo6: 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);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+       DBG("telephony-maemo6: got %s voice dial request",
+                       enable ? "enable" : "disable");
+
+       telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
+}
+
+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;
+       }
+
+       DBG("Incoming call to %s from number %s", call_path, number);
+
+       g_free(call->number);
+       call->number = g_strdup(number);
+
+       if (find_call_with_status(CSD_CALL_STATUS_ACTIVE) ||
+                       find_call_with_status(CSD_CALL_STATUS_HOLD))
+               telephony_call_waiting_ind(call->number,
+                                               number_type(call->number));
+       else
+               telephony_incoming_call_ind(call->number,
+                                               number_type(call->number));
+
+       telephony_update_indicator(maemo_indicators, "callsetup",
+                                       EV_CALLSETUP_INCOMING);
+}
+
+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;
+       }
+
+       DBG("Outgoing call from %s to number %s", call_path, number);
+
+       g_free(call->number);
+       call->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)
+{
+       DBG("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 call_set_status(struct csd_call *call, dbus_uint32_t status)
+{
+       dbus_uint32_t prev_status;
+       int callheld = telephony_get_indicator(maemo_indicators, "callheld");
+
+       prev_status = call->status;
+       DBG("Call %s changed from %s to %s", call->object_path,
+               call_status_str[prev_status], call_status_str[status]);
+
+       if (prev_status == status) {
+               DBG("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:
+               /* Some headsets expect incoming call notification before they
+                * can send ATA command. When call changed status from waiting
+                * to alerting we need to send missing notification. Otherwise
+                * headsets like Nokia BH-108 or BackBeat 903 are unable to
+                * answer incoming call that was previously waiting. */
+               if (prev_status == CSD_CALL_STATUS_WAITING)
+                       telephony_incoming_call_ind(call->number,
+                                               number_type(call->number));
+               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);
+
+               if (create_tones_timer)
+                       g_source_remove(create_tones_timer);
+               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);
+                       return;
+               }
+
+               if (callheld == EV_CALLHELD_MULTIPLE &&
+                               find_call_with_status(CSD_CALL_STATUS_HOLD) &&
+                               !find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+                       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_call_status(DBusMessage *msg, const char *call_path)
+{
+       struct csd_call *call;
+       dbus_uint32_t status, cause_type, cause;
+
+       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;
+       }
+
+       call_set_status(call, status);
+}
+
+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;
+       }
+
+       DBG("Call %s %s the conference", path, joined ? "joined" : "left");
+
+       call->conference = joined;
+}
+
+static uint8_t str2status(const char *state)
+{
+       if (g_strcmp0(state, "Home") == 0)
+               return NETWORK_REG_STATUS_HOME;
+       else if (g_strcmp0(state, "Roaming") == 0)
+               return NETWORK_REG_STATUS_ROAMING;
+       else if (g_strcmp0(state, "Offline") == 0)
+               return NETWORK_REG_STATUS_OFFLINE;
+       else if (g_strcmp0(state, "Searching") == 0)
+               return NETWORK_REG_STATUS_SEARCHING;
+       else if (g_strcmp0(state, "NoSim") == 0)
+               return NETWORK_REG_STATUS_NO_SIM;
+       else if (g_strcmp0(state, "Poweroff") == 0)
+               return NETWORK_REG_STATUS_POWEROFF;
+       else if (g_strcmp0(state, "Powersafe") == 0)
+               return NETWORK_REG_STATUS_POWERSAFE;
+       else if (g_strcmp0(state, "NoCoverage") == 0)
+               return NETWORK_REG_STATUS_NO_COVERAGE;
+       else if (g_strcmp0(state, "Reject") == 0)
+               return NETWORK_REG_STATUS_REJECTED;
+       else
+               return NETWORK_REG_STATUS_UNKOWN;
+}
+
+static void update_registration_status(const char *status)
+{
+       uint8_t new_status;
+
+       new_status = str2status(status);
+
+       if (net.status == new_status)
+               return;
+
+       switch (new_status) {
+       case NETWORK_REG_STATUS_HOME:
+               telephony_update_indicator(maemo_indicators, "roam",
+                                                       EV_ROAM_INACTIVE);
+               if (net.status > NETWORK_REG_STATUS_ROAMING)
+                       telephony_update_indicator(maemo_indicators,
+                                                       "service",
+                                                       EV_SERVICE_PRESENT);
+               break;
+       case NETWORK_REG_STATUS_ROAMING:
+               telephony_update_indicator(maemo_indicators, "roam",
+                                                       EV_ROAM_ACTIVE);
+               if (net.status > NETWORK_REG_STATUS_ROAMING)
+                       telephony_update_indicator(maemo_indicators,
+                                                       "service",
+                                                       EV_SERVICE_PRESENT);
+               break;
+       case NETWORK_REG_STATUS_OFFLINE:
+       case NETWORK_REG_STATUS_SEARCHING:
+       case NETWORK_REG_STATUS_NO_SIM:
+       case NETWORK_REG_STATUS_POWEROFF:
+       case NETWORK_REG_STATUS_POWERSAFE:
+       case NETWORK_REG_STATUS_NO_COVERAGE:
+       case NETWORK_REG_STATUS_REJECTED:
+       case NETWORK_REG_STATUS_UNKOWN:
+               if (net.status < NETWORK_REG_STATUS_OFFLINE)
+                       telephony_update_indicator(maemo_indicators,
+                                                       "service",
+                                                       EV_SERVICE_NONE);
+               break;
+       }
+
+       net.status = new_status;
+
+       DBG("telephony-maemo6: registration status changed: %s", status);
+}
+
+static void handle_registration_changed(DBusMessage *msg)
+{
+       const char *status;
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &status,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in RegistrationChanged");
+               return;
+       }
+
+       update_registration_status(status);
+}
+
+static void update_signal_strength(int32_t signal_bars)
+{
+       if (signal_bars < 0) {
+               DBG("signal strength smaller than expected: %d < 0",
+                                                               signal_bars);
+               signal_bars = 0;
+       } else if (signal_bars > 5) {
+               DBG("signal strength greater than expected: %d > 5",
+                                                               signal_bars);
+               signal_bars = 5;
+       }
+
+       if (net.signal_bars == signal_bars)
+               return;
+
+       telephony_update_indicator(maemo_indicators, "signal", signal_bars);
+
+       net.signal_bars = signal_bars;
+       DBG("telephony-maemo6: signal strength updated: %d/5", signal_bars);
+}
+
+static void handle_signal_bars_changed(DBusMessage *msg)
+{
+       int32_t signal_bars;
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_INT32, &signal_bars,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in SignalBarsChanged");
+               return;
+       }
+
+       update_signal_strength(signal_bars);
+}
+
+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;
+       }
+
+       if (!dbus_message_get_args(reply, NULL,
+                               DBUS_TYPE_INT32, &level,
+                               DBUS_TYPE_INVALID)) {
+               error("Unexpected args in hald reply");
+               goto done;
+       }
+
+       *value = (int) level;
+
+       if (value == &battchg_last)
+               DBG("telephony-maemo6: battery.charge_level.last_full is %d",
+                               *value);
+       else if (value == &battchg_design)
+               DBG("telephony-maemo6: battery.charge_level.design is %d",
+                               *value);
+       else
+               DBG("telephony-maemo6: 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);
+       remove_pending(call);
+}
+
+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 void csd_call_free(void *data)
+{
+       struct csd_call *call = data;
+
+       if (!call)
+               return;
+
+       g_free(call->object_path);
+       g_free(call->number);
+
+       g_slist_foreach(pending, remove_pending_by_data, call);
+
+       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);
+                       calls = g_slist_append(calls, call);
+                       DBG("telephony-maemo6: new csd call instance at %s",
+                                                               object_path);
+               }
+
+               if (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);
+
+               /* Update indicators */
+               call_set_status(call, status);
+
+       } while (dbus_message_iter_next(iter));
+}
+
+static void update_operator_name(const char *name)
+{
+       if (name == NULL)
+               return;
+
+       g_free(net.operator_name);
+       net.operator_name = g_strndup(name, 16);
+       DBG("telephony-maemo6: operator name updated: %s", name);
+}
+
+static void get_property_reply(DBusPendingCall *call, void *user_data)
+{
+       char *prop = 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_VARIANT) {
+               error("Unexpected signature in Get return");
+               goto done;
+       }
+
+       dbus_message_iter_recurse(&iter, &sub);
+
+       if (g_strcmp0(prop, "RegistrationStatus") == 0) {
+               const char *status;
+
+               dbus_message_iter_get_basic(&sub, &status);
+               update_registration_status(status);
+
+               get_property(CSD_CSNET_OPERATOR, "OperatorName");
+               get_property(CSD_CSNET_SIGNAL, "SignalBars");
+       } else if (g_strcmp0(prop, "OperatorName") == 0) {
+               const char *name;
+
+               dbus_message_iter_get_basic(&sub, &name);
+               update_operator_name(name);
+       } else if (g_strcmp0(prop, "SignalBars") == 0) {
+               int32_t signal_bars;
+
+               dbus_message_iter_get_basic(&sub, &signal_bars);
+               update_signal_strength(signal_bars);
+       }
+
+done:
+       g_free(prop);
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+static int get_property(const char *iface, const char *prop)
+{
+       return send_method_call(CSD_CSNET_BUS_NAME, CSD_CSNET_PATH,
+                               DBUS_INTERFACE_PROPERTIES, "Get",
+                               get_property_reply, g_strdup(prop),
+                               DBUS_TYPE_STRING, &iface,
+                               DBUS_TYPE_STRING, &prop,
+                               DBUS_TYPE_INVALID);
+}
+
+static void handle_operator_name_changed(DBusMessage *msg)
+{
+       const char *name;
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &name,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in OperatorNameChanged");
+               return;
+       }
+
+       update_operator_name(name);
+}
+
+static void call_info_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusError err;
+       DBusMessage *reply;
+       DBusMessageIter iter, sub;
+
+       get_calls_active = FALSE;
+
+       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_property(CSD_CSNET_REGISTRATION, "RegistrationStatus");
+
+done:
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+
+static void phonebook_read_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusError derr;
+       DBusMessage *reply;
+       const char *name, *number, *secondname, *additionalnumber, *email;
+       int index;
+       char **number_type = user_data;
+
+       reply = dbus_pending_call_steal_reply(call);
+
+       dbus_error_init(&derr);
+       if (dbus_set_error_from_message(&derr, reply)) {
+               error("%s.ReadFirst replied with an error: %s, %s",
+                               CSD_SIMPB_INTERFACE, derr.name, derr.message);
+               dbus_error_free(&derr);
+               if (number_type == &vmbx)
+                       vmbx = g_strdup(getenv("VMBX_NUMBER"));
+               goto done;
+       }
+
+       dbus_error_init(&derr);
+       if (dbus_message_get_args(reply, NULL,
+                               DBUS_TYPE_INT32, &index,
+                               DBUS_TYPE_STRING, &name,
+                               DBUS_TYPE_STRING, &number,
+                               DBUS_TYPE_STRING, &secondname,
+                               DBUS_TYPE_STRING, &additionalnumber,
+                               DBUS_TYPE_STRING, &email,
+                               DBUS_TYPE_INVALID) == FALSE) {
+               error("Unable to parse %s.ReadFirst arguments: %s, %s",
+                               CSD_SIMPB_INTERFACE, derr.name, derr.message);
+               dbus_error_free(&derr);
+               goto done;
+       }
+
+       if (number_type == &msisdn) {
+               g_free(msisdn);
+               msisdn = g_strdup(number);
+               DBG("Got MSISDN %s (%s)", number, name);
+       } else {
+               g_free(vmbx);
+               vmbx = g_strdup(number);
+               DBG("Got voice mailbox number %s (%s)", number, name);
+       }
+
+done:
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+static void csd_init(void)
+{
+       const char *pb_type;
+       int ret;
+
+       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;
+       }
+
+       get_calls_active = TRUE;
+
+       pb_type = CSD_SIMPB_TYPE_MSISDN;
+
+       ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH,
+                               CSD_SIMPB_INTERFACE, "ReadFirst",
+                               phonebook_read_reply, &msisdn,
+                               DBUS_TYPE_STRING, &pb_type,
+                               DBUS_TYPE_INVALID);
+       if (ret < 0) {
+               error("Unable to send " CSD_SIMPB_INTERFACE ".read()");
+               return;
+       }
+
+       /* Voicemail should be in MBDN index 0 */
+       pb_type = CSD_SIMPB_TYPE_MBDN;
+
+       ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH,
+                               CSD_SIMPB_INTERFACE, "ReadFirst",
+                               phonebook_read_reply, &vmbx,
+                               DBUS_TYPE_STRING, &pb_type,
+                               DBUS_TYPE_INVALID);
+       if (ret < 0) {
+               error("Unable to send " CSD_SIMPB_INTERFACE ".read()");
+               return;
+       }
+}
+
+static void handle_modem_state(DBusMessage *msg)
+{
+       const char *state;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &state,
+                                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected modem state parameters");
+               return;
+       }
+
+       DBG("SSC modem state: %s", state);
+
+       if (calls != NULL || get_calls_active)
+               return;
+
+       if (g_str_equal(state, "cmt_ready") || g_str_equal(state, "online"))
+               csd_init();
+}
+
+static void modem_state_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusMessage *reply = dbus_pending_call_steal_reply(call);
+       DBusError err;
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, reply)) {
+               error("get_modem_state: %s, %s", err.name, err.message);
+               dbus_error_free(&err);
+       } else
+               handle_modem_state(reply);
+
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+static gboolean signal_filter(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       const char *path = dbus_message_get_path(msg);
+
+       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, CSD_CSNET_REGISTRATION,
+                               "RegistrationChanged"))
+               handle_registration_changed(msg);
+       else if (dbus_message_is_signal(msg, CSD_CSNET_OPERATOR,
+                               "OperatorNameChanged"))
+               handle_operator_name_changed(msg);
+       else if (dbus_message_is_signal(msg, CSD_CSNET_SIGNAL,
+                               "SignalBarsChanged"))
+               handle_signal_bars_changed(msg);
+       else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device",
+                                       "PropertyModified"))
+               handle_hal_property_modified(msg);
+       else if (dbus_message_is_signal(msg, SSC_DBUS_IFACE,
+                                               "modem_state_changed_ind"))
+               handle_modem_state(msg);
+
+       return TRUE;
+}
+
+static void add_watch(const char *sender, const char *path,
+                               const char *interface, const char *member)
+{
+       guint watch;
+
+       watch = g_dbus_add_signal_watch(connection, sender, path, interface,
+                                       member, signal_filter, NULL, NULL);
+
+       watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
+}
+
+static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusError err;
+       DBusMessage *reply;
+       DBusMessageIter iter, sub;
+       const char *path;
+       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 FindDeviceByCapability 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);
+
+       DBG("telephony-maemo6: found battery device at %s", path);
+
+       add_watch(NULL, path, "org.freedesktop.Hal.Device",
+                                                       "PropertyModified");
+
+       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);
+       remove_pending(call);
+}
+
+int telephony_init(void)
+{
+       const char *battery_cap = "battery";
+       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;
+       int i;
+
+       DBG("");
+
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+       add_watch(NULL, NULL, CSD_CALL_INTERFACE, NULL);
+       add_watch(NULL, NULL, CSD_CALL_INSTANCE, NULL);
+       add_watch(NULL, NULL, CSD_CALL_CONFERENCE, NULL);
+       add_watch(NULL, NULL, CSD_CSNET_REGISTRATION, "RegistrationChanged");
+       add_watch(NULL, NULL, CSD_CSNET_OPERATOR, "OperatorNameChanged");
+       add_watch(NULL, NULL, CSD_CSNET_SIGNAL, "SignalBarsChanged");
+       add_watch(NULL, NULL, SSC_DBUS_IFACE, "modem_state_changed_ind");
+
+       if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE,
+                                       "get_modem_state", modem_state_reply,
+                                       NULL, DBUS_TYPE_INVALID) < 0)
+               error("Unable to send " SSC_DBUS_IFACE ".get_modem_state()");
+
+       /* Reset indicators */
+       for (i = 0; maemo_indicators[i].desc != NULL; i++) {
+               if (g_str_equal(maemo_indicators[i].desc, "battchg"))
+                       maemo_indicators[i].val = 5;
+               else
+                       maemo_indicators[i].val = 0;
+       }
+
+       telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED,
+                                                               chld_str);
+       if (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) < 0)
+               error("Unable to send HAL method call");
+
+       return 0;
+}
+
+static void remove_watch(gpointer data)
+{
+       g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data));
+}
+
+void telephony_exit(void)
+{
+       DBG("");
+
+       g_free(net.operator_name);
+       net.operator_name = NULL;
+
+       net.status = NETWORK_REG_STATUS_UNKOWN;
+       net.signal_bars = 0;
+
+       g_slist_free(active_calls);
+       active_calls = NULL;
+
+       g_slist_free_full(calls, csd_call_free);
+       calls = NULL;
+
+       g_slist_free_full(pending, pending_req_finalize);
+       pending = NULL;
+
+       g_slist_free_full(watches, remove_watch);
+       watches = NULL;
+
+       dbus_connection_unref(connection);
+       connection = NULL;
+
+       telephony_deinit();
+}
diff --git a/audio/telephony-ofono.c b/audio/telephony-ofono.c
new file mode 100644 (file)
index 0000000..26815cf
--- /dev/null
@@ -0,0 +1,1638 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009-2010  Intel Corporation
+ *  Copyright (C) 2006-2009  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/sdp.h>
+
+#include "glib-helper.h"
+#include "log.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;
+       gboolean conference;
+       char *number;
+       guint watch;
+};
+
+static DBusConnection *connection = NULL;
+static char *modem_obj_path = NULL;
+static char *last_dialed_number = NULL;
+static GSList *calls = NULL;
+static GSList *watches = NULL;
+static GSList *pending = NULL;
+
+#define OFONO_BUS_NAME "org.ofono"
+#define OFONO_PATH "/"
+#define OFONO_MODEM_INTERFACE "org.ofono.Modem"
+#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;
+
+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 struct voice_call *find_vc_without_status(int status)
+{
+       GSList *l;
+
+       for (l = calls; l != NULL; l = l->next) {
+               struct voice_call *call = l->data;
+
+               if (call->status != status)
+                       return call;
+       }
+
+       return NULL;
+}
+
+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 voice_call *coming;
+
+       DBG("telephony-ofono: device %p connected", telephony_device);
+
+       coming = find_vc_with_status(CALL_STATUS_ALERTING);
+       if (coming) {
+               if (find_vc_with_status(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)
+{
+       DBG("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)
+{
+       telephony_response_and_hold_rsp(telephony_device,
+                                               CME_ERROR_NOT_SUPPORTED);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+       DBG("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);
+       pending = g_slist_prepend(pending, call);
+       dbus_message_unref(msg);
+
+       return 0;
+}
+
+static int answer_call(struct voice_call *vc)
+{
+       DBG("%s", vc->number);
+       return send_method_call(OFONO_BUS_NAME, vc->obj_path,
+                                               OFONO_VC_INTERFACE, "Answer",
+                                               NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_call(struct voice_call *vc)
+{
+       DBG("%s", vc->number);
+       return send_method_call(OFONO_BUS_NAME, vc->obj_path,
+                                               OFONO_VC_INTERFACE, "Hangup",
+                                               NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_answer_calls(void)
+{
+       DBG("");
+       return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+                                               OFONO_VCMANAGER_INTERFACE,
+                                               "ReleaseAndAnswer",
+                                               NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int split_call(struct voice_call *call)
+{
+       DBG("%s", call->number);
+       return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+                                               OFONO_VCMANAGER_INTERFACE,
+                                               "PrivateChat",
+                                               NULL, NULL,
+                                               DBUS_TYPE_OBJECT_PATH,
+                                               call->obj_path,
+                                               DBUS_TYPE_INVALID);
+       return -1;
+}
+
+static int swap_calls(void)
+{
+       DBG("");
+       return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+                                               OFONO_VCMANAGER_INTERFACE,
+                                               "SwapCalls",
+                                               NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int create_conference(void)
+{
+       DBG("");
+       return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+                                               OFONO_VCMANAGER_INTERFACE,
+                                               "CreateMultiparty",
+                                               NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_conference(void)
+{
+       DBG("");
+       return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+                                               OFONO_VCMANAGER_INTERFACE,
+                                               "HangupMultiparty",
+                                               NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int call_transfer(void)
+{
+       DBG("");
+       return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+                                               OFONO_VCMANAGER_INTERFACE,
+                                               "Transfer",
+                                               NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+       struct voice_call *call;
+       struct voice_call *alerting;
+       int err;
+
+       call = find_vc_with_status(CALL_STATUS_ACTIVE);
+       if (!call)
+               call = calls->data;
+
+       if (!call) {
+               error("No active call");
+               telephony_terminate_call_rsp(telephony_device,
+                                               CME_ERROR_NOT_ALLOWED);
+               return;
+       }
+
+       alerting = find_vc_with_status(CALL_STATUS_ALERTING);
+       if (call->status == CALL_STATUS_HELD && alerting)
+               err = release_call(alerting);
+       else if (call->conference)
+               err = release_conference();
+       else
+               err = release_call(call);
+
+       if (err < 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 voice_call *vc;
+       int ret;
+
+       vc = find_vc_with_status(CALL_STATUS_INCOMING);
+       if (!vc)
+               vc = find_vc_with_status(CALL_STATUS_ALERTING);
+
+       if (!vc)
+               vc = find_vc_with_status(CALL_STATUS_WAITING);
+
+       if (!vc) {
+               telephony_answer_call_rsp(telephony_device,
+                                       CME_ERROR_NOT_ALLOWED);
+               return;
+       }
+
+       ret = answer_call(vc);
+       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)
+{
+       const char *clir;
+       int ret;
+
+       DBG("telephony-ofono: dial request to %s", number);
+
+       if (!modem_obj_path) {
+               telephony_dial_number_rsp(telephony_device,
+                                       CME_ERROR_AG_FAILURE);
+               return;
+       }
+
+       if (!strncmp(number, "*31#", 4)) {
+               number += 4;
+               clir = "enabled";
+       } else if (!strncmp(number, "#31#", 4)) {
+               number += 4;
+               clir =  "disabled";
+       } else
+               clir = "default";
+
+       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;
+
+       DBG("telephony-ofono: transmit dtmf: %c", tone);
+
+       if (!modem_obj_path) {
+               telephony_transmit_dtmf_rsp(telephony_device,
+                                       CME_ERROR_AG_FAILURE);
+               return;
+       }
+
+       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)
+{
+       DBG("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;
+
+       DBG("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, multiparty;
+
+               direction = vc->originating ?
+                               CALL_DIR_OUTGOING : CALL_DIR_INCOMING;
+
+               multiparty = vc->conference ?
+                               CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO;
+
+               DBG("call %s direction %d multiparty %d", vc->number,
+                                                       direction, multiparty);
+
+               telephony_list_current_call_ind(i, direction, vc->status,
+                                       CALL_MODE_VOICE, multiparty,
+                                       vc->number, number_type(vc->number));
+       }
+
+       telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_operator_selection_req(void *telephony_device)
+{
+       DBG("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);
+}
+
+static void foreach_vc_with_status(int status,
+                                       int (*func)(struct voice_call *vc))
+{
+       GSList *l;
+
+       for (l = calls; l != NULL; l = l->next) {
+               struct voice_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 voice_call *call;
+       int err = 0;
+
+       DBG("telephony-ofono: 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':
+               if (find_vc_with_status(CALL_STATUS_WAITING))
+                       foreach_vc_with_status(CALL_STATUS_WAITING,
+                                                               release_call);
+               else
+                       foreach_vc_with_status(CALL_STATUS_HELD, release_call);
+               break;
+       case '1':
+               if (idx) {
+                       if (call)
+                               err = release_call(call);
+                       break;
+               }
+               err = release_answer_calls();
+               break;
+       case '2':
+               if (idx) {
+                       if (call)
+                               err = split_call(call);
+               } else {
+                       call = find_vc_with_status(CALL_STATUS_WAITING);
+
+                       if (call)
+                               err = answer_call(call);
+                       else
+                               err = swap_calls();
+               }
+               break;
+       case '3':
+               if (find_vc_with_status(CALL_STATUS_HELD) ||
+                               find_vc_with_status(CALL_STATUS_WAITING))
+                       err = create_conference();
+               break;
+       case '4':
+               err = call_transfer();
+               break;
+       default:
+               DBG("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)
+{
+       DBG("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)
+{
+       struct voice_call *active, *incoming;
+       int err;
+
+       DBG("telephony-ofono: got key press request for %s", keys);
+
+       incoming = find_vc_with_status(CALL_STATUS_INCOMING);
+
+       active = find_vc_with_status(CALL_STATUS_ACTIVE);
+
+       if (incoming)
+               err = answer_call(incoming);
+       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);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+       DBG("telephony-ofono: got %s voice dial request",
+                       enable ? "enable" : "disable");
+
+       telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
+}
+
+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 call_free(void *data)
+{
+       struct voice_call *vc = data;
+
+       DBG("%s", vc->obj_path);
+
+       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();
+
+       g_dbus_remove_watch(connection, vc->watch);
+       g_free(vc->obj_path);
+       g_free(vc->number);
+       g_free(vc);
+}
+
+static gboolean handle_vc_property_changed(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct voice_call *vc = data;
+       const char *obj_path = dbus_message_get_path(msg);
+       DBusMessageIter iter, sub;
+       const char *property, *state;
+
+       DBG("path %s", obj_path);
+
+       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 TRUE;
+       }
+
+       dbus_message_iter_get_basic(&iter, &property);
+       DBG("property %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);
+               DBG("State %s", state);
+               if (g_str_equal(state, "disconnected")) {
+                       calls = g_slist_remove(calls, vc);
+                       call_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;
+               } else if (g_str_equal(state, "alerting")) {
+                       telephony_update_indicator(ofono_indicators,
+                                       "callsetup", EV_CALLSETUP_ALERTING);
+                       vc->status = CALL_STATUS_ALERTING;
+                       vc->originating = TRUE;
+               } 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;
+                       vc->originating = FALSE;
+               } else if (g_str_equal(state, "held")) {
+                       vc->status = CALL_STATUS_HELD;
+                       if (find_vc_without_status(CALL_STATUS_HELD))
+                               telephony_update_indicator(ofono_indicators,
+                                                       "callheld",
+                                                       EV_CALLHELD_MULTIPLE);
+                       else
+                               telephony_update_indicator(ofono_indicators,
+                                                       "callheld",
+                                                       EV_CALLHELD_ON_HOLD);
+               }
+       } else if (g_str_equal(property, "Multiparty")) {
+               dbus_bool_t multiparty;
+
+               dbus_message_iter_get_basic(&sub, &multiparty);
+               DBG("Multiparty %s", multiparty ? "True" : "False");
+               vc->conference = multiparty;
+       }
+
+       return TRUE;
+}
+
+static struct voice_call *call_new(const char *path, DBusMessageIter *properties)
+{
+       struct voice_call *vc;
+
+       DBG("%s", path);
+
+       vc = g_new0(struct voice_call, 1);
+       vc->obj_path = g_strdup(path);
+       vc->watch = g_dbus_add_signal_watch(connection, NULL, path,
+                                       OFONO_VC_INTERFACE, "PropertyChanged",
+                                       handle_vc_property_changed, vc, NULL);
+
+       while (dbus_message_iter_get_arg_type(properties)
+                                               == DBUS_TYPE_DICT_ENTRY) {
+               DBusMessageIter entry, value;
+               const char *property, *cli, *state;
+               dbus_bool_t multiparty;
+
+               dbus_message_iter_recurse(properties, &entry);
+               dbus_message_iter_get_basic(&entry, &property);
+
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_recurse(&entry, &value);
+
+               if (g_str_equal(property, "LineIdentification")) {
+                       dbus_message_iter_get_basic(&value, &cli);
+                       DBG("cli %s", cli);
+                       vc->number = g_strdup(cli);
+               } else if (g_str_equal(property, "State")) {
+                       dbus_message_iter_get_basic(&value, &state);
+                       DBG("state %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;
+                       else if (g_str_equal(state, "held"))
+                               vc->status = CALL_STATUS_HELD;
+               } else if (g_str_equal(property, "Multiparty")) {
+                       dbus_message_iter_get_basic(&value, &multiparty);
+                       DBG("Multipary %s", multiparty ? "True" : "False");
+                       vc->conference = multiparty;
+               }
+
+               dbus_message_iter_next(properties);
+       }
+
+       switch (vc->status) {
+       case CALL_STATUS_INCOMING:
+               DBG("CALL_STATUS_INCOMING");
+               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:
+               DBG("CALL_STATUS_DIALING");
+               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:
+               DBG("CALL_STATUS_ALERTING");
+               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:
+               DBG("CALL_STATUS_WAITING");
+               vc->originating = FALSE;
+               telephony_update_indicator(ofono_indicators, "callsetup",
+                                       EV_CALLSETUP_INCOMING);
+               telephony_call_waiting_ind(vc->number, NUMBER_TYPE_TELEPHONY);
+               break;
+       }
+
+       return vc;
+}
+
+static void remove_pending(DBusPendingCall *call)
+{
+       pending = g_slist_remove(pending, call);
+       dbus_pending_call_unref(call);
+}
+
+static void call_added(const char *path, DBusMessageIter *properties)
+{
+       struct voice_call *vc;
+
+       DBG("%s", path);
+
+       vc = find_vc(path);
+       if (vc)
+               return;
+
+       vc = call_new(path, properties);
+       calls = g_slist_prepend(calls, vc);
+}
+
+static void get_calls_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusError err;
+       DBusMessage *reply;
+       DBusMessageIter iter, entry;
+
+       DBG("");
+       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");
+               goto done;
+       }
+
+       dbus_message_iter_recurse(&iter, &entry);
+
+       while (dbus_message_iter_get_arg_type(&entry)
+                                               == DBUS_TYPE_STRUCT) {
+               const char *path;
+               DBusMessageIter value, properties;
+
+               dbus_message_iter_recurse(&entry, &value);
+               dbus_message_iter_get_basic(&value, &path);
+
+               dbus_message_iter_next(&value);
+               dbus_message_iter_recurse(&value, &properties);
+
+               call_added(path, &properties);
+
+               dbus_message_iter_next(&entry);
+       }
+
+done:
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+static void handle_network_property(const char *property, DBusMessageIter *variant)
+{
+       const char *status, *operator;
+       unsigned int signals_bar;
+
+       if (g_str_equal(property, "Status")) {
+               dbus_message_iter_get_basic(variant, &status);
+               DBG("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, "Name")) {
+               dbus_message_iter_get_basic(variant, &operator);
+               DBG("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(variant, &signals_bar);
+               DBG("SignalStrength is %d", signals_bar);
+               net.signals_bar = signals_bar;
+               telephony_update_indicator(ofono_indicators, "signal",
+                                               (signals_bar + 20) / 21);
+       }
+}
+
+static int parse_network_properties(DBusMessageIter *properties)
+{
+       int i;
+
+       /* Reset indicators */
+       for (i = 0; ofono_indicators[i].desc != NULL; i++) {
+               if (g_str_equal(ofono_indicators[i].desc, "battchg"))
+                       ofono_indicators[i].val = 5;
+               else
+                       ofono_indicators[i].val = 0;
+       }
+
+       while (dbus_message_iter_get_arg_type(properties)
+                                               == DBUS_TYPE_DICT_ENTRY) {
+               const char *key;
+               DBusMessageIter value, entry;
+
+               dbus_message_iter_recurse(properties, &entry);
+               dbus_message_iter_get_basic(&entry, &key);
+
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_recurse(&entry, &value);
+
+               handle_network_property(key, &value);
+
+               dbus_message_iter_next(properties);
+       }
+
+       return 0;
+}
+
+static void get_properties_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusError err;
+       DBusMessage *reply;
+       DBusMessageIter iter, properties;
+       int ret = 0;
+
+       DBG("");
+       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");
+               goto done;
+       }
+
+       dbus_message_iter_recurse(&iter, &properties);
+
+       ret = parse_network_properties(&properties);
+       if (ret < 0) {
+               error("Unable to parse %s.GetProperty reply",
+                                               OFONO_NETWORKREG_INTERFACE);
+               goto done;
+       }
+
+       ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
+                               OFONO_VCMANAGER_INTERFACE, "GetCalls",
+                               get_calls_reply, NULL, DBUS_TYPE_INVALID);
+       if (ret < 0)
+               error("Unable to send %s.GetCalls",
+                                               OFONO_VCMANAGER_INTERFACE);
+
+done:
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+static void network_found(const char *path)
+{
+       int ret;
+
+       DBG("%s", path);
+
+       modem_obj_path = g_strdup(path);
+
+       ret = send_method_call(OFONO_BUS_NAME, path,
+                               OFONO_NETWORKREG_INTERFACE, "GetProperties",
+                               get_properties_reply, NULL, DBUS_TYPE_INVALID);
+       if (ret < 0)
+               error("Unable to send %s.GetProperties",
+                                               OFONO_NETWORKREG_INTERFACE);
+}
+
+static void modem_removed(const char *path)
+{
+       if (g_strcmp0(modem_obj_path, path) != 0)
+               return;
+
+       DBG("%s", path);
+
+       g_slist_free_full(calls, call_free);
+       calls = NULL;
+
+       g_free(net.operator_name);
+       net.operator_name = NULL;
+       net.status = NETWORK_REG_STATUS_NOSERV;
+       net.signals_bar = 0;
+
+       g_free(modem_obj_path);
+       modem_obj_path = NULL;
+}
+
+static void parse_modem_interfaces(const char *path, DBusMessageIter *ifaces)
+{
+       DBG("%s", path);
+
+       while (dbus_message_iter_get_arg_type(ifaces) == DBUS_TYPE_STRING) {
+               const char *iface;
+
+               dbus_message_iter_get_basic(ifaces, &iface);
+
+               if (g_str_equal(iface, OFONO_NETWORKREG_INTERFACE)) {
+                       network_found(path);
+                       return;
+               }
+
+               dbus_message_iter_next(ifaces);
+       }
+
+       modem_removed(path);
+}
+
+static void modem_added(const char *path, DBusMessageIter *properties)
+{
+       if (modem_obj_path != NULL) {
+               DBG("Ignoring, modem already exist");
+               return;
+       }
+
+       DBG("%s", path);
+
+       while (dbus_message_iter_get_arg_type(properties)
+                                               == DBUS_TYPE_DICT_ENTRY) {
+               const char *key;
+               DBusMessageIter interfaces, value, entry;
+
+               dbus_message_iter_recurse(properties, &entry);
+               dbus_message_iter_get_basic(&entry, &key);
+
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_recurse(&entry, &value);
+
+               if (strcasecmp(key, "Interfaces") != 0)
+                       goto next;
+
+               if (dbus_message_iter_get_arg_type(&value)
+                                                       != DBUS_TYPE_ARRAY) {
+                       error("Invalid Signature");
+                       return;
+               }
+
+               dbus_message_iter_recurse(&value, &interfaces);
+
+               parse_modem_interfaces(path, &interfaces);
+
+               if (modem_obj_path != NULL)
+                       return;
+
+       next:
+               dbus_message_iter_next(properties);
+       }
+}
+
+static void get_modems_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusError err;
+       DBusMessage *reply;
+       DBusMessageIter iter, entry;
+
+       DBG("");
+       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;
+       }
+
+       /* Skip modem selection if a modem already exist */
+       if (modem_obj_path != NULL)
+               goto done;
+
+       dbus_message_iter_init(reply, &iter);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+               error("Unexpected signature");
+               goto done;
+       }
+
+       dbus_message_iter_recurse(&iter, &entry);
+
+       while (dbus_message_iter_get_arg_type(&entry)
+                                               == DBUS_TYPE_STRUCT) {
+               const char *path;
+               DBusMessageIter item, properties;
+
+               dbus_message_iter_recurse(&entry, &item);
+               dbus_message_iter_get_basic(&item, &path);
+
+               dbus_message_iter_next(&item);
+               dbus_message_iter_recurse(&item, &properties);
+
+               modem_added(path, &properties);
+               if (modem_obj_path != NULL)
+                       break;
+
+               dbus_message_iter_next(&entry);
+       }
+
+done:
+       dbus_message_unref(reply);
+       remove_pending(call);
+}
+
+static gboolean handle_network_property_changed(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessageIter iter, variant;
+       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 TRUE;
+       }
+       dbus_message_iter_get_basic(&iter, &property);
+       DBG("in handle_registration_property_changed(),"
+                                       " the property is %s", property);
+
+       dbus_message_iter_next(&iter);
+       dbus_message_iter_recurse(&iter, &variant);
+
+       handle_network_property(property, &variant);
+
+       return TRUE;
+}
+
+static void handle_modem_property(const char *path, const char *property,
+                                               DBusMessageIter *variant)
+{
+       DBG("%s", property);
+
+       if (g_str_equal(property, "Interfaces")) {
+               DBusMessageIter interfaces;
+
+               if (dbus_message_iter_get_arg_type(variant)
+                                                       != DBUS_TYPE_ARRAY) {
+                       error("Invalid signature");
+                       return;
+               }
+
+               dbus_message_iter_recurse(variant, &interfaces);
+               parse_modem_interfaces(path, &interfaces);
+       }
+}
+
+static gboolean handle_modem_property_changed(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessageIter iter, variant;
+       const char *property, *path;
+
+       path = dbus_message_get_path(msg);
+
+       /* Ignore if modem already exist and paths doesn't match */
+       if (modem_obj_path != NULL &&
+                               g_str_equal(path, modem_obj_path) == FALSE)
+               return TRUE;
+
+       dbus_message_iter_init(msg, &iter);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+               error("Unexpected signature in %s.%s PropertyChanged signal",
+                                       dbus_message_get_interface(msg),
+                                       dbus_message_get_member(msg));
+               return TRUE;
+       }
+
+       dbus_message_iter_get_basic(&iter, &property);
+
+       dbus_message_iter_next(&iter);
+       dbus_message_iter_recurse(&iter, &variant);
+
+       handle_modem_property(path, property, &variant);
+
+       return TRUE;
+}
+
+static gboolean handle_vcmanager_call_added(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessageIter iter, properties;
+       const char *path = dbus_message_get_path(msg);
+
+       /* Ignore call if modem path doesn't math */
+       if (g_strcmp0(modem_obj_path, path) != 0)
+               return TRUE;
+
+       dbus_message_iter_init(msg, &iter);
+
+       if (dbus_message_iter_get_arg_type(&iter)
+                                               != DBUS_TYPE_OBJECT_PATH) {
+               error("Unexpected signature in %s.%s signal",
+                                       dbus_message_get_interface(msg),
+                                       dbus_message_get_member(msg));
+               return TRUE;
+       }
+
+       dbus_message_iter_get_basic(&iter, &path);
+       dbus_message_iter_next(&iter);
+       dbus_message_iter_recurse(&iter, &properties);
+
+       call_added(path, &properties);
+
+       return TRUE;
+}
+
+static void call_removed(const char *path)
+{
+       struct voice_call *vc;
+
+       DBG("%s", path);
+
+       vc = find_vc(path);
+       if (vc == NULL)
+               return;
+
+       calls = g_slist_remove(calls, vc);
+       call_free(vc);
+}
+
+static gboolean handle_vcmanager_call_removed(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       const char *path = dbus_message_get_path(msg);
+
+       /* Ignore call if modem path doesn't math */
+       if (g_strcmp0(modem_obj_path, path) != 0)
+               return TRUE;
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID)) {
+               error("Unexpected signature in %s.%s signal",
+                                       dbus_message_get_interface(msg),
+                                       dbus_message_get_member(msg));
+               return TRUE;
+       }
+
+       call_removed(path);
+
+       return TRUE;
+}
+
+static gboolean handle_manager_modem_added(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessageIter iter, properties;
+       const char *path;
+
+       if (modem_obj_path != NULL)
+               return TRUE;
+
+       dbus_message_iter_init(msg, &iter);
+
+       if (dbus_message_iter_get_arg_type(&iter)
+                                               != DBUS_TYPE_OBJECT_PATH) {
+               error("Unexpected signature in %s.%s signal",
+                                       dbus_message_get_interface(msg),
+                                       dbus_message_get_member(msg));
+               return TRUE;
+       }
+
+       dbus_message_iter_get_basic(&iter, &path);
+       dbus_message_iter_next(&iter);
+       dbus_message_iter_recurse(&iter, &properties);
+
+       modem_added(path, &properties);
+
+       return TRUE;
+}
+
+static gboolean handle_manager_modem_removed(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       const char *path;
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID)) {
+               error("Unexpected signature in %s.%s signal",
+                                       dbus_message_get_interface(msg),
+                                       dbus_message_get_member(msg));
+               return TRUE;
+       }
+
+       modem_removed(path);
+
+       return TRUE;
+}
+
+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_error_init(&err);
+       if (dbus_message_get_args(reply, &err,
+                               DBUS_TYPE_INT32, &level,
+                               DBUS_TYPE_INVALID) == FALSE) {
+               error("Unable to parse GetPropertyInteger reply: %s, %s",
+                                                       err.name, err.message);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       *value = (int) level;
+
+       if (value == &battchg_last)
+               DBG("telephony-ofono: battery.charge_level.last_full"
+                                       " is %d", *value);
+       else if (value == &battchg_design)
+               DBG("telephony-ofono: battery.charge_level.design"
+                                       " is %d", *value);
+       else
+               DBG("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);
+       remove_pending(call);
+}
+
+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 gboolean handle_hal_property_modified(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       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 TRUE;
+       }
+
+       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 TRUE;
+       }
+
+       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);
+       }
+
+       return TRUE;
+}
+
+static void add_watch(const char *sender, const char *path,
+                               const char *interface, const char *member,
+                               GDBusSignalFunction function)
+{
+       guint watch;
+
+       watch = g_dbus_add_signal_watch(connection, sender, path, interface,
+                                       member, function, NULL, NULL);
+
+       watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
+}
+
+static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusMessage *reply;
+       DBusError err;
+       DBusMessageIter iter, sub;
+       int type;
+       const char *path;
+
+       DBG("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);
+
+       DBG("telephony-ofono: found battery device at %s", path);
+
+       add_watch(NULL, path, "org.freedesktop.Hal.Device",
+                       "PropertyModified", handle_hal_property_modified);
+
+       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);
+       remove_pending(call);
+}
+
+static void handle_service_connect(DBusConnection *conn, void *user_data)
+{
+       DBG("telephony-ofono: %s found", OFONO_BUS_NAME);
+
+       send_method_call(OFONO_BUS_NAME, OFONO_PATH,
+                               OFONO_MANAGER_INTERFACE, "GetModems",
+                               get_modems_reply, NULL, DBUS_TYPE_INVALID);
+}
+
+static void handle_service_disconnect(DBusConnection *conn, void *user_data)
+{
+       DBG("telephony-ofono: %s exitted", OFONO_BUS_NAME);
+
+       if (modem_obj_path)
+               modem_removed(modem_obj_path);
+}
+
+int telephony_init(void)
+{
+       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;
+       const char *battery_cap = "battery";
+       int ret;
+       guint watch;
+
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+       add_watch(OFONO_BUS_NAME, NULL, OFONO_MODEM_INTERFACE,
+                       "PropertyChanged", handle_modem_property_changed);
+       add_watch(OFONO_BUS_NAME, NULL, OFONO_NETWORKREG_INTERFACE,
+                       "PropertyChanged", handle_network_property_changed);
+       add_watch(OFONO_BUS_NAME, NULL, OFONO_MANAGER_INTERFACE,
+                       "ModemAdded", handle_manager_modem_added);
+       add_watch(OFONO_BUS_NAME, NULL, OFONO_MANAGER_INTERFACE,
+                       "ModemRemoved", handle_manager_modem_removed);
+       add_watch(OFONO_BUS_NAME, NULL, OFONO_VCMANAGER_INTERFACE,
+                       "CallAdded", handle_vcmanager_call_added);
+       add_watch(OFONO_BUS_NAME, NULL, OFONO_VCMANAGER_INTERFACE,
+                       "CallRemoved", handle_vcmanager_call_removed);
+
+       watch = g_dbus_add_service_watch(connection, OFONO_BUS_NAME,
+                                               handle_service_connect,
+                                               handle_service_disconnect,
+                                               NULL, NULL);
+       if (watch == 0)
+               return -ENOMEM;
+
+       watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
+
+       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;
+
+       DBG("telephony_init() successfully");
+
+       telephony_ready_ind(features, ofono_indicators, BTRH_NOT_SUPPORTED,
+                                                               chld_str);
+
+       return ret;
+}
+
+static void remove_watch(gpointer data)
+{
+       g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data));
+}
+
+static void pending_free(void *data)
+{
+       DBusPendingCall *call = data;
+
+       if (!dbus_pending_call_get_completed(call))
+               dbus_pending_call_cancel(call);
+
+       dbus_pending_call_unref(call);
+}
+
+void telephony_exit(void)
+{
+       DBG("");
+
+       g_free(last_dialed_number);
+       last_dialed_number = NULL;
+
+       if (modem_obj_path)
+               modem_removed(modem_obj_path);
+
+       g_slist_free_full(watches, remove_watch);
+       watches = NULL;
+
+       g_slist_free_full(pending, pending_free);
+       pending = NULL;
+
+       dbus_connection_unref(connection);
+       connection = NULL;
+
+       telephony_deinit();
+}
diff --git a/audio/telephony-tizen.c b/audio/telephony-tizen.c
new file mode 100644 (file)
index 0000000..c5bbc3f
--- /dev/null
@@ -0,0 +1,2261 @@
+/*
+ *  Bluetooth protocol stack for Linux
+ *
+ * Copyright (c) 2000 - 2010 Samsung Electronics Co., Ltd.
+ *
+ * Contact: Chethan  T N <chethan.tn@samsung.com>
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ *//*:Associate with "Bluetooth" */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "telephony.h"
+#include "error.h"
+
+
+/* CSD CALL plugin D-Bus definitions */
+#define CSD_CALL_BUS_NAME      "org.tizen.csd"
+#define CSD_CALL_INTERFACE     "org.tizen.csd.Call"
+#define CSD_CALL_INSTANCE      "org.tizen.csd.Call.Instance"
+#define CSD_CALL_CONFERENCE    "org.tizen.csd.Call.Conference"
+#define CSD_CALL_PATH          "/org/tizen/csd/call"
+#define CSD_CALL_CONFERENCE_PATH "/org/tizen/csd/call/conference"
+#define CSD_CALL_SENDER_PATH   "/org/tizen/csd/call/sender"
+
+/* libcsnet D-Bus definitions */
+#define CSD_CSNET_BUS_NAME     "org.tizen.csd.CSNet"
+#define CSD_CSNET_PATH         "/org/tizen/csd/csnet"
+#define CSD_CSNET_IFACE                "org.tizen.csd.CSNet"
+#define CSD_CSNET_REGISTRATION "org.tizen.csd.CSNet.NetworkRegistration"
+#define CSD_CSNET_OPERATOR     "org.tizen.csd.CSNet.NetworkOperator"
+#define CSD_CSNET_SIGNAL       "org.tizen.csd.CSNet.SignalStrength"
+#define CSD_TELEPHONE_BATTERY  "org.tizen.csd.CSNet.BatteryStrength"
+#define CSD_CSNET_SUBSCRIBER   "org.tizen.csd.CSNet.SubscriberNumber"
+
+
+#define CALL_FLAG_NONE                         0
+#define CALL_FLAG_PRESENTATION_ALLOWED         0x01
+#define CALL_FLAG_PRESENTATION_RESTRICTED      0x02
+
+/* 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
+
+
+#define DBUS_STRUCT_STRING_STRING_UINT (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INVALID))
+
+#define PHONEBOOK_STORE_LIST "(\"SM\",\"ME\",\"DC\",\"MC\",\"RC\")"
+#define PHONEBOOK_STORE_LIST_BLUENME "(\"ME\",\"DC\",\"MC\",\"RC\")"
+#define PREFFERED_MESSAGE_STORAGE_LIST "(\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\")"
+#define PHONEBOOK_CHARACTER_SET_LIST "(\"IRA\",\"GSM\",\"UCS2\")"
+#define PHONEBOOK_CHARACTER_SET_SUPPORTED "\"GSM\""
+
+#define PREFFERED_MESSAGE_STORAGE_MAX 500
+#define CALL_LOG_COUNT_MAX 30
+#define PHONEBOOK_COUNT_MAX 1000
+
+#define PHONEBOOK_PATH_LENGTH 5
+#define PHONEBOOK_NAME_MAX_LENGTH 20
+#define PHONEBOOK_NUMBER_MAX_LENGTH 20
+
+#define PHONEBOOK_READ_RESP_LENGTH 20
+
+typedef struct
+{
+       uint8_t utf_8;
+       uint8_t gsm;
+}GsmUnicodeTable;
+
+
+ //0xC3 charcterset
+const GsmUnicodeTable gsm_unicode_C3[] = {
+       {0xA8,0x04},{0xA9,0x05},{0xB9,0x06},{0xAC,0x07},{0xB2,0x08},
+       {0xB7,0x09},{0x98,0x0B},{0xB8,0x0C},{0x85,0x0E},{0xA5,0x0F},
+       {0x86,0x1C},{0xA6,0x1D},{0x9F,0x1E},{0x89,0x1F},{0x84,0x5B},
+       {0x96,0x5C},{0x91,0x5D},{0x9C,0x5E},{0x80,0x5F},{0xA4,0x7B},
+       {0xB6,0x7C},{0xB1,0x7D},{0xBC,0x7E},{0xA0,0x7F},
+};
+
+//0xCE charcterset
+const GsmUnicodeTable gsm_unicode_CE[] = {
+       {0x85,0x14},{0xA1,0x50},{0x98,0x19},{0xA0,0x16},{0x94,0x10},
+       {0xA6,0x12},{0x93,0x13},{0x9E,0x1A},{0x9B,0x14},{0xA8,0x17},
+       {0xA9,0x15},
+};
+
+#define GSM_UNI_MAX_C3         (sizeof(gsm_unicode_C3)/sizeof(GsmUnicodeTable))
+#define GSM_UNI_MAX_CE         (sizeof(gsm_unicode_CE)/sizeof(GsmUnicodeTable))
+
+
+static DBusConnection *ag_connection = NULL;
+
+static GSList *calls = NULL;
+static GSList *watches = NULL;
+
+static gboolean events_enabled = FALSE;
+static uint32_t callerid = 0;
+
+/* Reference count for determining the call indicator status */
+static GSList *active_calls = NULL;
+
+static struct indicator telephony_ag_indicators[] =
+{
+       { "battchg",    "0-5",  5,      TRUE },
+       /* signal strength in terms of bars */
+       { "signal",     "0-5",  0,      TRUE },
+       { "service",    "0,1",  0,      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",
+       "???"
+};
+
+enum net_registration_status {
+       NETWORK_REG_STATUS_HOME,
+       NETWORK_REG_STATUS_ROAMING,
+       NETWORK_REG_STATUS_OFFLINE,
+       NETWORK_REG_STATUS_SEARCHING,
+       NETWORK_REG_STATUS_NO_SIM,
+       NETWORK_REG_STATUS_POWEROFF,
+       NETWORK_REG_STATUS_POWERSAFE,
+       NETWORK_REG_STATUS_NO_COVERAGE,
+       NETWORK_REG_STATUS_REJECTED,
+       NETWORK_REG_STATUS_UNKOWN
+};
+
+struct csd_call {
+       char *object_path;
+       int status;
+       gboolean originating;
+       gboolean emergency;
+       gboolean on_hold;
+       gboolean conference;
+       char *number;
+       gboolean setup;
+       uint32_t call_id;
+};
+
+static struct {
+       char *operator_name;
+       uint8_t status;
+       int32_t signal_bars;
+} net = {
+       .operator_name = NULL,
+       .status = NETWORK_REG_STATUS_UNKOWN,
+       /* Init as 0 meaning inactive mode. In modem power off state
+        * can be be -1, but we treat all values as 0s regardless
+        * inactive or power off. */
+       .signal_bars = 0,
+};
+
+/* Supported set of call hold operations */
+//static const char *telephony_chld_str = "0,1,1x,2,2x,3,4";
+static const char *telephony_chld_str = "0,1,2,3";
+
+
+static char *subscriber_number = NULL; /* Subscriber number */
+
+
+static struct {
+       char path[PHONEBOOK_PATH_LENGTH];
+       uint32_t type;
+       uint32_t max_size;
+       uint32_t used;
+} ag_pb_info = {
+       .path = {0,},
+       .type = 0,
+       .max_size = 0,
+       .used =0,
+};
+
+
+static struct csd_call *find_call(uint32_t call_id)
+
+{
+       GSList *l;
+
+       for (l = calls; l != NULL; l = l->next) {
+               struct csd_call *call = l->data;
+
+               if(call->call_id == call_id)
+                       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;
+
+       if( NULL != calls ) {
+               for (l = calls; l != NULL; l = l->next) {
+                       struct csd_call *call = l->data;
+
+                       if (call->status == status)
+                               return call;
+               }
+       }
+       return NULL;
+}
+
+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);
+       }
+}
+
+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 int release_conference(void)
+{
+       DBusMessage *msg;
+
+       DBG("+\n");
+
+       msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+                                               CSD_CALL_CONFERENCE_PATH,
+                                               CSD_CALL_INSTANCE,
+                                               "Release");
+       if (!msg) {
+               error("Unable to allocate new D-Bus message");
+               return -ENOMEM;
+       }
+
+       g_dbus_send_message(ag_connection, msg);
+
+       DBG("-\n");
+
+       return 0;
+}
+
+static int reject_call(struct csd_call *call)
+{
+       DBusMessage *msg;
+
+       DBG("+\n");
+
+       msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+                                               call->object_path,
+                                               CSD_CALL_INSTANCE,
+                                               "Reject");
+       if (!msg) {
+               error("Unable to allocate new D-Bus message");
+               return -ENOMEM;
+       }
+       DBG(" Object Path =[ %s] and Call id = [%d]\n", call->object_path, call->call_id);
+       if (!dbus_message_append_args(msg,DBUS_TYPE_UINT32,&call->call_id, DBUS_TYPE_INVALID))
+       {
+
+               DBG("dbus_message_append_args - ERROR\n");
+               dbus_message_unref(msg);
+               return -ENOMEM;
+       }
+
+       gboolean ret = g_dbus_send_message(ag_connection, msg);
+
+       DBG(" ret  = [%d]\n", ret);
+       DBG("-\n");
+
+       return 0;
+
+}
+static int release_call(struct csd_call *call)
+{
+       DBusMessage *msg;
+
+       DBG("+\n");
+
+       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;
+       }
+       DBG(" Object Path =[ %s] and Call id = [%d]\n", call->object_path, call->call_id);
+       if (!dbus_message_append_args(msg,DBUS_TYPE_UINT32,&call->call_id, DBUS_TYPE_INVALID))
+       {
+
+               DBG("dbus_message_append_args - ERROR\n");
+               dbus_message_unref(msg);
+               return -ENOMEM;
+       }
+
+       gboolean ret = g_dbus_send_message(ag_connection, msg);
+
+       DBG(" ret  = [%d]\n", ret);
+       DBG("-\n");
+
+       return 0;
+}
+
+static int dbus_method_call_send(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;
+
+       DBG("+\n");
+
+       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(ag_connection, msg);
+               return 0;
+       }
+
+       if (!dbus_connection_send_with_reply(ag_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_message_unref(msg);
+       DBG("-\n");
+
+       return 0;
+}
+
+static int answer_call(struct csd_call *call)
+{
+       DBusMessage *msg;
+       DBG("+\n");
+
+       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;
+       }
+
+       if (!dbus_message_append_args(msg,DBUS_TYPE_UINT32,&call->call_id, DBUS_TYPE_INVALID))
+       {
+
+               DBG("dbus_message_append_args - ERROR\n");
+               dbus_message_unref(msg);
+               return -ENOMEM;
+       }
+       g_dbus_send_message(ag_connection, msg);
+       DBG("-\n");
+       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;
+}
+
+static void call_set_status(struct csd_call *call, dbus_uint32_t status)
+{
+       dbus_uint32_t prev_status;
+       DBG("+\n");
+
+       int callheld = telephony_get_indicator(telephony_ag_indicators, "callheld");
+
+       prev_status = call->status;
+       DBG("Call %s Call id %d changed from %s to %s", call->object_path, call->call_id,
+               call_status_str[prev_status], call_status_str[status]);
+
+       if (prev_status == status) {
+               DBG("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(telephony_ag_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(telephony_ag_indicators, "callsetup",
+                                               EV_CALLSETUP_ALERTING);
+               break;
+       case CSD_CALL_STATUS_MT_ALERTING:
+               /* Some headsets expect incoming call notification before they
+                * can send ATA command. When call changed status from waiting
+                * to alerting we need to send missing notification. Otherwise
+                * headsets like Nokia BH-108 or BackBeat 903 are unable to
+                * answer incoming call that was previously waiting. */
+               if (prev_status == CSD_CALL_STATUS_WAITING)
+                       telephony_incoming_call_ind(call->number,
+                                               number_type(call->number));
+               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(telephony_ag_indicators,
+                                                       "callheld",
+                                                       EV_CALLHELD_MULTIPLE);
+                       else
+                               telephony_update_indicator(telephony_ag_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(telephony_ag_indicators,
+                                                               "call",
+                                                               EV_CALL_ACTIVE);
+                       /* Upgrade callheld status if necessary */
+                       if (callheld == EV_CALLHELD_ON_HOLD)
+                               telephony_update_indicator(telephony_ag_indicators,
+                                                       "callheld",
+                                                       EV_CALLHELD_MULTIPLE);
+                       telephony_update_indicator(telephony_ag_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(telephony_ag_indicators, "call",
+                                                       EV_CALL_INACTIVE);
+
+               if ((prev_status == CSD_CALL_STATUS_MO_ALERTING) ||
+                       (prev_status == CSD_CALL_STATUS_COMING) ||
+                       (prev_status == CSD_CALL_STATUS_CREATE) ||
+                       (prev_status == CSD_CALL_STATUS_WAITING))
+               {
+                               telephony_update_indicator(telephony_ag_indicators,
+                                                       "callsetup",
+                                                       EV_CALLSETUP_INACTIVE);
+               }
+
+               if( prev_status == CSD_CALL_STATUS_COMING) {
+                       if (!call->originating)
+                               telephony_calling_stopped_ind();
+               }
+               calls = g_slist_remove(calls, call);
+               csd_call_free(call);
+               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(telephony_ag_indicators,
+                                                       "callheld",
+                                                       EV_CALLHELD_MULTIPLE);
+               else
+                       telephony_update_indicator(telephony_ag_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(telephony_ag_indicators,
+                                                       "callheld",
+                                                       EV_CALLHELD_NONE);
+                       return;
+               }
+
+               if (callheld == EV_CALLHELD_MULTIPLE &&
+                               find_call_with_status(CSD_CALL_STATUS_HOLD) &&
+                               !find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+                       telephony_update_indicator(telephony_ag_indicators,
+                                                       "callheld",
+                                                       EV_CALLHELD_ON_HOLD);
+               break;
+       case CSD_CALL_STATUS_SWAP_INITIATED:
+               break;
+       default:
+               error("Unknown call status %u", status);
+               break;
+       }
+       DBG("-\n");
+}
+
+/**
+ * This API shall invoke a dbus method call to bluetooth framework to split the call
+ *
+ * @return             This function returns zero on success.
+ * @param[in]          call    Pointer to the Call information structure.
+ * @param[out]         NONE.
+ */
+static int split_call(struct csd_call *call)
+{
+       DBusMessage *msg;
+
+       DBG("+n");
+
+       if(NULL != call ) {
+               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(ag_connection, msg);
+       }
+       DBG("-\n");
+
+       return 0;
+}
+/**
+ * This API shall invoke a dbus method call to bluetooth framework to swap the calls
+ *
+ * @return             This function returns zero on success.
+ * @param[in]          NONE.
+ * @param[out]         NONE.
+ */
+static int swap_calls(void)
+{
+       DBusMessage *msg;
+       DBG("+\n");
+
+       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(ag_connection, msg);
+       DBG("-\n");
+
+       return 0;
+}
+
+/**
+ * This API shall invoke a dbus method call to bluetooth framework to hold the call
+ *
+ * @return             This function returns zero on success.
+ * @param[in]          call    Pointer to the Call information structure.
+ * @param[out]         NONE.
+ */
+static int hold_call(struct csd_call *call)
+{
+       DBusMessage *msg;
+       DBG("+\n");
+
+       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;
+       }
+       if( NULL != call ) {
+               DBG(" Object Path =[ %s] and Call id = [%d]\n", call->object_path, call->call_id);
+
+               if (!dbus_message_append_args(msg,DBUS_TYPE_UINT32,&call->call_id, DBUS_TYPE_INVALID))
+               {
+
+                       DBG("dbus_message_append_args - ERROR\n");
+                       dbus_message_unref(msg);
+                       return -ENOMEM;
+               }
+
+               g_dbus_send_message(ag_connection, msg);
+       }
+       DBG("-\n");
+
+       return 0;
+}
+
+
+/**
+ * This API shall invoke a dbus method call to bluetooth framework to unhold the call
+ *
+ * @return             This function returns zero on success.
+ * @param[in]          call    Pointer to the Call information structure.
+ * @param[out]         NONE.
+ */
+static int unhold_call(struct csd_call *call)
+{
+       DBusMessage *msg;
+       DBG("+\n");
+
+       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;
+       }
+
+       if( NULL != call ) {
+               DBG(" Object Path =[ %s] and Call id = [%d]\n", call->object_path, call->call_id);
+
+               if (!dbus_message_append_args(msg,DBUS_TYPE_UINT32,&call->call_id, DBUS_TYPE_INVALID))
+               {
+
+                       DBG("dbus_message_append_args - ERROR\n");
+                       dbus_message_unref(msg);
+                       return -ENOMEM;
+               }
+
+               g_dbus_send_message(ag_connection, msg);
+       }
+       DBG("-\n");
+
+       return 0;
+}
+
+/**
+ * This API shall invoke a dbus method call to bluetooth framework to create conmference calls
+ *
+ * @return             This function returns zero on success.
+ * @param[in]          NONE.
+ * @param[out]         NONE.
+ */
+static int create_conference(void)
+{
+       DBusMessage *msg;
+       DBG("+\n");
+
+       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(ag_connection, msg);
+       DBG("-\n");
+
+       return 0;
+}
+/**
+ * This API shall invoke a dbus method call to bluetooth framework to transfer the call
+ *
+ * @return             This function returns zero on success.
+ * @param[in]          NONE.
+ * @param[out]         NONE.
+ */
+static int call_transfer(void)
+{
+       DBusMessage *msg;
+       DBG("+\n");
+
+       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(ag_connection, msg);
+       DBG("-\n");
+
+       return 0;
+}
+
+static void telephony_chld_reply(DBusPendingCall *call, void *data)
+{
+       DBusMessage *reply = dbus_pending_call_steal_reply(call);
+       DBusError derr;
+
+       DBG("redial_reply");
+
+       dbus_error_init(&derr);
+       if (!dbus_set_error_from_message(&derr, reply)) {
+               DBG("chld  reply: cmd is valid");
+               telephony_dial_number_rsp(data, CME_ERROR_NONE);
+               goto done;
+       }
+
+       DBG("chld_reply reply: %s", derr.message);
+
+       dbus_error_free(&derr);
+       telephony_dial_number_rsp(data, CME_ERROR_AG_FAILURE);
+
+done:
+       dbus_message_unref(reply);
+}
+
+void telephony_call_hold_req(void *telephony_device, const char *cmd)
+{
+       const char *idx;
+       struct csd_call *call;
+       int err = 0;
+       DBG("+\n");
+
+       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;
+#if 0
+       switch (cmd[0]) {
+       case '0':
+               if (find_call_with_status(CSD_CALL_STATUS_WAITING))
+                       foreach_call_with_status(CSD_CALL_STATUS_WAITING,
+                                                               release_call);
+               else
+                       foreach_call_with_status(CSD_CALL_STATUS_HOLD,
+                                                               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);
+               }
+               else {
+                       struct csd_call *held;
+                       held = find_call_with_status(CSD_CALL_STATUS_HOLD);
+                       if(held)
+                               err = unhold_call(held);
+               }
+               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:
+               DBG("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);
+#else
+       idx = &cmd[0];
+       int chld_value = strtol(idx, NULL, 0);
+
+       err = dbus_method_call_send(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+                                       CSD_CALL_INSTANCE, "Threeway",
+                                       telephony_chld_reply, telephony_device,
+                                       DBUS_TYPE_INT32, &chld_value,
+                                       DBUS_TYPE_INVALID);
+
+       if (err)
+               telephony_call_hold_rsp(telephony_device,
+                                       CME_ERROR_AG_FAILURE);
+#endif
+       DBG("-\n");
+}
+
+static void handle_incoming_call(DBusMessage *msg)
+{
+
+       const char *number, *call_path;
+       struct csd_call *call;
+       uint32_t call_id;
+
+       DBG("+\n");
+       DBG("handle_incoming_call()\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_OBJECT_PATH, &call_path,
+                                       DBUS_TYPE_STRING, &number,
+                                       DBUS_TYPE_UINT32, &call_id,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in Call.Coming() signal");
+               return;
+       }
+
+       call = g_new0(struct csd_call, 1);
+       call->object_path = g_strdup(call_path);
+       call->call_id = call_id;
+       call->number = g_strdup(number);
+       calls = g_slist_append(calls, call);
+
+       DBG("Incoming call to %s from number %s call id %d", call_path, number, call_id);
+
+       if (find_call_with_status(CSD_CALL_STATUS_ACTIVE) ||
+                       find_call_with_status(CSD_CALL_STATUS_HOLD)){
+               telephony_call_waiting_ind(call->number,
+                                               number_type(call->number));
+               call_set_status(call,CSD_CALL_STATUS_WAITING);
+               }else {
+               telephony_incoming_call_ind(call->number,
+                                               number_type(call->number));
+
+               call_set_status(call,CSD_CALL_STATUS_COMING);
+               }
+       telephony_update_indicator(telephony_ag_indicators, "callsetup",
+                                       EV_CALLSETUP_INCOMING);
+       DBG("-\n");
+}
+
+static void update_registration_status(uint8_t status)
+{
+       uint8_t new_status;
+       DBG("+\n");
+
+       //new_status = str2status(status);
+       new_status = status;
+
+       if (net.status == new_status)
+               return;
+
+       switch (new_status) {
+       case NETWORK_REG_STATUS_HOME:
+               telephony_update_indicator(telephony_ag_indicators, "roam",
+                                                       EV_ROAM_INACTIVE);
+               if (net.status > NETWORK_REG_STATUS_ROAMING)
+                       telephony_update_indicator(telephony_ag_indicators,
+                                                       "service",
+                                                       EV_SERVICE_PRESENT);
+               break;
+       case NETWORK_REG_STATUS_ROAMING:
+               telephony_update_indicator(telephony_ag_indicators, "roam",
+                                                       EV_ROAM_ACTIVE);
+               if (net.status > NETWORK_REG_STATUS_ROAMING)
+                       telephony_update_indicator(telephony_ag_indicators,
+                                                       "service",
+                                                       EV_SERVICE_PRESENT);
+               break;
+       case NETWORK_REG_STATUS_OFFLINE:
+       case NETWORK_REG_STATUS_SEARCHING:
+       case NETWORK_REG_STATUS_NO_SIM:
+       case NETWORK_REG_STATUS_POWEROFF:
+       case NETWORK_REG_STATUS_POWERSAFE:
+       case NETWORK_REG_STATUS_NO_COVERAGE:
+       case NETWORK_REG_STATUS_REJECTED:
+       case NETWORK_REG_STATUS_UNKOWN:
+               if (net.status < NETWORK_REG_STATUS_OFFLINE)
+                       telephony_update_indicator(telephony_ag_indicators,
+                                                       "service",
+                                                       EV_SERVICE_NONE);
+               break;
+       }
+
+       net.status = new_status;
+
+       DBG("-\n");
+}
+
+static void update_signal_strength(int32_t signal_bars)
+{
+       DBG("+\n");
+
+       if (signal_bars < 0) {
+               DBG("signal strength smaller than expected: %d < 0",
+                                                               signal_bars);
+               signal_bars = 0;
+       } else if (signal_bars > 5) {
+               DBG("signal strength greater than expected: %d > 5",
+                                                               signal_bars);
+               signal_bars = 5;
+       }
+
+       if (net.signal_bars == signal_bars)
+               return;
+
+       telephony_update_indicator(telephony_ag_indicators, "signal", signal_bars);
+
+       net.signal_bars = signal_bars;
+
+       DBG("-\n");
+}
+
+static void update_battery_strength(int32_t battery_level)
+{
+       DBG("+\n");
+       int current_battchg = telephony_get_indicator(telephony_ag_indicators, "battchg");
+
+       if (battery_level < 0) {
+               DBG("Battery strength smaller than expected: %d < 0",
+                                                               battery_level);
+               battery_level = 0;
+       } else if (battery_level > 5) {
+               DBG("Battery strength greater than expected: %d > 5",
+                                                               battery_level);
+               battery_level = 5;
+       }
+       if( current_battchg == battery_level)
+               return;
+
+       telephony_update_indicator(telephony_ag_indicators, "battchg", battery_level);
+
+
+       DBG("-\n");
+}
+
+
+static void handle_outgoing_call(DBusMessage *msg)
+{
+       const char *number, *call_path;
+       struct csd_call *call;
+       uint32_t call_id;
+
+       DBG("+\n");
+       DBG("handle_outgoing_call()\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_OBJECT_PATH, &call_path,
+                                       DBUS_TYPE_STRING, &number,
+                                       DBUS_TYPE_UINT32, &call_id,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in Call.Coming() signal");
+               return;
+       }
+
+       call = g_new0(struct csd_call, 1);
+       call->object_path = g_strdup(call_path);
+       call->call_id = call_id;
+       call->number = g_strdup(number);
+       calls = g_slist_append(calls, call);
+
+       DBG("Outgoing call to %s from number %s call id %d", call_path, number, call_id);
+
+       call_set_status(call,CSD_CALL_STATUS_CREATE);
+
+       telephony_update_indicator(telephony_ag_indicators, "callsetup",
+                                       EV_CALLSETUP_OUTGOING);
+       DBG("-\n");
+}
+
+static void handle_create_requested(DBusMessage *msg)
+{
+       DBG("+\n");
+       DBG("handle_create_requested()\n");
+       DBG("-\n");
+}
+
+static void handle_call_status(DBusMessage *msg, const char *call_path)
+{
+       struct csd_call *call;
+       dbus_uint32_t status, call_id;
+       DBG("+\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_UINT32, &status,
+                                       DBUS_TYPE_UINT32, &call_id,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected paramters in Instance.CallStatus() signal");
+               return;
+       }
+
+       DBG("status = [%d] and call_id = [%d]\n",status, call_id);
+       call = find_call(call_id);
+       if (!call) {
+/*
+       call_path is equal to CSD_CALL_PATH then we should update the call list
+       since the call_path is sent from native AG applicaton
+
+       Added for updation of the call status if the call is not added inthe call list
+*/
+               if (g_str_equal(CSD_CALL_PATH, call_path)) {
+                       call = g_new0(struct csd_call, 1);
+                       call->object_path = g_strdup(call_path);
+                       call->call_id = call_id;
+                       calls = g_slist_append(calls, call);
+               }
+       }
+
+       if (status > 16) {
+               error("Invalid call status %u", status);
+               return;
+       }
+
+       call_set_status(call, status);
+       DBG("-\n");
+}
+
+static void update_operator_name(const char *name)
+{
+       DBG("+\n");
+       if (name == NULL)
+               return;
+
+       g_free(net.operator_name);
+       net.operator_name = g_strndup(name, 16);
+       DBG("-\n");
+}
+
+static void update_subscriber_number(const char *number)
+{
+       DBG("+\n");
+       if (number == NULL)
+               return;
+
+       g_free(subscriber_number);
+       subscriber_number = g_strdup(number);
+       DBG("-\n");
+}
+
+static void handle_conference(DBusMessage *msg, gboolean joined)
+{
+       DBG("+\n");
+       DBG("handle_conference()\n");
+       DBG("-\n");
+}
+
+static void handle_registration_changed(DBusMessage *msg)
+{
+       uint8_t status;
+
+       DBG("+\n");
+       DBG("handle_registration_changed()\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_BYTE, &status,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in RegistrationChanged");
+               return;
+       }
+
+       update_registration_status((uint8_t) status);
+       DBG("-\n");
+}
+
+static void handle_operator_name_changed(DBusMessage *msg)
+{
+       DBG("+\n");
+
+       const char *name;
+       DBG("handle_operator_name_changed()\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &name,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in OperatorNameChanged");
+               return;
+       }
+
+       update_operator_name(name);
+       DBG("-\n");
+}
+
+static void handle_signal_bars_changed(DBusMessage *msg)
+{
+       DBG("+\n");
+
+       int32_t signal_bars;
+       DBG("handle_signal_bars_changed()\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_INT32, &signal_bars,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in SignalBarsChanged");
+               return;
+       }
+
+       update_signal_strength(signal_bars);
+       DBG("-\n");
+}
+
+static void handle_battery_bars_changed(DBusMessage *msg)
+{
+       DBG("+\n");
+
+       int32_t battery_level;
+       DBG("handle_signal_bars_changed()\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_INT32, &battery_level,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in SignalBarsChanged");
+               return;
+       }
+
+       update_battery_strength(battery_level);
+       DBG("-\n");
+}
+
+static void handle_subscriber_number_changed(DBusMessage *msg)
+{
+       DBG("+\n");
+
+       const char *number;
+       DBG("handle_subscriber_number_changed()\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &number,
+                                       DBUS_TYPE_INVALID)) {
+               error("Unexpected parameters in SubscriberNumberChanged");
+               return;
+       }
+
+       update_subscriber_number(number);
+       DBG("-\n");
+}
+static gboolean signal_filter(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       DBG("+\n");
+
+       const char *path = dbus_message_get_path(msg);
+
+       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, CSD_CSNET_REGISTRATION,
+                               "RegistrationChanged"))
+               handle_registration_changed(msg);
+       else if (dbus_message_is_signal(msg, CSD_CSNET_OPERATOR,
+                               "OperatorNameChanged"))
+               handle_operator_name_changed(msg);
+       else if (dbus_message_is_signal(msg, CSD_CSNET_SIGNAL,
+                               "SignalBarsChanged"))
+               handle_signal_bars_changed(msg);
+       else if (dbus_message_is_signal(msg, CSD_TELEPHONE_BATTERY,
+                               "BatteryBarsChanged"))
+               handle_battery_bars_changed(msg);
+       else if (dbus_message_is_signal(msg, CSD_CSNET_SUBSCRIBER,
+                               "SubscriberNumberChanged"))
+               handle_subscriber_number_changed(msg);
+
+       DBG("-\n");
+       return TRUE;
+}
+
+static void dbus_add_watch(const char *sender, const char *path,
+                               const char *interface, const char *member)
+{
+       guint watch;
+
+       watch = g_dbus_add_signal_watch(ag_connection, sender, path, interface,
+                                       member, signal_filter, NULL, NULL);
+
+       watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
+}
+
+static const char *telephony_memory_dial_lookup(int location)
+{
+       /*memory dial not supported*/
+       if (location == 1)
+               return NULL;
+       else
+               return NULL;
+}
+
+/*API's that shall be ported*/
+
+int telephony_init(void)
+{
+       //const char *battery_cap = "battery";
+       uint32_t features = AG_FEATURE_EC_ANDOR_NR |
+                               AG_FEATURE_REJECT_A_CALL |
+                               AG_FEATURE_ENHANCED_CALL_STATUS |
+                               AG_FEATURE_THREE_WAY_CALLING;
+       int i;
+
+       DBG("");
+
+       ag_connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+       dbus_add_watch(NULL, CSD_CALL_PATH, CSD_CALL_INTERFACE, NULL);
+       dbus_add_watch(NULL, CSD_CALL_PATH, CSD_CALL_INSTANCE, NULL);
+       dbus_add_watch(NULL, CSD_CALL_PATH, CSD_CALL_CONFERENCE, NULL);
+       dbus_add_watch(NULL, CSD_CSNET_PATH, CSD_CSNET_REGISTRATION, "RegistrationChanged");
+       dbus_add_watch(NULL, CSD_CSNET_PATH, CSD_CSNET_OPERATOR, "OperatorNameChanged");
+       dbus_add_watch(NULL, CSD_CSNET_PATH, CSD_CSNET_SIGNAL, "SignalBarsChanged");
+       dbus_add_watch(NULL, CSD_CSNET_PATH, CSD_TELEPHONE_BATTERY, "BatteryBarsChanged");
+       dbus_add_watch(NULL, CSD_CSNET_PATH, CSD_CSNET_SUBSCRIBER, "SubscriberNumberChanged");
+
+       /* Reset indicators */
+       for (i = 0; telephony_ag_indicators[i].desc != NULL; i++) {
+               if (g_str_equal(telephony_ag_indicators[i].desc, "battchg"))
+                       telephony_ag_indicators[i].val = 5;
+               else
+                       telephony_ag_indicators[i].val = 0;
+       }
+
+       /*Initializatoin of the indicators*/
+       telephony_ready_ind(features, telephony_ag_indicators, BTRH_NOT_SUPPORTED,
+                                                               telephony_chld_str);
+
+       return 0;
+}
+
+static void remove_watch(gpointer data)
+{
+       g_dbus_remove_watch(ag_connection, GPOINTER_TO_UINT(data));
+}
+
+void telephony_exit(void)
+{
+       DBG("");
+
+       g_free(net.operator_name);
+       net.operator_name = NULL;
+
+       g_free(subscriber_number);
+       subscriber_number = NULL;
+
+       net.status = NETWORK_REG_STATUS_UNKOWN;
+       net.signal_bars = 0;
+
+       g_slist_free(active_calls);
+       active_calls = NULL;
+
+       g_slist_foreach(calls, (GFunc) csd_call_free, NULL);
+       g_slist_free(calls);
+       calls = NULL;
+
+       g_slist_foreach(watches, (GFunc) remove_watch, NULL);
+       g_slist_free(watches);
+       watches = NULL;
+
+       dbus_connection_unref(ag_connection);
+       ag_connection = NULL;
+
+       telephony_deinit();
+}
+
+void telephony_device_connected(void *telephony_device)
+{
+}
+
+void telephony_device_disconnected(void *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)
+{
+       telephony_response_and_hold_rsp(telephony_device,
+                                               CME_ERROR_NOT_SUPPORTED);
+}
+
+static void telephony_dial_number_reply(DBusPendingCall *call, void *data)
+{
+       DBusMessage *reply = dbus_pending_call_steal_reply(call);
+       DBusError derr;
+
+       DBG("redial_reply");
+
+       dbus_error_init(&derr);
+       if (!dbus_set_error_from_message(&derr, reply)) {
+               DBG("hfg  reply: dial done  successfully");
+               telephony_dial_number_rsp(data, CME_ERROR_NONE);
+               goto done;
+       }
+
+       DBG("dial_reply reply: %s", derr.message);
+
+       dbus_error_free(&derr);
+       telephony_dial_number_rsp(data, CME_ERROR_AG_FAILURE);
+
+done:
+       dbus_message_unref(reply);
+}
+
+void telephony_last_dial_number_req(void *telephony_device )
+{
+       int ret;
+
+       ret = dbus_method_call_send(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+                               CSD_CALL_INTERFACE, "DialLastNo",
+                               telephony_dial_number_reply, telephony_device,
+                               DBUS_TYPE_INVALID);
+       if (ret < 0) {
+               telephony_dial_number_rsp(telephony_device,
+                                               CME_ERROR_AG_FAILURE);
+       }
+}
+void telephony_dial_number_req(void *telephony_device, const char *number)
+{
+       uint32_t flags = callerid;
+       int ret;
+
+       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] == '>') {
+               int location = strtol(&number[1], NULL, 0);
+
+               ret = dbus_method_call_send(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+                                       CSD_CALL_INTERFACE, "DialMemory",
+                                       telephony_dial_number_reply, telephony_device,
+                                       DBUS_TYPE_INT32, &location,
+                                       DBUS_TYPE_INVALID);
+               if (ret < 0) {
+                       telephony_dial_number_rsp(telephony_device,
+                                                       CME_ERROR_AG_FAILURE);
+               }
+               return;
+       }
+
+       ret = dbus_method_call_send(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+                               CSD_CALL_INTERFACE, "DialNo",
+                               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_terminate_call_req(void *telephony_device)
+{
+       struct csd_call *call;
+       struct csd_call *alerting;
+       int err;
+
+       DBG("+\n");
+
+       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 (NULL != (alerting = find_call_with_status(CSD_CALL_STATUS_CREATE)))
+               err = reject_call(alerting);
+       else if (NULL != (alerting = find_call_with_status(CSD_CALL_STATUS_MO_ALERTING)))
+               err = reject_call(alerting);
+       else if (NULL != (alerting = find_call_with_status(CSD_CALL_STATUS_COMING)))
+               err = reject_call(alerting);
+       else if (call->conference)
+               err = release_conference();
+       else
+               err = release_call(call);
+
+       if (err < 0)
+               telephony_terminate_call_rsp(telephony_device,
+                                               CME_ERROR_AG_FAILURE);
+       else
+               telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
+       DBG("-\n");
+
+}
+
+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);
+
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+       struct csd_call *active, *waiting;
+       int err;
+
+       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);
+}
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+       telephony_last_dial_number_req(telephony_device);
+}
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+       int ret;
+       char buf[2] = { tone, '\0' }, *buf_ptr = buf;
+
+       ret = dbus_method_call_send(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);
+}
+
+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;
+               /*
+                * PROCEEDING is followed by WAITING CSD status, therefore
+                * second incoming call status indication is set immediately
+                * to waiting.
+                */
+               if (g_slist_length(active_calls) > 0)
+                       return CALL_STATUS_WAITING;
+
+               return CALL_STATUS_INCOMING;
+       case CSD_CALL_STATUS_COMING:
+               if (g_slist_length(active_calls) > 0)
+                       return CALL_STATUS_WAITING;
+
+               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;
+
+       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);
+}
+
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
+{
+       telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+       telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+       if (subscriber_number)
+               telephony_subscriber_number_ind(subscriber_number,
+                                               number_type(subscriber_number),
+                                               SUBSCRIBER_SERVICE_VOICE);
+       telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_list_phonebook_store(void *telephony_device)
+{
+       /*
+               For Blue & Me car kit we may have to add  the patch here( similar to the patch done in H1 and H2 )
+       */
+       telephony_list_phonebook_store_rsp(telephony_device, PHONEBOOK_STORE_LIST,CME_ERROR_NONE);
+}
+
+static int get_phonebook_count(const char *path, uint32_t *max_size, uint32_t *used)
+{
+       DBusConnection *conn;
+       DBusMessageIter iter;
+       DBusMessageIter value;
+       DBusMessage *message, *reply;
+       DBusError error;
+
+       conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (!conn) {
+               DBG("Can't get on system bus");
+               return -1;
+       }
+
+       message = dbus_message_new_method_call("org.bluez.pb_agent",
+                                                                               "/org/bluez/pb_agent",
+                                                                               "org.bluez.PbAgent",
+                                                                               "GetCallLogSize");
+       if (!message) {
+               DBG("Can't allocate new message");
+               return -1;
+       }
+       dbus_message_iter_init_append(message, &iter);
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &path);
+
+       dbus_message_iter_close_container(&iter, &value);
+       dbus_error_init(&error);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                       message, -1, &error);
+
+       if (!reply) {
+               if (dbus_error_is_set(&error) == TRUE) {
+                       DBG("%s", error.message);
+                       dbus_error_free(&error);
+               } else {
+                       DBG("Failed to get contacts");
+               }
+               return -1;
+       }
+
+       if (!dbus_message_get_args(reply, &error,
+                               DBUS_TYPE_UINT32, used,
+                               DBUS_TYPE_INVALID)) 
+       {
+                       DBG("Can't get reply arguments\n");
+                       if (dbus_error_is_set(&error)) {
+                               DBG("%s\n", error.message);
+                               dbus_error_free(&error);
+                       }
+                       return -1;
+       }
+
+       if( (g_strcmp0(path, "SM") == 0 ) ||  (g_strcmp0(path, "ME") == 0)) {
+               *max_size = PHONEBOOK_COUNT_MAX;
+               
+               if( *used > PHONEBOOK_COUNT_MAX)
+                       *used = PHONEBOOK_COUNT_MAX;
+       }
+       if( (g_strcmp0(path, "DC") == 0) || (g_strcmp0(path, "MC") == 0) ||  (g_strcmp0(path, "RC") == 0) ) {
+               *max_size = CALL_LOG_COUNT_MAX;
+
+               if( *used > CALL_LOG_COUNT_MAX)
+                       *used = CALL_LOG_COUNT_MAX;
+       }
+       
+       dbus_message_unref(message);
+       dbus_message_unref(reply);
+       dbus_connection_unref(conn);
+
+       return 0;
+}
+
+void telephony_read_phonebook_store(void *telephony_device)
+{
+       if( '\0' != ag_pb_info.path[0] ) {
+               /* Check the phone path ag_pb_info.path[] to which path it was set. If the path is NULL then set to "SM"
+                    and get the max_size and used counts from the phonebook type through dbus call to pbap agent
+               */
+               if(!get_phonebook_count(ag_pb_info.path, &ag_pb_info.max_size, &ag_pb_info.used) ) {
+                                       telephony_read_phonebook_store_rsp(telephony_device, ag_pb_info.path, 
+                                                                                                       ag_pb_info.max_size, ag_pb_info.used,CME_ERROR_NONE);
+               } else {
+                       telephony_read_phonebook_store_rsp(telephony_device, ag_pb_info.path, 
+                                                                                       ag_pb_info.max_size, ag_pb_info.used,CME_ERROR_AG_FAILURE);
+               }
+               
+       }
+}
+
+void telephony_set_phonebook_store(void *telephony_device, const char *path)
+{
+       if ( NULL != path ) {
+               DBG("set phonebook type to [%s]\n",path);
+               strncpy(ag_pb_info.path, path, 4 );
+       }
+}
+
+void telephony_read_phonebook_attributes(void *telephony_device)
+{
+       uint32_t total_count = 0;
+       uint32_t used_count = 0;
+       
+       if( '\0' != ag_pb_info.path[0] ) {
+               /* Check the phone path ag_pb_info.path[] to which path it was set. If the path is NULL then set to "SM"
+                    and get the max_size and used counts from the phonebook type through dbus call to pbap agent
+               */
+               telephony_read_phonebook_attributes_rsp(telephony_device, 
+                                                                                               ag_pb_info.max_size,    PHONEBOOK_NUMBER_MAX_LENGTH,
+                                                                                               PHONEBOOK_NAME_MAX_LENGTH, CME_ERROR_NONE);
+       }
+}
+
+static int convert_utf8_gsm(uint8_t ascii, uint8_t utf_8, uint8_t *gsm)
+{
+       uint32_t i = 0;
+
+       if( ascii == 0xC3 )
+       {
+               for(i =0; i< GSM_UNI_MAX_C3 ; i++ )
+               {
+                       if( gsm_unicode_C3[i].utf_8 == utf_8 )
+                       {
+                               *gsm = gsm_unicode_C3[i].gsm;
+                               return 0;
+                       }
+               }
+       }
+       else if (ascii == 0xCE)
+       {
+               for(i =0; i< GSM_UNI_MAX_CE ; i++ )
+               {
+                       if( gsm_unicode_CE[i].utf_8 == utf_8 )
+                       {
+                               *gsm = gsm_unicode_CE[i].gsm;
+                               return 0;
+                       }
+               }
+       }
+}
+
+static void get_unicode_string( const char *name, char *unicodename)
+{
+       if( NULL != name || NULL!= unicodename)
+       {
+               uint32_t len = strlen(name);
+               if(len > 0 ) {
+                       int x = 0;
+                       int y =0;
+                       if( len > 20 )
+                               len = 20;
+                       for( x =0, y = 0 ; x < len ; x++, y++ ) {
+                               if(x < (len-1)){
+                                       if( convert_utf8_gsm(name[x],name[x+1] , (uint8_t *)&unicodename[y]) )
+                                       {
+                                               x++;
+                                               continue;
+                                       }
+                               }
+                                       
+                               if( name[x] == '_' )
+                               {
+                                        unicodename[y] = ' ';
+                                        continue;
+                               }
+
+                               unicodename[y] = name[x];                       
+                       }
+               }
+       }
+       return; 
+}
+
+static int send_read_phonebook_resp(void *telephony_device, int32_t index, const char *name, const char *number )
+{
+       gchar *msg = NULL;
+       int ret = -1;
+
+       msg =  g_new0(gchar, PHONEBOOK_NAME_MAX_LENGTH + PHONEBOOK_NUMBER_MAX_LENGTH + PHONEBOOK_READ_RESP_LENGTH + 3);
+
+       if( NULL != msg )
+       {
+               char nm[PHONEBOOK_NAME_MAX_LENGTH +1] = {0,};
+               char nb[PHONEBOOK_NAME_MAX_LENGTH +1] = {0,};
+
+               get_unicode_string(name,nm);
+               get_unicode_string(number,nb);
+
+               snprintf(msg, PHONEBOOK_NAME_MAX_LENGTH + PHONEBOOK_NUMBER_MAX_LENGTH + PHONEBOOK_READ_RESP_LENGTH + 3,
+                               "%d,\"%s\",0,\"%s\"",index, nb,nm);
+
+               ret = telephony_read_phonebook_rsp(telephony_device,msg,CME_ERROR_NONE);
+
+               g_free(msg);
+       }
+       return ret;
+}
+
+static int get_phonebook_list(void *telephony_device, const char* path, int32_t start_index, int32_t end_index )
+{
+       DBusConnection *conn;
+       DBusMessage *message, *reply;
+       DBusError error;
+       DBusMessageIter iter, iter_struct, entry;
+       int32_t idx =0;
+       if( (start_index > ag_pb_info.max_size) || (start_index <=0) || (start_index > PHONEBOOK_COUNT_MAX) ) {
+               return -1;
+       }
+
+       if(end_index > ag_pb_info.max_size )
+               end_index = PHONEBOOK_COUNT_MAX ;
+
+
+       conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (!conn) {
+               DBG("Can't get on system bus");
+               return -1;
+       }
+
+       message = dbus_message_new_method_call("org.bluez.pb_agent",
+                                                                               "/org/bluez/pb_agent",
+                                                                               "org.bluez.PbAgent",
+                                                                               "GetPhonebookList");
+       if (!message) {
+               DBG("Can't allocate new message");
+               return -1;
+       }
+       dbus_error_init(&error);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                       message, -1, &error);
+
+       if (!reply) {
+               if (dbus_error_is_set(&error) == TRUE) {
+                       DBG("%s", error.message);
+                       dbus_error_free(&error);
+               } else {
+                       DBG("Failed to get contacts");
+               }
+               return -1;
+       }
+
+       dbus_message_iter_init(reply, &iter);
+       dbus_message_iter_recurse(&iter, &iter_struct);
+
+       idx = start_index;
+       while (dbus_message_iter_get_arg_type(&iter_struct) == DBUS_TYPE_STRUCT) {
+                               dbus_message_iter_recurse(&iter_struct, &entry);
+               const char *name = NULL;
+               const char *tel = NULL;
+               uint32_t handle = 0;
+               
+               dbus_message_iter_get_basic(&entry, &name);
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_get_basic(&entry, &tel);
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_get_basic(&entry, &handle);
+
+               DBG("[%d] handle:%d name:%s tel:%s]\n", handle, name, tel);
+
+               /*form the packet and sent to the remote headset*/
+               if( -1 == end_index ) {
+                       if(send_read_phonebook_resp(telephony_device, start_index, name, tel ) )
+                               DBG("send_read_phonebook_resp - ERROR\n");
+                       break;
+               }
+               else
+               {
+                       if( idx >= start_index || idx <= end_index )
+                       {
+                               if(send_read_phonebook_resp(telephony_device, idx, name, tel ) ) {
+                                       DBG("send_read_phonebook_resp - ERROR\n");
+                                       telephony_read_phonebook_rsp(telephony_device,NULL, CME_ERROR_AG_FAILURE);
+
+                                       dbus_message_unref(message);
+                                       dbus_message_unref(reply);
+                                       dbus_connection_unref(conn);
+
+                                       return -1;
+                               }
+                               idx++;
+                       }
+               }
+               dbus_message_iter_next(&iter_struct);
+       }
+
+       telephony_read_phonebook_rsp(telephony_device,NULL, CME_ERROR_NONE);
+
+       dbus_message_unref(message);
+       dbus_message_unref(reply);
+       dbus_connection_unref(conn);
+
+       /*Process the List and send response*/
+       return 0;
+}
+
+static int get_call_log_list(void *telephony_device, char* path , int32_t start_index, int32_t end_index )
+{
+       DBusConnection *conn;
+       DBusMessage *message = NULL, *reply;
+       DBusError error;
+       DBusMessageIter iter, iter_struct, entry;
+       int32_t idx =0;
+
+       if( (start_index > ag_pb_info.max_size) || (start_index <=0) || (start_index > CALL_LOG_COUNT_MAX) ) {
+               return -1;
+       }
+
+       if(end_index > ag_pb_info.max_size )
+               end_index = CALL_LOG_COUNT_MAX ;
+
+
+       conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (!conn) {
+               DBG("Can't get on system bus");
+               return -1;
+       }
+
+       if( g_strcmp0(ag_pb_info.path, "DC") == 0) {
+               message = dbus_message_new_method_call("org.bluez.pb_agent",
+                                                                                       "/org/bluez/pb_agent",
+                                                                                       "org.bluez.PbAgent",
+                                                                                       "GetOutgoingCallsList");
+       }else if(g_strcmp0(ag_pb_info.path, "MC") == 0) {
+               message = dbus_message_new_method_call("org.bluez.pb_agent",
+                                                                                       "/org/bluez/pb_agent",
+                                                                                       "org.bluez.PbAgent",
+                                                                                       "GetMissedCallsList");
+       }else if(g_strcmp0(ag_pb_info.path, "RC") == 0) {
+               message = dbus_message_new_method_call("org.bluez.pb_agent",
+                                                                                       "/org/bluez/pb_agent",
+                                                                                       "org.bluez.PbAgent",
+                                                                                       "GetIncomingCallsList");
+       }
+       if (!message) {
+               DBG("Can't allocate new message");
+               dbus_connection_unref(conn);
+               return -1;
+       }
+       dbus_error_init(&error);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                       message, -1, &error);
+
+       if (!reply) {
+               if (dbus_error_is_set(&error) == TRUE) {
+                       DBG("%s", error.message);
+                       dbus_error_free(&error);
+               } else {
+                       DBG("Failed to get contacts");
+               }
+               dbus_message_unref(message);
+               dbus_connection_unref(conn);
+               return -1;
+       }
+
+       dbus_message_iter_init(reply, &iter);
+       dbus_message_iter_recurse(&iter, &iter_struct);
+
+       idx = start_index;
+       while (dbus_message_iter_get_arg_type(&iter_struct) == DBUS_TYPE_STRUCT) {
+                               dbus_message_iter_recurse(&iter_struct, &entry);
+               const char *name = NULL;
+               const char *tel = NULL;
+               uint32_t handle = 0;
+
+               dbus_message_iter_get_basic(&entry, &name);
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_get_basic(&entry, &tel);
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_get_basic(&entry, &handle);
+
+               DBG("[%d] handle:%d name:%s tel:%s]\n", handle, name, tel);
+
+               /*form the packet and sent to the remote headset*/
+               if( -1 == end_index ) {
+                       if(send_read_phonebook_resp(telephony_device, start_index, name, tel ) )
+                               DBG("send_read_phonebook_resp - ERROR\n");
+                       break;
+               }
+               else
+               {
+                       if( idx >= start_index || idx <= end_index )
+                       {
+                               /* Need to form the time stamp pkt  also */
+                               if(send_read_phonebook_resp(telephony_device, idx, name, tel ) ) {
+                                       DBG("send_read_phonebook_resp - ERROR\n");
+                                       telephony_read_phonebook_rsp(telephony_device,NULL, CME_ERROR_AG_FAILURE);
+
+                                       dbus_message_unref(message);
+                                       dbus_message_unref(reply);
+                                       dbus_connection_unref(conn);
+
+                                       return -1;
+                               }
+                               idx++;
+                       }
+               }
+               dbus_message_iter_next(&iter_struct);
+       }
+
+       telephony_read_phonebook_rsp(telephony_device,NULL, CME_ERROR_NONE);
+
+       dbus_message_unref(message);
+       dbus_message_unref(reply);
+       dbus_connection_unref(conn);
+
+       /*Process the List and send response*/
+       return 0;
+
+}
+void telephony_read_phonebook(void *telephony_device, const char *cmd)
+{
+       char *ptr = 0;
+
+       if( NULL != cmd ) {
+               int32_t start_index;
+               int32_t end_index;
+               ptr = (char*) strchr(cmd,(int32_t)',');
+               if( NULL == ptr ) {
+                       start_index = strtol(cmd, NULL, 0);
+                       end_index = -1;
+                       DBG("telephony_read_phonebook:start_index = [%d] \n", start_index);
+               } else {
+                       ptr++;
+                       start_index = strtol(cmd, NULL, 0);
+                       end_index = strtol(ptr, NULL, 0);
+                       DBG("telephony_read_phonebook:start_index = [%d], end_index = [%d] \n", start_index,end_index);
+               }
+
+               if( (g_strcmp0(ag_pb_info.path, "SM") == 0 ) ||  (g_strcmp0(ag_pb_info.path, "ME") == 0 )) {
+                       if( get_phonebook_list(telephony_device, ag_pb_info.path, start_index, end_index) ) {
+                               telephony_read_phonebook_rsp(telephony_device,NULL,CME_ERROR_AG_FAILURE);
+                               return;
+                       }
+               }
+               if( (g_strcmp0(ag_pb_info.path, "DC") == 0 ) || (g_strcmp0(ag_pb_info.path, "MC") == 0 ) ||
+                                                                                                       (g_strcmp0(ag_pb_info.path, "RC") == 0) ) {
+                       if(get_call_log_list(telephony_device,ag_pb_info.path, start_index, end_index) ) {
+                               telephony_read_phonebook_rsp(telephony_device,NULL,CME_ERROR_AG_FAILURE);
+                               return;
+                       }
+               }
+               
+               /*
+               Using the start and end index get the contact list from the pbap agent and send the data 
+               to remote headset.
+               */
+       }
+ }
+
+void telephony_find_phonebook_entry_properties(void *telephony_device)
+{
+       telephony_find_phonebook_entry_properties_rsp( telephony_device, 
+                                                                                       20,
+                                                                                       128,
+                                                                                       CME_ERROR_NONE );
+
+}
+
+void telephony_find_phonebook_entry(void *telephony_device, const char *cmd)
+{
+       /* Get the contact that matches with the string "cmd" and send it back to the remote headset
+       Need a dbus API to pbap agent that does the above operation
+       */
+
+}
+void telephony_get_preffered_store_capacity(void *telephony_device)
+{
+       telephony_get_preffered_store_capacity_rsp( telephony_device, 
+                                                                                               PREFFERED_MESSAGE_STORAGE_MAX,
+                                                                                               CME_ERROR_NONE );
+}
+
+void telephony_list_preffered_store(void *telephony_device)
+{
+       telephony_list_preffered_store_rsp( telephony_device, 
+                                                                               PREFFERED_MESSAGE_STORAGE_LIST,
+                                                                               CME_ERROR_NONE );
+}
+
+/*
+void telephony_set_preffered_store_capcity(void *telephony_device, const char *cmd)
+{
+}
+*/
+void telephony_get_character_set(void *telephony_device)
+{
+       telephony_supported_character_generic_rsp( telephony_device, 
+                                                                               PHONEBOOK_CHARACTER_SET_SUPPORTED,
+                                                                               CME_ERROR_NONE );
+
+}
+
+void telephony_list_supported_character(void *telephony_device)
+{
+       telephony_supported_character_generic_rsp( telephony_device, 
+                                                                               PHONEBOOK_CHARACTER_SET_LIST,
+                                                                               CME_ERROR_NONE );
+}
+
+/*
+void telephony_set_characterset(void *telephony_device, const char *cmd)
+{
+}
+*/
+
diff --git a/audio/telephony.h b/audio/telephony.h
new file mode 100644 (file)
index 0000000..73b390c
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <stdint.h>
+#include <errno.h>
+#include <glib.h>
+
+/* Response and hold values */
+#define BTRH_NOT_SUPPORTED     -2
+#define BTRH_NONE              -1
+#define BTRH_HOLD              0
+#define BTRH_ACCEPT            1
+#define BTRH_REJECT            2
+
+/* 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_voice_dial_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_voice_dial_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_deinit(void);
+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;
+
+       DBG("Telephony indicator \"%s\" %d->%d", desc, ind->val, new_val);
+
+       if (ind->ignore_redundant && ind->val == new_val) {
+               DBG("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/transport.c b/audio/transport.c
new file mode 100644 (file)
index 0000000..2739199
--- /dev/null
@@ -0,0 +1,1054 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/adapter.h"
+#include "../src/dbus-common.h"
+
+#include "log.h"
+#include "error.h"
+#include "device.h"
+#include "avdtp.h"
+#include "media.h"
+#include "transport.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "gateway.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport"
+
+struct media_request {
+       DBusMessage             *msg;
+       guint                   id;
+};
+
+struct media_owner {
+       struct media_transport  *transport;
+       struct media_request    *pending;
+       char                    *name;
+       char                    *accesstype;
+       guint                   watch;
+};
+
+struct media_transport {
+       DBusConnection          *conn;
+       char                    *path;          /* Transport object path */
+       struct audio_device     *device;        /* Transport device */
+       struct avdtp            *session;       /* Signalling session (a2dp only) */
+       struct media_endpoint   *endpoint;      /* Transport endpoint */
+       GSList                  *owners;        /* Transport owners */
+       uint8_t                 *configuration; /* Transport configuration */
+       int                     size;           /* Transport configuration size */
+       int                     fd;             /* Transport file descriptor */
+       uint16_t                imtu;           /* Transport input mtu */
+       uint16_t                omtu;           /* Transport output mtu */
+       uint16_t                delay;          /* Transport delay (a2dp only) */
+       unsigned int            nrec_id;        /* Transport nrec watch (headset only) */
+       gboolean                read_lock;
+       gboolean                write_lock;
+       gboolean                in_use;
+       guint                   (*resume) (struct media_transport *transport,
+                                       struct media_owner *owner);
+       guint                   (*suspend) (struct media_transport *transport,
+                                       struct media_owner *owner);
+       void                    (*cancel) (struct media_transport *transport,
+                                                               guint id);
+       void                    (*get_properties) (
+                                       struct media_transport *transport,
+                                       DBusMessageIter *dict);
+       int                     (*set_property) (
+                                       struct media_transport *transport,
+                                       const char *property,
+                                       DBusMessageIter *value);
+};
+
+void media_transport_destroy(struct media_transport *transport)
+{
+       char *path;
+
+       path = g_strdup(transport->path);
+       g_dbus_unregister_interface(transport->conn, path,
+                                               MEDIA_TRANSPORT_INTERFACE);
+
+       g_free(path);
+}
+
+static struct media_request *media_request_create(DBusMessage *msg, guint id)
+{
+       struct media_request *req;
+
+       req = g_new0(struct media_request, 1);
+       req->msg = dbus_message_ref(msg);
+       req->id = id;
+
+       DBG("Request created: method=%s id=%u", dbus_message_get_member(msg),
+                                                                       id);
+
+       return req;
+}
+
+static void media_request_reply(struct media_request *req,
+                                               DBusConnection *conn, int err)
+{
+       DBusMessage *reply;
+
+       DBG("Request %s Reply %s", dbus_message_get_member(req->msg),
+                                                       strerror(err));
+
+       if (!err)
+               reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID);
+       else
+               reply = g_dbus_create_error(req->msg,
+                                               ERROR_INTERFACE ".Failed",
+                                               "%s", strerror(err));
+
+       g_dbus_send_message(conn, reply);
+}
+
+static gboolean media_transport_release(struct media_transport *transport,
+                                       const char *accesstype)
+{
+       if (g_strstr_len(accesstype, -1, "r") != NULL) {
+               transport->read_lock = FALSE;
+               DBG("Transport %s: read lock released", transport->path);
+       }
+
+       if (g_strstr_len(accesstype, -1, "w") != NULL) {
+               transport->write_lock = FALSE;
+               DBG("Transport %s: write lock released", transport->path);
+       }
+
+       return TRUE;
+}
+
+static void media_owner_remove(struct media_owner *owner)
+{
+       struct media_transport *transport = owner->transport;
+       struct media_request *req = owner->pending;
+
+       if (!req)
+               return;
+
+       DBG("Owner %s Request %s", owner->name,
+                                       dbus_message_get_member(req->msg));
+
+       if (req->id)
+               transport->cancel(transport, req->id);
+
+       owner->pending = NULL;
+       if (req->msg)
+               dbus_message_unref(req->msg);
+
+       g_free(req);
+}
+
+static void media_owner_free(struct media_owner *owner)
+{
+       DBG("Owner %s", owner->name);
+
+       media_owner_remove(owner);
+
+       g_free(owner->name);
+       g_free(owner->accesstype);
+       g_free(owner);
+}
+
+static void media_transport_remove(struct media_transport *transport,
+                                               struct media_owner *owner)
+{
+       DBG("Transport %s Owner %s", transport->path, owner->name);
+
+       media_transport_release(transport, owner->accesstype);
+
+       /* Reply if owner has a pending request */
+       if (owner->pending)
+               media_request_reply(owner->pending, transport->conn, EIO);
+
+       transport->owners = g_slist_remove(transport->owners, owner);
+
+       if (owner->watch)
+               g_dbus_remove_watch(transport->conn, owner->watch);
+
+       media_owner_free(owner);
+
+       /* Suspend if there is no longer any owner */
+       if (transport->owners == NULL && transport->in_use)
+               transport->suspend(transport, NULL);
+}
+
+static gboolean media_transport_set_fd(struct media_transport *transport,
+                                       int fd, uint16_t imtu, uint16_t omtu)
+{
+       if (transport->fd == fd)
+               return TRUE;
+
+       transport->fd = fd;
+       transport->imtu = imtu;
+       transport->omtu = omtu;
+
+       info("%s: fd(%d) ready", transport->path, fd);
+
+       return TRUE;
+}
+
+static void a2dp_resume_complete(struct avdtp *session,
+                               struct avdtp_error *err, void *user_data)
+{
+       struct media_owner *owner = user_data;
+       struct media_request *req = owner->pending;
+       struct media_transport *transport = owner->transport;
+       struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
+       struct avdtp_stream *stream;
+       int fd;
+       uint16_t imtu, omtu;
+       gboolean ret;
+
+       req->id = 0;
+
+       if (err)
+               goto fail;
+
+       stream = a2dp_sep_get_stream(sep);
+       if (stream == NULL)
+               goto fail;
+
+       ret = avdtp_stream_get_transport(stream, &fd, &imtu, &omtu, NULL);
+       if (ret == FALSE)
+               goto fail;
+
+       media_transport_set_fd(transport, fd, imtu, omtu);
+
+       if (g_strstr_len(owner->accesstype, -1, "r") == NULL)
+               imtu = 0;
+
+       if (g_strstr_len(owner->accesstype, -1, "w") == NULL)
+               omtu = 0;
+
+       ret = g_dbus_send_reply(transport->conn, req->msg,
+                                               DBUS_TYPE_UNIX_FD, &fd,
+                                               DBUS_TYPE_UINT16, &imtu,
+                                               DBUS_TYPE_UINT16, &omtu,
+                                               DBUS_TYPE_INVALID);
+       if (ret == FALSE)
+               goto fail;
+
+       media_owner_remove(owner);
+
+       return;
+
+fail:
+       media_transport_remove(transport, owner);
+}
+
+static guint resume_a2dp(struct media_transport *transport,
+                               struct media_owner *owner)
+{
+       struct media_endpoint *endpoint = transport->endpoint;
+       struct audio_device *device = transport->device;
+       struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
+
+       if (transport->session == NULL) {
+               transport->session = avdtp_get(&device->src, &device->dst);
+               if (transport->session == NULL)
+                       return 0;
+       }
+
+       if (transport->in_use == TRUE)
+               goto done;
+
+       transport->in_use = a2dp_sep_lock(sep, transport->session);
+       if (transport->in_use == FALSE)
+               return 0;
+
+done:
+       return a2dp_resume(transport->session, sep, a2dp_resume_complete,
+                               owner);
+}
+
+static void a2dp_suspend_complete(struct avdtp *session,
+                               struct avdtp_error *err, void *user_data)
+{
+       struct media_owner *owner = user_data;
+       struct media_transport *transport = owner->transport;
+       struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
+
+       /* Release always succeeds */
+       if (owner->pending) {
+               owner->pending->id = 0;
+               media_request_reply(owner->pending, transport->conn, 0);
+               media_owner_remove(owner);
+       }
+
+       a2dp_sep_unlock(sep, transport->session);
+       transport->in_use = FALSE;
+       media_transport_remove(transport, owner);
+}
+
+static guint suspend_a2dp(struct media_transport *transport,
+                                               struct media_owner *owner)
+{
+       struct media_endpoint *endpoint = transport->endpoint;
+       struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
+
+       if (!owner) {
+               a2dp_sep_unlock(sep, transport->session);
+               transport->in_use = FALSE;
+               return 0;
+       }
+
+       return a2dp_suspend(transport->session, sep, a2dp_suspend_complete,
+                               owner);
+}
+
+static void cancel_a2dp(struct media_transport *transport, guint id)
+{
+       a2dp_cancel(transport->device, id);
+}
+
+static void headset_resume_complete(struct audio_device *dev, void *user_data)
+{
+       struct media_owner *owner = user_data;
+       struct media_request *req = owner->pending;
+       struct media_transport *transport = owner->transport;
+       int fd;
+       uint16_t imtu, omtu;
+       gboolean ret;
+
+       req->id = 0;
+
+       if (dev == NULL)
+               goto fail;
+
+       fd = headset_get_sco_fd(dev);
+       if (fd < 0)
+               goto fail;
+
+       imtu = 48;
+       omtu = 48;
+
+       media_transport_set_fd(transport, fd, imtu, omtu);
+
+       if (g_strstr_len(owner->accesstype, -1, "r") == NULL)
+               imtu = 0;
+
+       if (g_strstr_len(owner->accesstype, -1, "w") == NULL)
+               omtu = 0;
+
+       ret = g_dbus_send_reply(transport->conn, req->msg,
+                                               DBUS_TYPE_UNIX_FD, &fd,
+                                               DBUS_TYPE_UINT16, &imtu,
+                                               DBUS_TYPE_UINT16, &omtu,
+                                               DBUS_TYPE_INVALID);
+       if (ret == FALSE)
+               goto fail;
+
+       media_owner_remove(owner);
+
+       return;
+
+fail:
+       media_transport_remove(transport, owner);
+}
+
+static guint resume_headset(struct media_transport *transport,
+                               struct media_owner *owner)
+{
+       struct audio_device *device = transport->device;
+
+       if (transport->in_use == TRUE)
+               goto done;
+
+       transport->in_use = headset_lock(device, HEADSET_LOCK_READ |
+                                               HEADSET_LOCK_WRITE);
+       if (transport->in_use == FALSE)
+               return 0;
+
+done:
+       return headset_request_stream(device, headset_resume_complete,
+                                       owner);
+}
+
+static void headset_suspend_complete(struct audio_device *dev, void *user_data)
+{
+       struct media_owner *owner = user_data;
+       struct media_transport *transport = owner->transport;
+
+       /* Release always succeeds */
+       if (owner->pending) {
+               owner->pending->id = 0;
+               media_request_reply(owner->pending, transport->conn, 0);
+               media_owner_remove(owner);
+       }
+
+       headset_unlock(dev, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE);
+       transport->in_use = FALSE;
+       media_transport_remove(transport, owner);
+}
+
+static guint suspend_headset(struct media_transport *transport,
+                                               struct media_owner *owner)
+{
+       struct audio_device *device = transport->device;
+
+       if (!owner) {
+               headset_unlock(device, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE);
+               transport->in_use = FALSE;
+               return 0;
+       }
+
+       return headset_suspend_stream(device, headset_suspend_complete, owner);
+}
+
+static void cancel_headset(struct media_transport *transport, guint id)
+{
+       headset_cancel_stream(transport->device, id);
+}
+
+static void gateway_resume_complete(struct audio_device *dev, GError *err,
+                                                       void *user_data)
+{
+       struct media_owner *owner = user_data;
+       struct media_request *req = owner->pending;
+       struct media_transport *transport = owner->transport;
+       int fd;
+       uint16_t imtu, omtu;
+       gboolean ret;
+
+       req->id = 0;
+
+       if (dev == NULL)
+               goto fail;
+
+       if (err) {
+               error("Failed to resume gateway: error %s", err->message);
+               goto fail;
+       }
+
+       fd = gateway_get_sco_fd(dev);
+       if (fd < 0)
+               goto fail;
+
+       imtu = 48;
+       omtu = 48;
+
+       media_transport_set_fd(transport, fd, imtu, omtu);
+
+       if (g_strstr_len(owner->accesstype, -1, "r") == NULL)
+               imtu = 0;
+
+       if (g_strstr_len(owner->accesstype, -1, "w") == NULL)
+               omtu = 0;
+
+       ret = g_dbus_send_reply(transport->conn, req->msg,
+                                               DBUS_TYPE_UNIX_FD, &fd,
+                                               DBUS_TYPE_UINT16, &imtu,
+                                               DBUS_TYPE_UINT16, &omtu,
+                                               DBUS_TYPE_INVALID);
+       if (ret == FALSE)
+               goto fail;
+
+       media_owner_remove(owner);
+
+       return;
+
+fail:
+       media_transport_remove(transport, owner);
+}
+
+static guint resume_gateway(struct media_transport *transport,
+                               struct media_owner *owner)
+{
+       struct audio_device *device = transport->device;
+
+       if (transport->in_use == TRUE)
+               goto done;
+
+       transport->in_use = gateway_lock(device, GATEWAY_LOCK_READ |
+                                               GATEWAY_LOCK_WRITE);
+       if (transport->in_use == FALSE)
+               return 0;
+
+done:
+       return gateway_request_stream(device, gateway_resume_complete,
+                                       owner);
+}
+
+static gboolean gateway_suspend_complete(gpointer user_data)
+{
+       struct media_owner *owner = user_data;
+       struct media_transport *transport = owner->transport;
+
+       /* Release always succeeds */
+       if (owner->pending) {
+               owner->pending->id = 0;
+               media_request_reply(owner->pending, transport->conn, 0);
+               media_owner_remove(owner);
+       }
+
+       transport->in_use = FALSE;
+       media_transport_remove(transport, owner);
+       return FALSE;
+}
+
+static guint suspend_gateway(struct media_transport *transport,
+                                               struct media_owner *owner)
+{
+       struct audio_device *device = transport->device;
+       static int id = 1;
+
+       if (!owner) {
+               gateway_unlock(device, GATEWAY_LOCK_READ | GATEWAY_LOCK_WRITE);
+               transport->in_use = FALSE;
+               return 0;
+       }
+
+       gateway_suspend_stream(device);
+       gateway_unlock(device, GATEWAY_LOCK_READ | GATEWAY_LOCK_WRITE);
+       g_idle_add(gateway_suspend_complete, owner);
+       return id++;
+}
+
+static void cancel_gateway(struct media_transport *transport, guint id)
+{
+       gateway_cancel_stream(transport->device, id);
+}
+
+static void media_owner_exit(DBusConnection *connection, void *user_data)
+{
+       struct media_owner *owner = user_data;
+
+       owner->watch = 0;
+
+       media_owner_remove(owner);
+
+       media_transport_remove(owner->transport, owner);
+}
+
+static gboolean media_transport_acquire(struct media_transport *transport,
+                                                       const char *accesstype)
+{
+       gboolean read_lock = FALSE, write_lock = FALSE;
+
+       if (g_strstr_len(accesstype, -1, "r") != NULL) {
+               if (transport->read_lock == TRUE)
+                       return FALSE;
+               read_lock = TRUE;
+       }
+
+       if (g_strstr_len(accesstype, -1, "w") != NULL) {
+               if (transport->write_lock == TRUE)
+                       return FALSE;
+               write_lock = TRUE;
+       }
+
+       /* Check invalid accesstype */
+       if (read_lock == FALSE && write_lock == FALSE)
+               return FALSE;
+
+       if (read_lock) {
+               transport->read_lock = read_lock;
+               DBG("Transport %s: read lock acquired", transport->path);
+       }
+
+       if (write_lock) {
+               transport->write_lock = write_lock;
+               DBG("Transport %s: write lock acquired", transport->path);
+       }
+
+
+       return TRUE;
+}
+
+static void media_transport_add(struct media_transport *transport,
+                                       struct media_owner *owner)
+{
+       DBG("Transport %s Owner %s", transport->path, owner->name);
+       transport->owners = g_slist_append(transport->owners, owner);
+       owner->transport = transport;
+}
+
+static struct media_owner *media_owner_create(DBusConnection *conn,
+                                               DBusMessage *msg,
+                                               const char *accesstype)
+{
+       struct media_owner *owner;
+
+       owner = g_new0(struct media_owner, 1);
+       owner->name = g_strdup(dbus_message_get_sender(msg));
+       owner->accesstype = g_strdup(accesstype);
+       owner->watch = g_dbus_add_disconnect_watch(conn, owner->name,
+                                                       media_owner_exit,
+                                                       owner, NULL);
+
+       DBG("Owner created: sender=%s accesstype=%s", owner->name,
+                       accesstype);
+
+       return owner;
+}
+
+static void media_owner_add(struct media_owner *owner,
+                                               struct media_request *req)
+{
+       DBG("Owner %s Request %s", owner->name,
+                                       dbus_message_get_member(req->msg));
+
+       owner->pending = req;
+}
+
+static struct media_owner *media_transport_find_owner(
+                                       struct media_transport *transport,
+                                       const char *name)
+{
+       GSList *l;
+
+       for (l = transport->owners; l; l = l->next) {
+               struct media_owner *owner = l->data;
+
+               if (g_strcmp0(owner->name, name) == 0)
+                       return owner;
+       }
+
+       return NULL;
+}
+
+static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg,
+                                       void *data)
+{
+       struct media_transport *transport = data;
+       struct media_owner *owner;
+       struct media_request *req;
+       const char *accesstype, *sender;
+       guint id;
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_STRING, &accesstype,
+                               DBUS_TYPE_INVALID))
+               return NULL;
+
+       sender = dbus_message_get_sender(msg);
+
+       owner = media_transport_find_owner(transport, sender);
+       if (owner != NULL)
+               return btd_error_not_authorized(msg);
+
+       if (media_transport_acquire(transport, accesstype) == FALSE)
+               return btd_error_not_authorized(msg);
+
+       owner = media_owner_create(conn, msg, accesstype);
+       id = transport->resume(transport, owner);
+       if (id == 0) {
+               media_owner_free(owner);
+               return btd_error_not_authorized(msg);
+       }
+
+       req = media_request_create(msg, id);
+       media_owner_add(owner, req);
+       media_transport_add(transport, owner);
+
+       return NULL;
+}
+
+static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
+                                       void *data)
+{
+       struct media_transport *transport = data;
+       struct media_owner *owner;
+       const char *accesstype, *sender;
+       struct media_request *req;
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_STRING, &accesstype,
+                               DBUS_TYPE_INVALID))
+               return NULL;
+
+       sender = dbus_message_get_sender(msg);
+
+       owner = media_transport_find_owner(transport, sender);
+       if (owner == NULL)
+               return btd_error_not_authorized(msg);
+
+       if (g_strcmp0(owner->accesstype, accesstype) == 0) {
+               guint id;
+
+               /* Not the last owner, no need to suspend */
+               if (g_slist_length(transport->owners) != 1) {
+                       media_transport_remove(transport, owner);
+                       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+               }
+
+               if (owner->pending) {
+                       const char *member;
+
+                       member = dbus_message_get_member(owner->pending->msg);
+                       /* Cancel Acquire request if that exist */
+                       if (g_str_equal(member, "Acquire"))
+                               media_owner_remove(owner);
+                       else
+                               return btd_error_in_progress(msg);
+               }
+
+               id = transport->suspend(transport, owner);
+               if (id == 0) {
+                       media_transport_remove(transport, owner);
+                       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+               }
+
+               req = media_request_create(msg, id);
+               media_owner_add(owner, req);
+
+               return NULL;
+       } else if (g_strstr_len(owner->accesstype, -1, accesstype) != NULL) {
+               media_transport_release(transport, accesstype);
+               g_strdelimit(owner->accesstype, accesstype, ' ');
+       } else
+               return btd_error_not_authorized(msg);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static int set_property_a2dp(struct media_transport *transport,
+                                               const char *property,
+                                               DBusMessageIter *value)
+{
+       if (g_strcmp0(property, "Delay") == 0) {
+               if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16)
+                       return -EINVAL;
+               dbus_message_iter_get_basic(value, &transport->delay);
+
+               /* FIXME: send new delay */
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int set_property_headset(struct media_transport *transport,
+                                               const char *property,
+                                               DBusMessageIter *value)
+{
+       if (g_strcmp0(property, "NREC") == 0) {
+               gboolean nrec;
+
+               if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN)
+                       return -EINVAL;
+               dbus_message_iter_get_basic(value, &nrec);
+
+               /* FIXME: set new nrec */
+               return 0;
+       } else if (g_strcmp0(property, "InbandRingtone") == 0) {
+               gboolean inband;
+
+               if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN)
+                       return -EINVAL;
+               dbus_message_iter_get_basic(value, &inband);
+
+               /* FIXME: set new inband */
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int set_property_gateway(struct media_transport *transport,
+                                               const char *property,
+                                               DBusMessageIter *value)
+{
+       return -EINVAL;
+}
+
+static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct media_transport *transport = data;
+       DBusMessageIter iter;
+       DBusMessageIter value;
+       const char *property, *sender;
+       GSList *l;
+       int err;
+
+       if (!dbus_message_iter_init(msg, &iter))
+               return btd_error_invalid_args(msg);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+               return btd_error_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 btd_error_invalid_args(msg);
+       dbus_message_iter_recurse(&iter, &value);
+
+       sender = dbus_message_get_sender(msg);
+       err = -EINVAL;
+
+       /* Check if sender has acquired the transport */
+       for (l = transport->owners; l; l = l->next) {
+               struct media_owner *owner = l->data;
+
+               if (g_strcmp0(owner->name, sender) == 0) {
+                       err = transport->set_property(transport, property,
+                                                               &value);
+                       break;
+               }
+       }
+
+       if (err < 0) {
+               if (err == -EINVAL)
+                       return btd_error_invalid_args(msg);
+               return btd_error_failed(msg, strerror(-err));
+       }
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void get_properties_a2dp(struct media_transport *transport,
+                                               DBusMessageIter *dict)
+{
+       dict_append_entry(dict, "Delay", DBUS_TYPE_UINT16, &transport->delay);
+}
+
+static void get_properties_headset(struct media_transport *transport,
+                                               DBusMessageIter *dict)
+{
+       gboolean nrec, inband;
+       const char *routing;
+
+       nrec = headset_get_nrec(transport->device);
+       dict_append_entry(dict, "NREC", DBUS_TYPE_BOOLEAN, &nrec);
+
+       inband = headset_get_inband(transport->device);
+       dict_append_entry(dict, "InbandRingtone", DBUS_TYPE_BOOLEAN, &inband);
+
+       routing = headset_get_sco_hci(transport->device) ? "HCI" : "PCM";
+       dict_append_entry(dict, "Routing", DBUS_TYPE_STRING, &routing);
+}
+
+static void get_properties_gateway(struct media_transport *transport,
+                                               DBusMessageIter *dict)
+{
+       /* None */
+}
+
+void transport_get_properties(struct media_transport *transport,
+                                                       DBusMessageIter *iter)
+{
+       DBusMessageIter dict;
+       const char *uuid;
+       uint8_t codec;
+
+       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);
+
+       /* Device */
+       dict_append_entry(&dict, "Device", DBUS_TYPE_OBJECT_PATH,
+                                               &transport->device->path);
+
+       uuid = media_endpoint_get_uuid(transport->endpoint);
+       dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+
+       codec = media_endpoint_get_codec(transport->endpoint);
+       dict_append_entry(&dict, "Codec", DBUS_TYPE_BYTE, &codec);
+
+       dict_append_array(&dict, "Configuration", DBUS_TYPE_BYTE,
+                               &transport->configuration, transport->size);
+
+       if (transport->get_properties)
+               transport->get_properties(transport, &dict);
+
+       dbus_message_iter_close_container(iter, &dict);
+}
+
+static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
+                                       void *data)
+{
+       struct media_transport *transport = data;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       dbus_message_iter_init_append(reply, &iter);
+
+       transport_get_properties(transport, &iter);
+
+       return reply;
+}
+
+static GDBusMethodTable transport_methods[] = {
+       { "GetProperties",      "",     "a{sv}",        get_properties },
+       { "Acquire",            "s",    "h",            acquire,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "Release",            "s",    "",             release,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "SetProperty",        "sv",   "",             set_property },
+       { },
+};
+
+static GDBusSignalTable transport_signals[] = {
+       { "PropertyChanged",    "sv"    },
+       { }
+};
+
+static void media_transport_free(void *data)
+{
+       struct media_transport *transport = data;
+       GSList *l = transport->owners;
+
+       while (l) {
+               struct media_owner *owner = l->data;
+               l = l->next;
+               media_transport_remove(transport, owner);
+       }
+
+       g_slist_free(transport->owners);
+
+       if (transport->session)
+               avdtp_unref(transport->session);
+
+       if (transport->nrec_id)
+               headset_remove_nrec_cb(transport->device, transport->nrec_id);
+
+       if (transport->conn)
+               dbus_connection_unref(transport->conn);
+
+       g_free(transport->configuration);
+       g_free(transport->path);
+       g_free(transport);
+}
+
+static void headset_nrec_changed(struct audio_device *dev, gboolean nrec,
+                                                       void *user_data)
+{
+       struct media_transport *transport = user_data;
+
+       DBG("");
+
+       emit_property_changed(transport->conn, transport->path,
+                               MEDIA_TRANSPORT_INTERFACE, "NREC",
+                               DBUS_TYPE_BOOLEAN, &nrec);
+}
+
+struct media_transport *media_transport_create(DBusConnection *conn,
+                                               struct media_endpoint *endpoint,
+                                               struct audio_device *device,
+                                               uint8_t *configuration,
+                                               size_t size)
+{
+       struct media_transport *transport;
+       const char *uuid;
+       static int fd = 0;
+
+       transport = g_new0(struct media_transport, 1);
+       transport->conn = dbus_connection_ref(conn);
+       transport->device = device;
+       transport->endpoint = endpoint;
+       transport->configuration = g_new(uint8_t, size);
+       memcpy(transport->configuration, configuration, size);
+       transport->size = size;
+       transport->path = g_strdup_printf("%s/fd%d", device->path, fd++);
+       transport->fd = -1;
+
+       uuid = media_endpoint_get_uuid(endpoint);
+       if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0 ||
+                       strcasecmp(uuid, A2DP_SINK_UUID) == 0) {
+               transport->resume = resume_a2dp;
+               transport->suspend = suspend_a2dp;
+               transport->cancel = cancel_a2dp;
+               transport->get_properties = get_properties_a2dp;
+               transport->set_property = set_property_a2dp;
+       } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 ||
+                       strcasecmp(uuid, HSP_AG_UUID) == 0) {
+               transport->resume = resume_headset;
+               transport->suspend = suspend_headset;
+               transport->cancel = cancel_headset;
+               transport->get_properties = get_properties_headset;
+               transport->set_property = set_property_headset;
+               transport->nrec_id = headset_add_nrec_cb(device,
+                                                       headset_nrec_changed,
+                                                       transport);
+       } else if (strcasecmp(uuid, HFP_HS_UUID) == 0 ||
+                       strcasecmp(uuid, HSP_HS_UUID) == 0) {
+               transport->resume = resume_gateway;
+               transport->suspend = suspend_gateway;
+               transport->cancel = cancel_gateway;
+               transport->get_properties = get_properties_gateway;
+               transport->set_property = set_property_gateway;
+       } else
+               goto fail;
+
+       if (g_dbus_register_interface(transport->conn, transport->path,
+                               MEDIA_TRANSPORT_INTERFACE,
+                               transport_methods, transport_signals, NULL,
+                               transport, media_transport_free) == FALSE) {
+               error("Could not register transport %s", transport->path);
+               goto fail;
+       }
+
+       return transport;
+
+fail:
+       media_transport_free(transport);
+       return NULL;
+}
+
+const char *media_transport_get_path(struct media_transport *transport)
+{
+       return transport->path;
+}
+
+void media_transport_update_delay(struct media_transport *transport,
+                                                       uint16_t delay)
+{
+       /* Check if delay really changed */
+       if (transport->delay == delay)
+               return;
+
+       transport->delay = delay;
+
+       emit_property_changed(transport->conn, transport->path,
+                               MEDIA_TRANSPORT_INTERFACE, "Delay",
+                               DBUS_TYPE_UINT16, &transport->delay);
+}
+
+struct audio_device *media_transport_get_dev(struct media_transport *transport)
+{
+       return transport->device;
+}
diff --git a/audio/transport.h b/audio/transport.h
new file mode 100644 (file)
index 0000000..1f86cde
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 media_transport;
+
+struct media_transport *media_transport_create(DBusConnection *conn,
+                                               struct media_endpoint *endpoint,
+                                               struct audio_device *device,
+                                               uint8_t *configuration,
+                                               size_t size);
+
+void media_transport_destroy(struct media_transport *transport);
+const char *media_transport_get_path(struct media_transport *transport);
+struct audio_device *media_transport_get_dev(struct media_transport *transport);
+void media_transport_update_delay(struct media_transport *transport,
+                                                       uint16_t delay);
+void transport_get_properties(struct media_transport *transport,
+                                                       DBusMessageIter *iter);
diff --git a/audio/unix.c b/audio/unix.c
new file mode 100644 (file)
index 0000000..c2d6d4a
--- /dev/null
@@ -0,0 +1,1907 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#include "log.h"
+#include "ipc.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "media.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "sink.h"
+#include "source.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(void *data)
+{
+       struct unix_client *client = data;
+
+       DBG("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);
+
+       g_slist_free_full(client->caps, g_free);
+
+       g_free(client->interface);
+       g_free(client);
+}
+
+static 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;
+}
+
+/* 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);
+
+       DBG("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;
+
+       DBG("sending error %s(%d)", strerror(err), 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->source && avdtp_is_connected(&dev->src,
+                                                               &dev->dst))
+                       return TYPE_SOURCE;
+               else if (dev->headset && headset_is_active(dev))
+                       return TYPE_HEADSET;
+               else if (dev->sink)
+                       return TYPE_SINK;
+               else if (dev->source)
+                       return TYPE_SOURCE;
+               else if (dev->headset)
+                       return TYPE_HEADSET;
+       } else if (!strcmp(interface, AUDIO_SOURCE_INTERFACE) && dev->source)
+               return TYPE_SOURCE;
+       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, GError *err, void *user_data)
+{
+       struct unix_client *client = user_data;
+       char buf[BT_SUGGESTED_BUFFER_SIZE];
+       struct bt_set_configuration_rsp *rsp = (void *) buf;
+
+       if (err) {
+               unix_ipc_error(client, BT_SET_CONFIGURATION, err->code);
+               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, GError *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;
+
+       if (err) {
+               unix_ipc_error(client, BT_START_STREAM, err->code);
+               return;
+       }
+
+       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)
+{
+       DBG("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)
+{
+       DBG("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 type,
+                               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;
+
+               if (type == AVDTP_SEP_TYPE_SINK)
+                       codec->type = BT_A2DP_SBC_SINK;
+               else if (type == AVDTP_SEP_TYPE_SOURCE)
+                       codec->type = BT_A2DP_SBC_SOURCE;
+               else
+                       return -EINVAL;
+
+               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);
+       } 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;
+
+               if (type == AVDTP_SEP_TYPE_SINK)
+                       codec->type = BT_A2DP_MPEG12_SINK;
+               else if (type == AVDTP_SEP_TYPE_SOURCE)
+                       codec->type = BT_A2DP_MPEG12_SOURCE;
+               else
+                       return -EINVAL;
+
+               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);
+       } 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;
+
+               if (type == AVDTP_SEP_TYPE_SINK)
+                       codec->type = BT_A2DP_UNKNOWN_SINK;
+               else if (type == AVDTP_SEP_TYPE_SOURCE)
+                       codec->type = BT_A2DP_UNKNOWN_SOURCE;
+               else
+                       return -EINVAL;
+
+               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->seid = seid;
+       codec->configured = configured;
+       codec->lock = lock;
+       rsp->h.length += codec->length;
+
+       DBG("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;
+
+       if (!g_slist_find(clients, client)) {
+               DBG("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 (; seps; seps = g_slist_next(seps)) {
+               struct avdtp_remote_sep *rsep = seps->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 &&
+                                               type != AVDTP_SEP_TYPE_SOURCE)
+                       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->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, type, 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;
+
+       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);
+}
+
+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:
+       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;
+               }
+
+               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);
+}
+
+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:
+               id = gateway_config_stream(dev, gateway_setup_complete, client);
+               client->cancel = gateway_cancel_stream;
+               break;
+
+       default:
+               error("No known services for device");
+               goto failed;
+       }
+
+       if (id == 0) {
+               error("config failed");
+               goto failed;
+       }
+
+       client->req_id = id;
+       g_slist_free(client->caps);
+       client->caps = NULL;
+
+       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 = NULL;
+       struct headset_data *hs;
+       unsigned int id;
+       struct avdtp *session = NULL;
+
+       switch (client->type) {
+       case TYPE_SINK:
+       case TYPE_SOURCE:
+               a2dp = &client->d.a2dp;
+
+               if (!a2dp->sep) {
+                       error("seid not opened");
+                       goto failed;
+               }
+
+               if (!a2dp->session) {
+                       session = avdtp_get(&dev->src, &dev->dst);
+                       if (!session) {
+                               error("Unable to get a session");
+                               goto failed;
+                       }
+                       a2dp->session = session;
+               }
+
+               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:
+               id = gateway_request_stream(dev, gateway_resume_complete,
+                                               client);
+               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 (session) {
+               avdtp_unref(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 = NULL;
+       struct headset_data *hs;
+       unsigned int id;
+       struct avdtp *session = NULL;
+
+       switch (client->type) {
+       case TYPE_SINK:
+       case TYPE_SOURCE:
+               a2dp = &client->d.a2dp;
+
+               if (!a2dp->sep) {
+                       error("seid not opened");
+                       goto failed;
+               }
+
+               if (!a2dp->session) {
+                       session = avdtp_get(&dev->src, &dev->dst);
+                       if (!session) {
+                               error("Unable to get a session");
+                               goto failed;
+                       }
+                       a2dp->session = session;
+               }
+
+               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 (session) {
+               avdtp_unref(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);
+                       client->cb_id = 0;
+               }
+               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;
+               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;
+       const char *interface;
+
+       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 (!manager_find_device(req->object, &src, &dst, NULL, FALSE))
+               goto failed;
+
+       if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)
+               interface = AUDIO_HEADSET_INTERFACE;
+       else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+               interface = AUDIO_SINK_INTERFACE;
+       else
+               interface = client->interface;
+
+       dev = manager_find_device(req->object, &src, &dst, interface, TRUE);
+       if (!dev && (req->flags & BT_FLAG_AUTOCONNECT))
+               dev = manager_find_device(req->object, &src, &dst,
+                                                       interface, FALSE);
+
+       if (!dev) {
+               if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)
+                       interface = AUDIO_GATEWAY_INTERFACE;
+               else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+                       interface = AUDIO_SOURCE_INTERFACE;
+               else
+                       interface = NULL;
+               dev = manager_find_device(req->object, &src, &dst,
+                                                       interface, TRUE);
+               if (!dev && (req->flags & BT_FLAG_AUTOCONNECT))
+                       dev = manager_find_device(req->object, &src, &dst,
+                                                       interface, FALSE);
+       }
+
+       if (!dev) {
+               error("Unable to find a matching device");
+               goto failed;
+       }
+
+       client->type = select_service(dev, interface);
+       if (client->type == TYPE_NONE) {
+               error("No matching service found");
+               goto failed;
+       }
+
+       if (g_strcmp0(interface, client->interface) != 0) {
+               g_free(client->interface);
+               client->interface = g_strdup(interface);
+       }
+
+       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;
+
+       DBG("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)
+               /* FIXME: are we treating a sink or a source? */
+               client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+       else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE) &&
+                       !g_str_equal(client->interface, AUDIO_SOURCE_INTERFACE))
+               return -EIO;
+
+       DBG("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)
+               /* FIXME: are we treating a sink or a source? */
+               client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+       else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE) &&
+                       !g_str_equal(client->interface, AUDIO_SOURCE_INTERFACE))
+               return -EIO;
+
+       g_slist_free_full(client->caps, g_free);
+       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 ||
+               req->codec.type == BT_A2DP_MPEG12_SOURCE) {
+               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 ||
+                       req->codec.type == BT_A2DP_SBC_SOURCE) {
+               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",
+                               req->codec.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 void handle_delay_report_req(struct unix_client *client,
+                                       struct bt_delay_report_req *req)
+{
+       char buf[BT_SUGGESTED_BUFFER_SIZE];
+       struct bt_set_configuration_rsp *rsp = (void *) buf;
+       struct a2dp_data *a2dp;
+       int err;
+
+       if (!client->dev) {
+               err = -ENODEV;
+               goto failed;
+       }
+
+       switch (client->type) {
+       case TYPE_HEADSET:
+        case TYPE_GATEWAY:
+               err = -EINVAL;
+               goto failed;
+       case TYPE_SOURCE:
+       case TYPE_SINK:
+               a2dp = &client->d.a2dp;
+               if (a2dp->session && a2dp->stream) {
+                       err = avdtp_delay_report(a2dp->session, a2dp->stream,
+                                                               req->delay);
+                       if (err < 0)
+                               goto failed;
+               } else {
+                       err = -EINVAL;
+                       goto failed;
+               }
+               break;
+       default:
+               error("No known services for device");
+               err = -EINVAL;
+               goto failed;
+       }
+
+       memset(buf, 0, sizeof(buf));
+       rsp->h.type = BT_RESPONSE;
+       rsp->h.name = BT_DELAY_REPORT;
+       rsp->h.length = sizeof(*rsp);
+
+       unix_ipc_sendmsg(client, &rsp->h);
+
+       return;
+
+failed:
+       unix_ipc_error(client, BT_DELAY_REPORT, -err);
+}
+
+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)) {
+               DBG("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);
+
+       DBG("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;
+       case BT_DELAY_REPORT:
+               handle_delay_report_req(client,
+                               (struct bt_delay_report_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_shutdown(chan, TRUE, NULL);
+               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;
+       }
+
+       DBG("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;
+
+       DBG("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);
+               }
+       }
+}
+
+void unix_delay_report(struct audio_device *dev, uint8_t seid, uint16_t delay)
+{
+       GSList *l;
+       struct bt_delay_report_ind ind;
+
+       DBG("unix_delay_report(%p): %u.%ums", dev, delay / 10, delay % 10);
+
+       memset(&ind, 0, sizeof(ind));
+       ind.h.type = BT_INDICATION;
+       ind.h.name = BT_DELAY_REPORT;
+       ind.h.length = sizeof(ind);
+       ind.delay = delay;
+
+       for (l = clients; l != NULL; l = g_slist_next(l)) {
+               struct unix_client *client = l->data;
+
+               if (client->dev != dev || client->seid != seid)
+                       continue;
+
+               unix_ipc_sendmsg(client, (void *) &ind);
+       }
+}
+
+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);
+
+       DBG("Unix socket created: %d", sk);
+
+       return 0;
+}
+
+void unix_exit(void)
+{
+       g_slist_free_full(clients, client_free);
+       if (unix_sock >= 0) {
+               close(unix_sock);
+               unix_sock = -1;
+       }
+}
diff --git a/audio/unix.h b/audio/unix.h
new file mode 100644 (file)
index 0000000..74ca16d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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);
+
+void unix_delay_report(struct audio_device *dev, uint8_t seid, uint16_t delay);
+
+int unix_init(void);
+void unix_exit(void);
diff --git a/bluez.pc.in b/bluez.pc.in
new file mode 100644 (file)
index 0000000..3d6e596
--- /dev/null
@@ -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/btio/btio.c b/btio/btio.c
new file mode 100644 (file)
index 0000000..2cc9082
--- /dev/null
@@ -0,0 +1,1376 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009-2010  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 <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include <glib.h>
+
+#include "btio.h"
+
+#ifndef BT_FLUSHABLE
+#define BT_FLUSHABLE   8
+#endif
+
+#define ERROR_FAILED(gerr, str, err) \
+               g_set_error(gerr, BT_IO_ERROR, 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 cid;
+       uint16_t mtu;
+       uint16_t imtu;
+       uint16_t omtu;
+       int master;
+       uint8_t mode;
+       int flushable;
+};
+
+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)
+               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,
+                                               uint16_t cid, GError **err)
+{
+       struct sockaddr_l2 addr;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, src);
+
+       if (cid)
+               addr.l2_cid = htobs(cid);
+       else
+               addr.l2_psm = htobs(psm);
+
+       if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               int error = -errno;
+               ERROR_FAILED(err, "l2cap_bind", errno);
+               return error;
+       }
+
+       return 0;
+}
+
+static int l2cap_connect(int sock, const bdaddr_t *dst,
+                                       uint16_t psm, uint16_t cid)
+{
+       int err;
+       struct sockaddr_l2 addr;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, dst);
+       if (cid)
+               addr.l2_cid = htobs(cid);
+       else
+               addr.l2_psm = htobs(psm);
+
+       err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
+       if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
+               return -errno;
+
+       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 int l2cap_set_flushable(int sock, gboolean flushable)
+{
+       int f;
+
+       f = flushable;
+       if (setsockopt(sock, SOL_BLUETOOTH, BT_FLUSHABLE, &f, sizeof(f)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static gboolean l2cap_set(int sock, int sec_level, uint16_t imtu,
+                               uint16_t omtu, uint8_t mode, int master,
+                               int flushable, GError **err)
+{
+       if (imtu || omtu || mode) {
+               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 (mode)
+                       l2o.mode = mode;
+
+               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 (flushable >= 0 && l2cap_set_flushable(sock, flushable) < 0) {
+               ERROR_FAILED(err, "l2cap_set_flushable", errno);
+               return FALSE;
+       }
+
+       if (sec_level && !set_sec_level(sock, BT_IO_L2CAP, sec_level, err))
+               return FALSE;
+
+       return TRUE;
+}
+
+static int rfcomm_bind(int sock,
+               const bdaddr_t *src, uint8_t channel, GError **err)
+{
+       struct sockaddr_rc addr;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, src);
+       addr.rc_channel = channel;
+
+       if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               int error = -errno;
+               ERROR_FAILED(err, "rfcomm_bind", errno);
+               return error;
+       }
+
+       return 0;
+}
+
+static int rfcomm_connect(int sock, const bdaddr_t *dst, uint8_t channel)
+{
+       int err;
+       struct sockaddr_rc addr;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, dst);
+       addr.rc_channel = channel;
+
+       err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
+       if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
+               return -errno;
+
+       return 0;
+}
+
+static gboolean rfcomm_set(int sock, int sec_level, int 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, GError **err)
+{
+       struct sockaddr_sco addr;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, src);
+
+       if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               int error = -errno;
+               ERROR_FAILED(err, "sco_bind", errno);
+               return error;
+       }
+
+       return 0;
+}
+
+static int sco_connect(int sock, const bdaddr_t *dst)
+{
+       struct sockaddr_sco addr;
+       int err;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, dst);
+
+       err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
+       if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
+               return -errno;
+
+       return 0;
+}
+
+static 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;
+       opts->sec_level = BT_IO_SEC_MEDIUM;
+       opts->mode = L2CAP_MODE_BASIC;
+       opts->flushable = -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_CID:
+                       opts->cid = va_arg(args, int);
+                       break;
+               case BT_IO_OPT_MTU:
+                       opts->mtu = va_arg(args, int);
+                       opts->imtu = opts->mtu;
+                       opts->omtu = opts->mtu;
+                       break;
+               case BT_IO_OPT_OMTU:
+                       opts->omtu = va_arg(args, int);
+                       if (!opts->mtu)
+                               opts->mtu = opts->omtu;
+                       break;
+               case BT_IO_OPT_IMTU:
+                       opts->imtu = va_arg(args, int);
+                       if (!opts->mtu)
+                               opts->mtu = opts->imtu;
+                       break;
+               case BT_IO_OPT_MASTER:
+                       opts->master = va_arg(args, gboolean);
+                       break;
+               case BT_IO_OPT_MODE:
+                       opts->mode = va_arg(args, int);
+                       break;
+               case BT_IO_OPT_FLUSHABLE:
+                       opts->flushable = 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 int l2cap_get_flushable(int sock, gboolean *flushable)
+{
+       int f;
+       socklen_t len;
+
+       f = 0;
+       len = sizeof(f);
+       if (getsockopt(sock, SOL_BLUETOOTH, BT_FLUSHABLE, &f, &len) < 0)
+               return -errno;
+
+       if (f)
+               *flushable = TRUE;
+       else
+               *flushable = FALSE;
+
+       return 0;
+}
+
+static 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;
+       gboolean flushable = FALSE;
+
+       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_CID:
+                       *(va_arg(args, uint16_t *)) = src.l2_cid ?
+                                               src.l2_cid : dst.l2_cid;
+                       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;
+               case BT_IO_OPT_MODE:
+                       *(va_arg(args, uint8_t *)) = l2o.mode;
+                       break;
+               case BT_IO_OPT_FLUSHABLE:
+                       if (l2cap_get_flushable(sock, &flushable) < 0) {
+                               ERROR_FAILED(err, "get_flushable", errno);
+                               return FALSE;
+                       }
+                       *(va_arg(args, gboolean *)) = flushable;
+                       break;
+               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_SOURCE_CHANNEL:
+                       *(va_arg(args, uint8_t *)) = src.rc_channel;
+                       break;
+               case BT_IO_OPT_DEST_CHANNEL:
+                       *(va_arg(args, uint8_t *)) = 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, "SCO_CONNINFO", errno);
+                               return FALSE;
+                       }
+                       *(va_arg(args, uint16_t *)) = handle;
+                       break;
+               case BT_IO_OPT_CLASS:
+                       if (sco_get_info(sock, &handle, dev_class) < 0) {
+                               ERROR_FAILED(err, "SCO_CONNINFO", errno);
+                               return FALSE;
+                       }
+                       memcpy(va_arg(args, uint8_t *), dev_class, 3);
+                       break;
+               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)) {
+               if (read(sock, &c, 1) < 0) {
+                       ERROR_FAILED(err, "read", errno);
+                       return FALSE;
+               }
+       }
+
+       accept_add(io, connect, user_data, destroy);
+
+       return TRUE;
+}
+
+gboolean bt_io_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.mode, opts.master, opts.flushable, 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,
+                                                       opts->cid, err) < 0)
+                       goto failed;
+               if (!l2cap_set(sock, opts->sec_level, 0, 0, 0, -1, -1, err))
+                       goto failed;
+               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,
+                                                       opts->cid, err) < 0)
+                       goto failed;
+               if (!l2cap_set(sock, opts->sec_level, opts->imtu, opts->omtu,
+                               opts->mode, opts->master, opts->flushable, err))
+                       goto failed;
+               break;
+       case BT_IO_RFCOMM:
+               sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+               if (sock < 0) {
+                       ERROR_FAILED(err, "socket(STREAM, RFCOMM)", errno);
+                       return NULL;
+               }
+               if (rfcomm_bind(sock, &opts->src,
+                                       server ? opts->channel : 0, err) < 0)
+                       goto failed;
+               if (!rfcomm_set(sock, opts->sec_level, opts->master, err))
+                       goto failed;
+               break;
+       case BT_IO_SCO:
+               sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+               if (sock < 0) {
+                       ERROR_FAILED(err, "socket(SEQPACKET, SCO)", errno);
+                       return NULL;
+               }
+               if (sco_bind(sock, &opts->src, err) < 0)
+                       goto failed;
+               if (!sco_set(sock, opts->mtu, err))
+                       goto failed;
+               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;
+
+failed:
+       close(sock);
+
+       return NULL;
+}
+
+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, opts.cid);
+               break;
+       case BT_IO_L2CAP:
+               err = l2cap_connect(sock, &opts.dst, opts.psm, opts.cid);
+               break;
+       case BT_IO_RFCOMM:
+               err = rfcomm_connect(sock, &opts.dst, opts.channel);
+               break;
+       case BT_IO_SCO:
+               err = sco_connect(sock, &opts.dst);
+               break;
+       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/btio/btio.h b/btio/btio.h
new file mode 100644 (file)
index 0000000..c6b736f
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009-2010  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 <glib.h>
+
+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_SOURCE_CHANNEL,
+       BT_IO_OPT_DEST_CHANNEL,
+       BT_IO_OPT_PSM,
+       BT_IO_OPT_CID,
+       BT_IO_OPT_MTU,
+       BT_IO_OPT_OMTU,
+       BT_IO_OPT_IMTU,
+       BT_IO_OPT_MASTER,
+       BT_IO_OPT_HANDLE,
+       BT_IO_OPT_CLASS,
+       BT_IO_OPT_MODE,
+       BT_IO_OPT_FLUSHABLE,
+} 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/compat/bnep.c b/compat/bnep.c
new file mode 100644 (file)
index 0000000..281350b
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/bnep.h>
+
+#include <netinet/in.h>
+
+#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++) {
+               char addr[18];
+               ba2str((bdaddr_t *) ci[i].dst, addr);
+               printf("%s %s %s\n", ci[i].device,
+                       addr, 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;
+
+       /* Highest known Control command ID
+        * is BNEP_FILTER_MULT_ADDR_RSP = 0x06 */
+       if (req->type == BNEP_CONTROL &&
+                               req->ctrl > BNEP_FILTER_MULT_ADDR_RSP) {
+               uint8_t pkt[3];
+
+               pkt[0] = BNEP_CONTROL;
+               pkt[1] = BNEP_CMD_NOT_UNDERSTOOD;
+               pkt[2] = req->ctrl;
+
+               send(sk, pkt, sizeof(pkt), 0);
+
+               return -1;
+       }
+
+       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 (file)
index 0000000..59f036f
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <dirent.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+#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);
+       free(dl);
+       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)) {
+                               closedir(dir);
+                               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<<RFCOMM_REUSE_DLC)) &&
+               (di->flags & (1<<RFCOMM_TTY_ATTACHED)) &&
+               (di->flags & (1<<RFCOMM_RELEASE_ONHUP))) {
+
+               if (find_pppd(di->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<<RFCOMM_REUSE_DLC)) &&
+               (di->flags & (1<<RFCOMM_TTY_ATTACHED)) &&
+               (di->flags & (1<<RFCOMM_RELEASE_ONHUP))) {
+
+               if (dst && bacmp(&di->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 (file)
index 0000000..09fb7f7
--- /dev/null
@@ -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 <options> [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 <bdaddr>
+Create DUN connection
+.TP
+\fB\-\-mrouter\fR \fB\-m\fR <bdaddr>
+Create mRouter connection
+.TP
+\fB\-\-search\fR \fB\-Q[duration]\fR
+Search and connect
+.TP
+\fB\-\-kill\fR \fB\-k\fR <bdaddr>
+Kill DUN connection
+.TP
+\fB\-\-killall\fR \fB\-K\fR
+Kill all DUN connections
+.TP
+\fB\-\-channel\fR \fB\-C\fR <channel>
+RFCOMM channel
+.TP
+\fB\-\-device\fR \fB\-i\fR <bdaddr>
+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 <pppd>
+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 (file)
index 0000000..af1b536
--- /dev/null
@@ -0,0 +1,645 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <signal.h>
+#include <getopt.h>
+
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/hidp.h>
+
+#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);
+               snprintf(ch, sizeof(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 <options> [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 <bdaddr>     Create LAP connection\n"
+       "\t--mrouter -m <bdaddr>     Create mRouter connection\n"
+       "\t--search -Q[duration]     Search and connect\n"
+       "\t--kill -k <bdaddr>        Kill LAP connection\n"
+       "\t--killall -K              Kill all LAP connections\n"
+       "\t--channel -P <channel>    RFCOMM channel\n"
+       "\t--device -i <bdaddr>      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 <pppd>          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()) {
+               free(dst);
+               return -1;
+       }
+
+       /* Check non daemon modes first */
+       switch (mode) {
+       case SHOW:
+               do_show();
+               free(dst);
+               return 0;
+
+       case KILL:
+               do_kill(dst);
+               free(dst);
+               return 0;
+
+       case NONE:
+               printf(main_help, VERSION);
+               free(dst);
+               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);
+                       free(dst);
+                       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;
+       }
+
+       free(dst);
+       return 0;
+}
diff --git a/compat/dund.h b/compat/dund.h
new file mode 100644 (file)
index 0000000..e3a4ef6
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 (file)
index 0000000..b996d10
--- /dev/null
@@ -0,0 +1,669 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/hidp.h>
+
+#include "hidd.h"
+#include "uinput.h"
+
+#include <math.h>
+
+#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/hidd.1 b/compat/hidd.1
new file mode 100644 (file)
index 0000000..b186ac2
--- /dev/null
@@ -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 <hciX|bdaddr>
+Local HCI device or BD Address
+.TP
+\fB\-t\fR <timeout>
+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 <bdaddr>
+Connect remote HID device
+.TP
+\fB\-\-kill\fR <bdaddr>
+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 (file)
index 0000000..88944cf
--- /dev/null
@@ -0,0 +1,862 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/hidp.h>
+
+#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));
+       name[0] = '\0';
+
+       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 <hciX|bdaddr>     Local HCI device or BD Address\n"
+               "\t-t <timeout>         Set idle timeout (in minutes)\n"
+               "\t-b <subclass>        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 <bdaddr>   Connect remote HID device\n"
+               "\t--unplug <bdaddr>    Unplug the HID connection\n"
+               "\t--kill <bdaddr>      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 (file)
index 0000000..0536967
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 (file)
index 0000000..3b3aeb5
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <sys/types.h>
+#include <errno.h>
+#include <signal.h>
+
+#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 (file)
index 0000000..ae88c0c
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <setjmp.h>
+#include <string.h>
+
+#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 (file)
index 0000000..4603b8b
--- /dev/null
@@ -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 <options>
+.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 <bdaddr>
+Create PAN connection
+.TP
+\fB\-\-search\fR \fB\-Q[duration]\fR
+Search and connect
+.TP
+\fB\-\-kill\fR \fB\-k\fR <bdaddr>
+Kill PAN connection
+.TP
+\fB\-\-killall\fR \fB\-K\fR
+Kill all PAN connections
+.TP
+\fB\-\-role\fR \fB\-r\fR <role>
+Local PAN role (PANU, NAP, GN)
+.TP
+\fB\-\-service\fR \fB\-d\fR <role>
+Remote PAN service (PANU, NAP, GN)
+.TP
+\fB\-\-ethernet\fR \fB\-e\fR <name>
+Network interface name
+.TP
+\fB\-\-device\fR \fB\-i\fR <bdaddr>
+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 <pidfile>\fR
+Create PID file
+.TP
+\fB\-\-devup\fR \fB\-u <script>\fR
+Script to run when interface comes up
+.TP
+\fB\-\-devdown\fR \fB\-o <script>\fR
+Script to run when interface comes down
+.TP
+\fB\-\-autozap\fR \fB\-z\fR
+Disconnect automatically on exit
+
+.SH SCRIPTS
+The devup/devdown script will be called with bluetooth device as first argument
+and bluetooth destination address as second argument.
diff --git a/compat/pand.c b/compat/pand.c
new file mode 100644 (file)
index 0000000..b82650e
--- /dev/null
@@ -0,0 +1,811 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/bnep.h>
+#include <bluetooth/hidp.h>
+
+#include "sdp.h"
+#include "pand.h"
+
+#ifdef NEED_PPOLL
+#include "ppoll.h"
+#endif
+
+static uint16_t role    = BNEP_SVC_PANU;       /* Local role (ie service) */
+static uint16_t service = BNEP_SVC_NAP;                /* Remote service */
+
+static int detach = 1;
+static int persist;
+static int use_sdp = 1;
+static int use_cache;
+static int link_mode = 0;
+static int cleanup;
+static int search_duration = 10;
+
+static struct {
+       int      valid;
+       char     dst[40];
+       bdaddr_t bdaddr;
+} cache;
+
+static char netdev[16] = "bnep%d";
+static char *pidfile = NULL;
+static char *devupcmd = NULL;
+static char *devdowncmd = NULL;
+
+static bdaddr_t src_addr = *BDADDR_ANY;
+static int src_dev = -1;
+
+static volatile int terminate;
+
+static void do_kill(char *dst);
+
+enum {
+       NONE,
+       SHOW,
+       LISTEN,
+       CONNECT,
+       KILL
+} modes;
+
+struct script_arg {
+       char    dev[20];
+       char    dst[20];
+       int     sk;
+       int     nsk;
+};
+
+static void run_script(char *script, char *dev, char *dst, int sk, int nsk)
+{
+       char *argv[4];
+       struct sigaction sa;
+
+       if (!script)
+               return;
+
+       if (access(script, R_OK | X_OK))
+               return;
+
+       if (fork())
+               return;
+
+       if (sk >= 0)
+               close(sk);
+
+       if (nsk >= 0)
+               close(nsk);
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = SIG_DFL;
+       sigaction(SIGCHLD, &sa, NULL);
+       sigaction(SIGPIPE, &sa, NULL);
+
+       argv[0] = script;
+       argv[1] = dev;
+       argv[2] = dst;
+       argv[3] = NULL;
+
+       execv(script, argv);
+
+       exit(1);
+}
+
+/* Wait for disconnect or error condition on the socket */
+static int w4_hup(int sk, struct script_arg *down_cmd)
+{
+       struct pollfd pf;
+       sigset_t sigs;
+       int n;
+
+       sigfillset(&sigs);
+       sigdelset(&sigs, SIGCHLD);
+       sigdelset(&sigs, SIGPIPE);
+       sigdelset(&sigs, SIGTERM);
+       sigdelset(&sigs, SIGINT);
+       sigdelset(&sigs, SIGHUP);
+
+       while (!terminate) {
+               pf.fd = sk;
+               pf.events = POLLERR | POLLHUP;
+
+               n = ppoll(&pf, 1, NULL, &sigs);
+
+               if (n < 0) {
+                       if (errno == EINTR || errno == EAGAIN)
+                               continue;
+
+                       syslog(LOG_ERR, "Poll failed. %s(%d)",
+                                               strerror(errno), errno);
+
+                       return 1;
+               }
+
+               if (n) {
+                       int err = 0;
+                       socklen_t olen = sizeof(err);
+
+                       getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &olen);
+
+                       syslog(LOG_INFO, "%s disconnected%s%s", netdev,
+                               err ? " : " : "", err ? strerror(err) : "");
+
+                       if (down_cmd)
+                               run_script(devdowncmd,
+                                               down_cmd->dev, down_cmd->dst,
+                                               down_cmd->sk, down_cmd->nsk);
+
+                       close(sk);
+
+                       return 0;
+               }
+       }
+
+       return 0;
+}
+
+static int do_listen(void)
+{
+       struct l2cap_options l2o;
+       struct sockaddr_l2 l2a;
+       socklen_t olen;
+       int sk, lm;
+
+       if (use_sdp)
+               bnep_sdp_register(&src_addr, role);
+
+       /* Create L2CAP socket and bind it to PSM BNEP */
+       sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+       if (sk < 0) {
+               syslog(LOG_ERR, "Cannot create L2CAP socket. %s(%d)",
+                                               strerror(errno), errno);
+               return -1;
+       }
+
+       memset(&l2a, 0, sizeof(l2a));
+       l2a.l2_family = AF_BLUETOOTH;
+       bacpy(&l2a.l2_bdaddr, &src_addr);
+       l2a.l2_psm = htobs(BNEP_PSM);
+
+       if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a))) {
+               syslog(LOG_ERR, "Bind failed. %s(%d)",
+                                               strerror(errno), errno);
+               return -1;
+       }
+
+       /* Setup L2CAP options according to BNEP spec */
+       memset(&l2o, 0, sizeof(l2o));
+       olen = sizeof(l2o);
+       if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &olen) < 0) {
+               syslog(LOG_ERR, "Failed to get L2CAP options. %s(%d)",
+                                               strerror(errno), errno);
+               return -1;
+       }
+
+       l2o.imtu = l2o.omtu = BNEP_MTU;
+       if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) {
+               syslog(LOG_ERR, "Failed to set L2CAP options. %s(%d)",
+                                               strerror(errno), errno);
+               return -1;
+       }
+
+       /* Set link mode */
+       lm = link_mode;
+       if (lm && setsockopt(sk, SOL_L2CAP, L2CAP_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(l2a);
+               char devname[16];
+               int nsk;
+
+               nsk = accept(sk, (struct sockaddr *) &l2a, &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);
+                       continue;
+               }
+
+               strncpy(devname, netdev, 16);
+               devname[15] = '\0';
+
+               if (!bnep_accept_connection(nsk, role, devname)) {
+                       char str[40];
+                       struct script_arg down_cmd;
+
+                       ba2str(&l2a.l2_bdaddr, str);
+
+                       syslog(LOG_INFO, "New connection from %s at %s",
+                                                               str, devname);
+
+                       run_script(devupcmd, devname, str, sk, nsk);
+
+                       memset(&down_cmd, 0, sizeof(struct script_arg));
+                       strncpy(down_cmd.dev, devname, strlen(devname) + 1);
+                       strncpy(down_cmd.dst, str, strlen(str) + 1);
+                       down_cmd.sk = sk;
+                       down_cmd.nsk = nsk;
+                       w4_hup(nsk, &down_cmd);
+               } else {
+                       syslog(LOG_ERR, "Connection failed. %s(%d)",
+                                               strerror(errno), errno);
+               }
+
+               close(nsk);
+               exit(0);
+       }
+
+       if (use_sdp)
+               bnep_sdp_unregister();
+
+       return 0;
+}
+
+/* Connect and initiate BNEP session
+ * Returns:
+ *   -1 - critical error (exit persist mode)
+ *   1  - non critical error
+ *   0  - success
+ */
+static int create_connection(char *dst, bdaddr_t *bdaddr)
+{
+       struct l2cap_options l2o;
+       struct sockaddr_l2 l2a;
+       socklen_t olen;
+       int sk, r = 0;
+       struct script_arg down_cmd;
+
+       syslog(LOG_INFO, "Connecting to %s", dst);
+
+       sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+       if (sk < 0) {
+               syslog(LOG_ERR, "Cannot create L2CAP socket. %s(%d)",
+                                               strerror(errno), errno);
+               return -1;
+       }
+
+       /* Setup L2CAP options according to BNEP spec */
+       memset(&l2o, 0, sizeof(l2o));
+       olen = sizeof(l2o);
+       getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &olen);
+       l2o.imtu = l2o.omtu = BNEP_MTU;
+       setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o));
+
+       memset(&l2a, 0, sizeof(l2a));
+       l2a.l2_family = AF_BLUETOOTH;
+       bacpy(&l2a.l2_bdaddr, &src_addr);
+
+       if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a)))
+               syslog(LOG_ERR, "Bind failed. %s(%d)",
+                                               strerror(errno), errno);
+
+       memset(&l2a, 0, sizeof(l2a));
+       l2a.l2_family = AF_BLUETOOTH;
+       bacpy(&l2a.l2_bdaddr, bdaddr);
+       l2a.l2_psm = htobs(BNEP_PSM);
+
+       if (!connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) &&
+                       !bnep_create_connection(sk, role, service, netdev)) {
+
+               syslog(LOG_INFO, "%s connected", netdev);
+
+               run_script(devupcmd, netdev, dst, sk, -1);
+
+               if (persist || devdowncmd) {
+                               memset(&down_cmd, 0, sizeof(struct script_arg));
+                               strncpy(down_cmd.dev, netdev, strlen(netdev) + 1);
+                               strncpy(down_cmd.dst, dst, strlen(dst) + 1);
+                               down_cmd.sk = sk;
+                               down_cmd.nsk = -1;
+                               w4_hup(sk, &down_cmd);
+
+                       if (terminate && cleanup) {
+                               syslog(LOG_INFO, "Disconnecting from %s.", dst);
+                               do_kill(dst);
+                       }
+               }
+
+               r = 0;
+       } else {
+               syslog(LOG_ERR, "Connect to %s failed. %s(%d)",
+                                               dst, strerror(errno), errno);
+               r = 1;
+       }
+
+       close(sk);
+
+       if (use_cache) {
+               if (!r) {
+                       /* Succesesful connection, validate cache */
+                       strcpy(cache.dst, dst);
+                       bacpy(&cache.bdaddr, bdaddr);
+                       cache.valid = use_cache;
+               } else
+                       cache.valid--;
+       }
+
+       return r;
+}
+
+/* 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 > 0) {
+                       /* Use cached bdaddr */
+                       r = create_connection(cache.dst, &cache.bdaddr);
+                       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);
+
+                       if (use_sdp) {
+                               syslog(LOG_INFO, "Searching for %s on %s",
+                                               bnep_svc2str(service), dst);
+
+                               if (bnep_sdp_search(&src_addr, &ii[i].bdaddr, service) <= 0)
+                                       continue;
+                       }
+
+                       r = create_connection(dst, &ii[i].bdaddr);
+                       if (r < 0) {
+                               terminate = 1;
+                               break;
+                       }
+               }
+               bt_free(ii);
+       } while (!terminate && persist);
+
+       return r;
+}
+
+static void do_show(void)
+{
+       bnep_show_connections();
+}
+
+static void do_kill(char *dst)
+{
+       if (dst) {
+               bdaddr_t *ba = strtoba(dst);
+               bnep_kill_connection((void *) ba);
+               free(ba);
+       } else {
+               bnep_kill_all_connections();
+       }
+}
+
+static void sig_hup(int sig)
+{
+       return;
+}
+
+static void sig_term(int sig)
+{
+       terminate = 1;
+}
+
+static int write_pidfile(void)
+{
+       int fd;
+       FILE *f;
+       pid_t pid;
+
+       do {
+               fd = open(pidfile, O_WRONLY|O_TRUNC|O_CREAT|O_EXCL, 0644);
+               if (fd == -1) {
+                       /* Try to open the file for read. */
+                       fd = open(pidfile, O_RDONLY);
+                       if (fd < 0) {
+                               syslog(LOG_ERR, "Could not read old pidfile: %s(%d)",
+                                                       strerror(errno), errno);
+                               return -1;
+                       }
+
+                       /* We're already running; send a SIGHUP (we presume that they
+                        * are calling ifup for a reason, so they probably want to
+                        * rescan) and then exit cleanly and let things go on in the
+                        * background.  Muck with the filename so that we don't go
+                        * deleting the pid file for the already-running instance.
+                        */
+                       f = fdopen(fd, "r");
+                       if (!f) {
+                               syslog(LOG_ERR, "Could not fdopen old pidfile: %s(%d)",
+                                                       strerror(errno), errno);
+                               close(fd);
+                               return -1;
+                       }
+
+                       pid = 0;
+                       if (fscanf(f, "%d", &pid) != 1)
+                               pid = 0;
+                       fclose(f);
+
+                       if (pid) {
+                               /* Try to kill it. */
+                               if (kill(pid, SIGHUP) == -1) {
+                                       /* No such pid; remove the bogus pid file. */
+                                       syslog(LOG_INFO, "Removing stale pidfile");
+                                       unlink(pidfile);
+                                       fd = -1;
+                               } else {
+                                       /* Got it.  Don't mess with the pid file on
+                                        * our way out. */
+                                       syslog(LOG_INFO, "Signalling existing process %d and exiting\n", pid);
+                                       pidfile = NULL;
+                                       return -1;
+                               }
+                       }
+               }
+       } while(fd == -1);
+
+       f = fdopen(fd, "w");
+       if (!f) {
+               syslog(LOG_ERR, "Could not fdopen new pidfile: %s(%d)",
+                                               strerror(errno), errno);
+               close(fd);
+               unlink(pidfile);
+               return -1;
+       }
+
+       fprintf(f, "%d\n", getpid());
+       fclose(f);
+
+       return 0;
+}
+
+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' },
+       { "role",     1, 0, 'r' },
+       { "service",  1, 0, 'd' },
+       { "ethernet", 1, 0, 'e' },
+       { "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' },
+       { "pidfile",  1, 0, 'P' },
+       { "devup",    1, 0, 'u' },
+       { "devdown",  1, 0, 'o' },
+       { "autozap",  0, 0, 'z' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *main_sopts = "hsc:k:Kr:d:e:i:lnp::DQ::AESMC::P:u:o:z";
+
+static const char *main_help =
+       "Bluetooth PAN daemon version %s\n"
+       "Usage:\n"
+       "\tpand <options>\n"
+       "Options:\n"
+       "\t--show --list -l          Show active PAN connections\n"
+       "\t--listen -s               Listen for PAN connections\n"
+       "\t--connect -c <bdaddr>     Create PAN connection\n"
+       "\t--autozap -z              Disconnect automatically on exit\n"
+       "\t--search -Q[duration]     Search and connect\n"
+       "\t--kill -k <bdaddr>        Kill PAN connection\n"
+       "\t--killall -K              Kill all PAN connections\n"
+       "\t--role -r <role>          Local PAN role (PANU, NAP, GN)\n"
+       "\t--service -d <role>       Remote PAN service (PANU, NAP, GN)\n"
+       "\t--ethernet -e <name>      Network interface name\n"
+       "\t--device -i <bdaddr>      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--cache -C[valid]         Cache addresses\n"
+       "\t--pidfile -P <pidfile>    Create PID file\n"
+       "\t--devup -u <script>       Script to run when interface comes up\n"
+       "\t--devdown -o <script>     Script to run when interface comes down\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;
+                       break;
+
+               case 'c':
+                       mode = CONNECT;
+                       dst  = strdup(optarg);
+                       break;
+
+               case 'Q':
+                       mode = CONNECT;
+                       if (optarg)
+                               search_duration = atoi(optarg);
+                       break;
+
+               case 'k':
+                       mode = KILL;
+                       detach = 0;
+                       dst  = strdup(optarg);
+                       break;
+
+               case 'K':
+                       mode = KILL;
+                       detach = 0;
+                       break;
+
+               case 'i':
+                       src = strdup(optarg);
+                       break;
+
+               case 'r':
+                       bnep_str2svc(optarg, &role);
+                       break;
+
+               case 'd':
+                       bnep_str2svc(optarg, &service);
+                       break;
+
+               case 'D':
+                       use_sdp = 0;
+                       break;
+
+               case 'A':
+                       link_mode |= L2CAP_LM_AUTH;
+                       break;
+
+               case 'E':
+                       link_mode |= L2CAP_LM_ENCRYPT;
+                       break;
+
+               case 'S':
+                       link_mode |= L2CAP_LM_SECURE;
+                       break;
+
+               case 'M':
+                       link_mode |= L2CAP_LM_MASTER;
+                       break;
+
+               case 'e':
+                       strncpy(netdev, optarg, 16);
+                       netdev[15] = '\0';
+                       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 'P':
+                       pidfile = strdup(optarg);
+                       break;
+
+               case 'u':
+                       devupcmd = strdup(optarg);
+                       break;
+
+               case 'o':
+                       devdowncmd = strdup(optarg);
+                       break;
+
+               case 'z':
+                       cleanup = 1;
+                       break;
+
+               case 'h':
+               default:
+                       printf(main_help, VERSION);
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (bnep_init()) {
+               free(dst);
+               return -1;
+       }
+
+       /* Check non daemon modes first */
+       switch (mode) {
+       case SHOW:
+               do_show();
+               free(dst);
+               return 0;
+
+       case KILL:
+               do_kill(dst);
+               free(dst);
+               return 0;
+
+       case NONE:
+               printf(main_help, VERSION);
+               free(dst);
+               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_hup;
+       sigaction(SIGHUP, &sa, NULL);
+
+       sa.sa_handler = sig_term;
+       sigaction(SIGTERM, &sa, NULL);
+       sigaction(SIGINT,  &sa, NULL);
+
+       if (detach && daemon(0, 0)) {
+               perror("Can't start daemon");
+               exit(1);
+       }
+
+       openlog("pand", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
+       syslog(LOG_INFO, "Bluetooth PAN 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);
+                       free(dst);
+                       return -1;
+               }
+       }
+
+       if (pidfile && write_pidfile()) {
+               free(dst);
+               return -1;
+       }
+
+       if (dst) {
+               /* Disable cache invalidation */
+               use_cache = 0;
+
+               strncpy(cache.dst, dst, sizeof(cache.dst) - 1);
+               str2ba(dst, &cache.bdaddr);
+               cache.valid = 1;
+               free(dst);
+       }
+
+       switch (mode) {
+       case CONNECT:
+               do_connect();
+               break;
+
+       case LISTEN:
+               do_listen();
+               break;
+       }
+
+       if (pidfile)
+               unlink(pidfile);
+
+       return 0;
+}
diff --git a/compat/pand.h b/compat/pand.h
new file mode 100644 (file)
index 0000000..08b5bdc
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 bnep_init(void);
+int bnep_cleanup(void);
+
+int bnep_str2svc(char *svc, uint16_t *uuid);
+char *bnep_svc2str(uint16_t uuid);
+
+int bnep_show_connections(void);
+int bnep_kill_connection(uint8_t *dst);
+int bnep_kill_all_connections(void);
+
+int bnep_accept_connection(int sk, uint16_t role, char *dev);
+int bnep_create_connection(int sk, uint16_t role, uint16_t svc, char *dev);
diff --git a/compat/sdp.c b/compat/sdp.c
new file mode 100644 (file)
index 0000000..f384844
--- /dev/null
@@ -0,0 +1,720 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/hidp.h>
+#include <bluetooth/bnep.h>
+
+#include "textfile.h"
+#include "sdp.h"
+
+static sdp_record_t *record = NULL;
+static sdp_session_t *session = NULL;
+
+static void add_lang_attr(sdp_record_t *r)
+{
+       sdp_lang_attr_t base_lang;
+       sdp_list_t *langs = 0;
+
+       /* UTF-8 MIBenum (http://www.iana.org/assignments/character-sets) */
+       base_lang.code_ISO639 = (0x65 << 8) | 0x6e;
+       base_lang.encoding = 106;
+       base_lang.base_offset = SDP_PRIMARY_LANG_BASE;
+       langs = sdp_list_append(0, &base_lang);
+       sdp_set_lang_attr(r, langs);
+       sdp_list_free(langs, 0);
+}
+
+static void epox_endian_quirk(unsigned char *data, int size)
+{
+       /* USAGE_PAGE (Keyboard)        05 07
+        * USAGE_MINIMUM (0)            19 00
+        * USAGE_MAXIMUM (65280)        2A 00 FF   <= must be FF 00
+        * LOGICAL_MINIMUM (0)          15 00
+        * LOGICAL_MAXIMUM (65280)      26 00 FF   <= must be FF 00
+        */
+       unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff,
+                                               0x15, 0x00, 0x26, 0x00, 0xff };
+       unsigned int i;
+
+       if (!data)
+               return;
+
+       for (i = 0; i < size - sizeof(pattern); i++) {
+               if (!memcmp(data + i, pattern, sizeof(pattern))) {
+                       data[i + 5] = 0xff;
+                       data[i + 6] = 0x00;
+                       data[i + 10] = 0xff;
+                       data[i + 11] = 0x00;
+               }
+       }
+}
+
+static int store_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req)
+{
+       char filename[PATH_MAX + 1], addr[18], *str, *desc;
+       int i, err, size;
+
+       ba2str(src, addr);
+       create_name(filename, PATH_MAX, STORAGEDIR, addr, "hidd");
+
+       size = 15 + 3 + 3 + 5 + (req->rd_size * 2) + 1 + 9 + strlen(req->name) + 2;
+       str = malloc(size);
+       if (!str)
+               return -ENOMEM;
+
+       desc = malloc((req->rd_size * 2) + 1);
+       if (!desc) {
+               free(str);
+               return -ENOMEM;
+       }
+
+       memset(desc, 0, (req->rd_size * 2) + 1);
+       for (i = 0; i < req->rd_size; i++)
+               sprintf(desc + (i * 2), "%2.2X", req->rd_data[i]);
+
+       snprintf(str, size - 1, "%04X:%04X:%04X %02X %02X %04X %s %08X %s",
+                       req->vendor, req->product, req->version,
+                       req->subclass, req->country, req->parser, desc,
+                       req->flags, req->name);
+
+       free(desc);
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dst, addr);
+       err = textfile_put(filename, addr, str);
+
+       free(str);
+
+       return err;
+}
+
+int get_stored_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req)
+{
+       char filename[PATH_MAX + 1], addr[18], tmp[3], *str, *desc;
+       unsigned int vendor, product, version, subclass, country, parser, pos;
+       int i;
+
+       desc = malloc(4096);
+       if (!desc)
+               return -ENOMEM;
+
+       memset(desc, 0, 4096);
+
+       ba2str(src, addr);
+       create_name(filename, PATH_MAX, STORAGEDIR, addr, "hidd");
+
+       ba2str(dst, addr);
+       str = textfile_get(filename, addr);
+       if (!str) {
+               free(desc);
+               return -EIO;
+       }
+
+       sscanf(str, "%04X:%04X:%04X %02X %02X %04X %4095s %08X %n",
+                       &vendor, &product, &version, &subclass, &country,
+                       &parser, desc, &req->flags, &pos);
+
+
+       req->vendor   = vendor;
+       req->product  = product;
+       req->version  = version;
+       req->subclass = subclass;
+       req->country  = country;
+       req->parser   = parser;
+
+       snprintf(req->name, 128, "%s", str + pos);
+
+       free(str);
+       req->rd_size = strlen(desc) / 2;
+       req->rd_data = malloc(req->rd_size);
+       if (!req->rd_data) {
+               free(desc);
+               return -ENOMEM;
+       }
+
+       memset(tmp, 0, sizeof(tmp));
+       for (i = 0; i < req->rd_size; i++) {
+               memcpy(tmp, desc + (i * 2), 2);
+               req->rd_data[i] = (uint8_t) strtol(tmp, NULL, 16);
+       }
+
+       free(desc);
+
+       return 0;
+}
+
+int get_sdp_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req)
+{
+       struct sockaddr_l2 addr;
+       socklen_t addrlen;
+       bdaddr_t bdaddr;
+       uint32_t range = 0x0000ffff;
+       sdp_session_t *s;
+       sdp_list_t *search, *attrid, *pnp_rsp, *hid_rsp;
+       sdp_record_t *rec;
+       sdp_data_t *pdlist, *pdlist2;
+       uuid_t svclass;
+       int err;
+
+       s = sdp_connect(src, dst, SDP_RETRY_IF_BUSY | SDP_WAIT_ON_CLOSE);
+       if (!s)
+               return -1;
+
+       sdp_uuid16_create(&svclass, PNP_INFO_SVCLASS_ID);
+       search = sdp_list_append(NULL, &svclass);
+       attrid = sdp_list_append(NULL, &range);
+
+       err = sdp_service_search_attr_req(s, search,
+                                       SDP_ATTR_REQ_RANGE, attrid, &pnp_rsp);
+
+       sdp_list_free(search, NULL);
+       sdp_list_free(attrid, NULL);
+
+       sdp_uuid16_create(&svclass, HID_SVCLASS_ID);
+       search = sdp_list_append(NULL, &svclass);
+       attrid = sdp_list_append(NULL, &range);
+
+       err = sdp_service_search_attr_req(s, search,
+                                       SDP_ATTR_REQ_RANGE, attrid, &hid_rsp);
+
+       sdp_list_free(search, NULL);
+       sdp_list_free(attrid, NULL);
+
+       memset(&addr, 0, sizeof(addr));
+       addrlen = sizeof(addr);
+
+       if (getsockname(s->sock, (struct sockaddr *) &addr, &addrlen) < 0)
+               bacpy(&bdaddr, src);
+       else
+               bacpy(&bdaddr, &addr.l2_bdaddr);
+
+       sdp_close(s);
+
+       if (err || !hid_rsp)
+               return -1;
+
+       if (pnp_rsp) {
+               rec = (sdp_record_t *) pnp_rsp->data;
+
+               pdlist = sdp_data_get(rec, 0x0201);
+               req->vendor = pdlist ? pdlist->val.uint16 : 0x0000;
+
+               pdlist = sdp_data_get(rec, 0x0202);
+               req->product = pdlist ? pdlist->val.uint16 : 0x0000;
+
+               pdlist = sdp_data_get(rec, 0x0203);
+               req->version = pdlist ? pdlist->val.uint16 : 0x0000;
+
+               sdp_record_free(rec);
+       }
+
+       rec = (sdp_record_t *) hid_rsp->data;
+
+       pdlist2 = sdp_data_get(rec, 0x0100);
+       if (pdlist2)
+               strncpy(req->name, pdlist2->val.str, sizeof(req->name) - 1);
+       else {
+               pdlist = sdp_data_get(rec, 0x0101);
+               pdlist2 = sdp_data_get(rec, 0x0102);
+               if (pdlist) {
+                       if (pdlist2) {
+                               if (strncmp(pdlist->val.str, pdlist2->val.str, 5)) {
+                                       strncpy(req->name, pdlist2->val.str, sizeof(req->name) - 1);
+                                       strcat(req->name, " ");
+                               }
+                               strncat(req->name, pdlist->val.str,
+                                               sizeof(req->name) - strlen(req->name));
+                       } else
+                               strncpy(req->name, pdlist->val.str, sizeof(req->name) - 1);
+               }
+       }
+
+       pdlist = sdp_data_get(rec, 0x0201);
+       req->parser = pdlist ? pdlist->val.uint16 : 0x0100;
+
+       pdlist = sdp_data_get(rec, 0x0202);
+       req->subclass = pdlist ? pdlist->val.uint8 : 0;
+
+       pdlist = sdp_data_get(rec, 0x0203);
+       req->country = pdlist ? pdlist->val.uint8 : 0;
+
+       pdlist = sdp_data_get(rec, 0x0206);
+       if (pdlist) {
+               pdlist = pdlist->val.dataseq;
+               pdlist = pdlist->val.dataseq;
+               pdlist = pdlist->next;
+
+               req->rd_data = malloc(pdlist->unitSize);
+               if (req->rd_data) {
+                       memcpy(req->rd_data, (unsigned char *) pdlist->val.str, pdlist->unitSize);
+                       req->rd_size = pdlist->unitSize;
+                       epox_endian_quirk(req->rd_data, req->rd_size);
+               }
+       }
+
+       sdp_record_free(rec);
+
+       if (bacmp(&bdaddr, BDADDR_ANY))
+               store_device_info(&bdaddr, dst, req);
+
+       return 0;
+}
+
+int get_alternate_device_info(const bdaddr_t *src, const bdaddr_t *dst, uint16_t *uuid, uint8_t *channel, char *name, size_t len)
+{
+       uint16_t attr1 = SDP_ATTR_PROTO_DESC_LIST;
+       uint16_t attr2 = SDP_ATTR_SVCNAME_PRIMARY;
+       sdp_session_t *s;
+       sdp_list_t *search, *attrid, *rsp;
+       uuid_t svclass;
+       int err;
+
+       s = sdp_connect(src, dst, SDP_RETRY_IF_BUSY | SDP_WAIT_ON_CLOSE);
+       if (!s)
+               return -1;
+
+       sdp_uuid16_create(&svclass, HEADSET_SVCLASS_ID);
+       search = sdp_list_append(NULL, &svclass);
+       attrid = sdp_list_append(NULL, &attr1);
+       attrid = sdp_list_append(attrid, &attr2);
+
+       err = sdp_service_search_attr_req(s, search,
+                                       SDP_ATTR_REQ_INDIVIDUAL, attrid, &rsp);
+
+       sdp_list_free(search, NULL);
+       sdp_list_free(attrid, NULL);
+
+       if (err <= 0) {
+               sdp_uuid16_create(&svclass, SERIAL_PORT_SVCLASS_ID);
+               search = sdp_list_append(NULL, &svclass);
+               attrid = sdp_list_append(NULL, &attr1);
+               attrid = sdp_list_append(attrid, &attr2);
+
+               err = sdp_service_search_attr_req(s, search,
+                                       SDP_ATTR_REQ_INDIVIDUAL, attrid, &rsp);
+
+               sdp_list_free(search, NULL);
+               sdp_list_free(attrid, NULL);
+
+               if (err < 0) {
+                       sdp_close(s);
+                       return err;
+               }
+
+               if (uuid)
+                       *uuid = SERIAL_PORT_SVCLASS_ID;
+       } else {
+               if (uuid)
+                       *uuid = HEADSET_SVCLASS_ID;
+       }
+
+       sdp_close(s);
+
+       for (; rsp; rsp = rsp->next) {
+               sdp_record_t *rec = (sdp_record_t *) rsp->data;
+               sdp_list_t *protos;
+
+               sdp_get_service_name(rec, name, len);
+
+               if (!sdp_get_access_protos(rec, &protos)) {
+                       uint8_t ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+                       if (ch > 0) {
+                               if (channel)
+                                       *channel = ch;
+                               return 0;
+                       }
+               }
+
+               sdp_record_free(rec);
+       }
+
+       return -EIO;
+}
+
+void bnep_sdp_unregister(void)
+{
+       if (record && sdp_record_unregister(session, record))
+               syslog(LOG_ERR, "Service record unregistration failed.");
+
+       sdp_close(session);
+}
+
+int bnep_sdp_register(bdaddr_t *device, uint16_t role)
+{
+       sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto;
+       uuid_t root_uuid, pan, l2cap, bnep;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *proto[2];
+       sdp_data_t *v, *p;
+       uint16_t psm = 15, version = 0x0100;
+       uint16_t security_desc = 0;
+       uint16_t net_access_type = 0xfffe;
+       uint32_t max_net_access_rate = 0;
+       char *name = "BlueZ PAN";
+       char *desc = "BlueZ PAN Service";
+       int status;
+
+       session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0);
+       if (!session) {
+               syslog(LOG_ERR, "Failed to connect to the local SDP server. %s(%d)",
+                                                       strerror(errno), errno);
+               return -1;
+       }
+
+       record = sdp_record_alloc();
+       if (!record) {
+               syslog(LOG_ERR, "Failed to allocate service record %s(%d)",
+                                                       strerror(errno), errno);
+               sdp_close(session);
+               return -1;
+       }
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(record, root);
+       sdp_list_free(root, 0);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap);
+       p = sdp_data_alloc(SDP_UINT16, &psm);
+       proto[0] = sdp_list_append(proto[0], p);
+       apseq    = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&bnep, BNEP_UUID);
+       proto[1] = sdp_list_append(NULL, &bnep);
+       v = sdp_data_alloc(SDP_UINT16, &version);
+       proto[1] = sdp_list_append(proto[1], v);
+
+       /* Supported protocols */
+       {
+               uint16_t ptype[4] = {
+                       0x0800,  /* IPv4 */
+                       0x0806,  /* ARP */
+               };
+               sdp_data_t *head, *pseq;
+               int p;
+
+               for (p = 0, head = NULL; p < 2; p++) {
+                       sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]);
+                       if (head)
+                               sdp_seq_append(head, data);
+                       else
+                               head = data;
+               }
+               pseq = sdp_data_alloc(SDP_SEQ16, head);
+               proto[1] = sdp_list_append(proto[1], pseq);
+       }
+
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(NULL, apseq);
+       sdp_set_access_protos(record, aproto);
+
+       add_lang_attr(record);
+
+       sdp_list_free(proto[0], NULL);
+       sdp_list_free(proto[1], NULL);
+       sdp_list_free(apseq, NULL);
+       sdp_list_free(aproto, NULL);
+       sdp_data_free(p);
+       sdp_data_free(v);
+       sdp_attr_add_new(record, SDP_ATTR_SECURITY_DESC, SDP_UINT16, &security_desc);
+
+       switch (role) {
+       case BNEP_SVC_NAP:
+               sdp_uuid16_create(&pan, NAP_SVCLASS_ID);
+               svclass = sdp_list_append(NULL, &pan);
+               sdp_set_service_classes(record, svclass);
+
+               sdp_uuid16_create(&profile[0].uuid, NAP_PROFILE_ID);
+               profile[0].version = 0x0100;
+               pfseq = sdp_list_append(NULL, &profile[0]);
+               sdp_set_profile_descs(record, pfseq);
+
+               sdp_set_info_attr(record, "Network Access Point", name, desc);
+
+               sdp_attr_add_new(record, SDP_ATTR_NET_ACCESS_TYPE, SDP_UINT16, &net_access_type);
+               sdp_attr_add_new(record, SDP_ATTR_MAX_NET_ACCESSRATE, SDP_UINT32, &max_net_access_rate);
+               break;
+
+       case BNEP_SVC_GN:
+               sdp_uuid16_create(&pan, GN_SVCLASS_ID);
+               svclass = sdp_list_append(NULL, &pan);
+               sdp_set_service_classes(record, svclass);
+
+               sdp_uuid16_create(&profile[0].uuid, GN_PROFILE_ID);
+               profile[0].version = 0x0100;
+               pfseq = sdp_list_append(NULL, &profile[0]);
+               sdp_set_profile_descs(record, pfseq);
+
+               sdp_set_info_attr(record, "Group Network Service", name, desc);
+               break;
+
+       case BNEP_SVC_PANU:
+               sdp_uuid16_create(&pan, PANU_SVCLASS_ID);
+               svclass = sdp_list_append(NULL, &pan);
+               sdp_set_service_classes(record, svclass);
+               sdp_list_free(svclass, 0);
+
+               sdp_uuid16_create(&profile[0].uuid, PANU_PROFILE_ID);
+               profile[0].version = 0x0100;
+               pfseq = sdp_list_append(NULL, &profile[0]);
+               sdp_set_profile_descs(record, pfseq);
+               sdp_list_free(pfseq, 0);
+
+               sdp_set_info_attr(record, "PAN User", name, desc);
+               break;
+       }
+
+       status = sdp_device_record_register(session, device, record, 0);
+       if (status) {
+               syslog(LOG_ERR, "SDP registration failed.");
+               sdp_record_free(record); record = NULL;
+               sdp_close(session);
+               return -1;
+       }
+
+       return 0;
+}
+
+/* Search for PAN service.
+ * Returns 1 if service is found and 0 otherwise. */
+int bnep_sdp_search(bdaddr_t *src, bdaddr_t *dst, uint16_t service)
+{
+       sdp_list_t *srch, *rsp = NULL;
+       sdp_session_t *s;
+       uuid_t svclass;
+       int err;
+
+       switch (service) {
+       case BNEP_SVC_PANU:
+               sdp_uuid16_create(&svclass, PANU_SVCLASS_ID);
+               break;
+       case BNEP_SVC_NAP:
+               sdp_uuid16_create(&svclass, NAP_SVCLASS_ID);
+               break;
+       case BNEP_SVC_GN:
+               sdp_uuid16_create(&svclass, GN_SVCLASS_ID);
+               break;
+       }
+
+       srch = sdp_list_append(NULL, &svclass);
+
+       s = sdp_connect(src, dst, 0);
+       if (!s) {
+               syslog(LOG_ERR, "Failed to connect to the SDP server. %s(%d)",
+                                                       strerror(errno), errno);
+               return 0;
+       }
+
+       err = sdp_service_search_req(s, srch, 1, &rsp);
+       sdp_close(s);
+
+       /* Assume that search is successeful
+        * if at least one record is found */
+       if (!err && sdp_list_len(rsp))
+               return 1;
+
+       return 0;
+}
+
+static unsigned char async_uuid[] = {  0x03, 0x50, 0x27, 0x8F, 0x3D, 0xCA, 0x4E, 0x62,
+                                       0x83, 0x1D, 0xA4, 0x11, 0x65, 0xFF, 0x90, 0x6C };
+
+void dun_sdp_unregister(void)
+{
+       if (record && sdp_record_unregister(session, record))
+               syslog(LOG_ERR, "Service record unregistration failed.");
+       sdp_close(session);
+}
+
+int dun_sdp_register(bdaddr_t *device, uint8_t channel, int type)
+{
+       sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto;
+       uuid_t root_uuid, l2cap, rfcomm, dun;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *proto[2];
+       int status;
+
+       session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0);
+       if (!session) {
+               syslog(LOG_ERR, "Failed to connect to the local SDP server. %s(%d)",
+                               strerror(errno), errno);
+               return -1;
+       }
+
+       record = sdp_record_alloc();
+       if (!record) {
+               syslog(LOG_ERR, "Failed to alloc service record");
+               return -1;
+       }
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(record, root);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap);
+       apseq    = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
+       proto[1] = sdp_list_append(NULL, &rfcomm);
+       proto[1] = sdp_list_append(proto[1], sdp_data_alloc(SDP_UINT8, &channel));
+       apseq    = sdp_list_append(apseq, proto[1]);
+
+       aproto   = sdp_list_append(NULL, apseq);
+       sdp_set_access_protos(record, aproto);
+
+       switch (type) {
+       case MROUTER:
+               sdp_uuid16_create(&dun, SERIAL_PORT_SVCLASS_ID);
+               break;
+       case ACTIVESYNC:
+               sdp_uuid128_create(&dun, (void *) async_uuid);
+               break;
+       case DIALUP:
+               sdp_uuid16_create(&dun, DIALUP_NET_SVCLASS_ID);
+               break;
+       default:
+               sdp_uuid16_create(&dun, LAN_ACCESS_SVCLASS_ID);
+               break;
+       }
+
+       svclass = sdp_list_append(NULL, &dun);
+       sdp_set_service_classes(record, svclass);
+
+       switch (type) {
+       case LANACCESS:
+               sdp_uuid16_create(&profile[0].uuid, LAN_ACCESS_PROFILE_ID);
+               profile[0].version = 0x0100;
+               pfseq = sdp_list_append(NULL, &profile[0]);
+               sdp_set_profile_descs(record, pfseq);
+               break;
+       case DIALUP:
+               sdp_uuid16_create(&profile[0].uuid, DIALUP_NET_PROFILE_ID);
+               profile[0].version = 0x0100;
+               pfseq = sdp_list_append(NULL, &profile[0]);
+               sdp_set_profile_descs(record, pfseq);
+               break;
+       }
+
+       switch (type) {
+       case MROUTER:
+               sdp_set_info_attr(record, "mRouter", NULL, NULL);
+               break;
+       case ACTIVESYNC:
+               sdp_set_info_attr(record, "ActiveSync", NULL, NULL);
+               break;
+       case DIALUP:
+               sdp_set_info_attr(record, "Dialup Networking", NULL, NULL);
+               break;
+       default:
+               sdp_set_info_attr(record, "LAN Access Point", NULL, NULL);
+               break;
+       }
+
+       status = sdp_device_record_register(session, device, record, 0);
+       if (status) {
+               syslog(LOG_ERR, "SDP registration failed.");
+               sdp_record_free(record);
+               record = NULL;
+               return -1;
+       }
+       return 0;
+}
+
+int dun_sdp_search(bdaddr_t *src, bdaddr_t *dst, int *channel, int type)
+{
+       sdp_session_t *s;
+       sdp_list_t *srch, *attrs, *rsp;
+       uuid_t svclass;
+       uint16_t attr;
+       int err;
+
+       s = sdp_connect(src, dst, 0);
+       if (!s) {
+               syslog(LOG_ERR, "Failed to connect to the SDP server. %s(%d)",
+                               strerror(errno), errno);
+               return -1;
+       }
+
+       switch (type) {
+       case MROUTER:
+               sdp_uuid16_create(&svclass, SERIAL_PORT_SVCLASS_ID);
+               break;
+       case ACTIVESYNC:
+               sdp_uuid128_create(&svclass, (void *) async_uuid);
+               break;
+       case DIALUP:
+               sdp_uuid16_create(&svclass, DIALUP_NET_SVCLASS_ID);
+               break;
+       default:
+               sdp_uuid16_create(&svclass, LAN_ACCESS_SVCLASS_ID);
+               break;
+       }
+
+       srch  = sdp_list_append(NULL, &svclass);
+
+       attr  = SDP_ATTR_PROTO_DESC_LIST;
+       attrs = sdp_list_append(NULL, &attr);
+
+       err = sdp_service_search_attr_req(s, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp);
+
+       sdp_close(s);
+
+       if (err)
+               return 0;
+
+       for(; rsp; rsp = rsp->next) {
+               sdp_record_t *rec = (sdp_record_t *) rsp->data;
+               sdp_list_t *protos;
+
+               if (!sdp_get_access_protos(rec, &protos)) {
+                       int ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+                       if (ch > 0) {
+                               *channel = ch;
+                               return 1;
+                       }
+               }
+       }
+
+       return 0;
+}
diff --git a/compat/sdp.h b/compat/sdp.h
new file mode 100644 (file)
index 0000000..6ca975f
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 LANACCESS      0
+#define MROUTER                1
+#define ACTIVESYNC     2
+#define DIALUP         3
+
+int get_stored_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req);
+int get_sdp_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req);
+int get_alternate_device_info(const bdaddr_t *src, const bdaddr_t *dst, uint16_t *uuid, uint8_t *channel, char *name, size_t len);
+
+int bnep_sdp_search(bdaddr_t *src, bdaddr_t *dst, uint16_t service);
+int bnep_sdp_register(bdaddr_t *device, uint16_t role);
+void bnep_sdp_unregister(void);
+
+int dun_sdp_search(bdaddr_t *src, bdaddr_t *dst, int *channel, int type);
+int dun_sdp_register(bdaddr_t *device, uint8_t channel, int type);
+void dun_sdp_unregister(void);
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..fe2f88c
--- /dev/null
@@ -0,0 +1,60 @@
+AC_PREREQ(2.60)
+AC_INIT(bluez, 4.90)
+
+AM_INIT_AUTOMAKE([foreign subdir-objects])
+AM_CONFIG_HEADER(config.h)
+
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+AM_MAINTAINER_MODE
+
+PKG_PROG_PKG_CONFIG
+
+AC_INIT_BLUEZ
+
+COMPILER_FLAGS
+
+AC_LANG_C
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_PROG_CC_PIE
+AC_PROG_INSTALL
+AC_PROG_YACC
+AM_PROG_LEX
+AM_PROG_MKDIR_P
+
+m4_define([_LT_AC_TAGCONFIG], [])
+m4_ifdef([AC_LIBTOOL_TAGS], [AC_LIBTOOL_TAGS([])])
+
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
+
+AC_FUNC_PPOLL
+
+AC_CHECK_LIB(dl, dlopen, dummy=yes,
+                       AC_MSG_ERROR(dynamic linking loader is required))
+
+AC_PATH_DBUS
+AC_PATH_GLIB
+AC_PATH_ALSA
+AC_PATH_GSTREAMER
+AC_PATH_USB
+AC_PATH_SNDFILE
+AC_PATH_OUI
+AC_PATH_READLINE
+
+AC_ARG_BLUEZ
+
+AC_ARG_ENABLE(capng, AC_HELP_STRING([--enable-capng],
+               [enable capabilities support]), [enable_capng=${enableval}])
+if (test "${enable_capng}" = "yes"); then
+       PKG_CHECK_MODULES(CAPNG, libcap-ng, dummy=yes,
+                               AC_MSG_ERROR(Capabilities library is required))
+       AC_SUBST(CAPNG_CFLAGS)
+       AC_SUBST(CAPNG_LIBS)
+       AC_DEFINE(HAVE_CAPNG, 1, [Define to 1 if you have capabilities library.])
+fi
+
+AC_OUTPUT(Makefile scripts/bluetooth.rules doc/version.xml
+                                       src/bluetoothd.8 bluez.pc)
diff --git a/cups/cups.h b/cups/cups.h
new file mode 100644 (file)
index 0000000..f4e0c01
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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
+ *
+ */
+
+enum {                                 /**** Backend exit codes ****/
+       CUPS_BACKEND_OK = 0,            /* Job completed successfully */
+       CUPS_BACKEND_FAILED = 1,        /* Job failed, use error-policy */
+       CUPS_BACKEND_AUTH_REQUIRED = 2, /* Job failed, authentication required */
+       CUPS_BACKEND_HOLD = 3,          /* Job failed, hold job */
+       CUPS_BACKEND_STOP = 4,          /* Job failed, stop queue */
+       CUPS_BACKEND_CANCEL = 5,        /* Job failed, cancel job */
+       CUPS_BACKEND_RETRY = 6,         /* Failure requires us to retry (BlueZ specific) */
+};
+
+int sdp_search_spp(sdp_session_t *sdp, uint8_t *channel);
+int sdp_search_hcrp(sdp_session_t *sdp, unsigned short *ctrl_psm, unsigned short *data_psm);
+
+int spp_print(bdaddr_t *src, bdaddr_t *dst, uint8_t channel, int fd, int copies, const char *cups_class);
+int hcrp_print(bdaddr_t *src, bdaddr_t *dst, unsigned short ctrl_psm, unsigned short data_psm, int fd, int copies, const char *cups_class);
diff --git a/cups/hcrp.c b/cups/hcrp.c
new file mode 100644 (file)
index 0000000..7aafcdc
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <netinet/in.h>
+
+#include "cups.h"
+
+#define HCRP_PDU_CREDIT_GRANT          0x0001
+#define HCRP_PDU_CREDIT_REQUEST                0x0002
+#define HCRP_PDU_GET_LPT_STATUS                0x0005
+
+#define HCRP_STATUS_FEATURE_UNSUPPORTED        0x0000
+#define HCRP_STATUS_SUCCESS            0x0001
+#define HCRP_STATUS_CREDIT_SYNC_ERROR  0x0002
+#define HCRP_STATUS_GENERIC_FAILURE    0xffff
+
+struct hcrp_pdu_hdr {
+       uint16_t pid;
+       uint16_t tid;
+       uint16_t plen;
+} __attribute__ ((packed));
+#define HCRP_PDU_HDR_SIZE 6
+
+struct hcrp_credit_grant_cp {
+       uint32_t credit;
+} __attribute__ ((packed));
+#define HCRP_CREDIT_GRANT_CP_SIZE 4
+
+struct hcrp_credit_grant_rp {
+       uint16_t status;
+} __attribute__ ((packed));
+#define HCRP_CREDIT_GRANT_RP_SIZE 2
+
+struct hcrp_credit_request_rp {
+       uint16_t status;
+       uint32_t credit;
+} __attribute__ ((packed));
+#define HCRP_CREDIT_REQUEST_RP_SIZE 6
+
+struct hcrp_get_lpt_status_rp {
+       uint16_t status;
+       uint8_t  lpt_status;
+} __attribute__ ((packed));
+#define HCRP_GET_LPT_STATUS_RP_SIZE 3
+
+static int hcrp_credit_grant(int sk, uint16_t tid, uint32_t credit)
+{
+       struct hcrp_pdu_hdr hdr;
+       struct hcrp_credit_grant_cp cp;
+       struct hcrp_credit_grant_rp rp;
+       unsigned char buf[128];
+       int len;
+
+       hdr.pid = htons(HCRP_PDU_CREDIT_GRANT);
+       hdr.tid = htons(tid);
+       hdr.plen = htons(HCRP_CREDIT_GRANT_CP_SIZE);
+       cp.credit = credit;
+       memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE);
+       memcpy(buf + HCRP_PDU_HDR_SIZE, &cp, HCRP_CREDIT_GRANT_CP_SIZE);
+       len = write(sk, buf, HCRP_PDU_HDR_SIZE + HCRP_CREDIT_GRANT_CP_SIZE);
+
+       len = read(sk, buf, sizeof(buf));
+       memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE);
+       memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_CREDIT_GRANT_RP_SIZE);
+
+       if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+static int hcrp_credit_request(int sk, uint16_t tid, uint32_t *credit)
+{
+       struct hcrp_pdu_hdr hdr;
+       struct hcrp_credit_request_rp rp;
+       unsigned char buf[128];
+       int len;
+
+       hdr.pid = htons(HCRP_PDU_CREDIT_REQUEST);
+       hdr.tid = htons(tid);
+       hdr.plen = htons(0);
+       memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE);
+       len = write(sk, buf, HCRP_PDU_HDR_SIZE);
+
+       len = read(sk, buf, sizeof(buf));
+       memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE);
+       memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_CREDIT_REQUEST_RP_SIZE);
+
+       if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (credit)
+               *credit = ntohl(rp.credit);
+
+       return 0;
+}
+
+static int hcrp_get_lpt_status(int sk, uint16_t tid, uint8_t *lpt_status)
+{
+       struct hcrp_pdu_hdr hdr;
+       struct hcrp_get_lpt_status_rp rp;
+       unsigned char buf[128];
+       int len;
+
+       hdr.pid = htons(HCRP_PDU_GET_LPT_STATUS);
+       hdr.tid = htons(tid);
+       hdr.plen = htons(0);
+       memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE);
+       len = write(sk, buf, HCRP_PDU_HDR_SIZE);
+
+       len = read(sk, buf, sizeof(buf));
+       memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE);
+       memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_GET_LPT_STATUS_RP_SIZE);
+
+       if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (lpt_status)
+               *lpt_status = rp.lpt_status;
+
+       return 0;
+}
+
+static inline int hcrp_get_next_tid(int tid)
+{
+       if (tid > 0xf000)
+               return 0;
+       else
+               return tid + 1;
+}
+
+int hcrp_print(bdaddr_t *src, bdaddr_t *dst, unsigned short ctrl_psm, unsigned short data_psm, int fd, int copies, const char *cups_class)
+{
+       struct sockaddr_l2 addr;
+       struct l2cap_options opts;
+       socklen_t size;
+       unsigned char buf[2048];
+       int i, ctrl_sk, data_sk, count, len, timeout = 0;
+       unsigned int mtu;
+       uint8_t status;
+       uint16_t tid = 0;
+       uint32_t tmp, credit = 0;
+
+       if ((ctrl_sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) {
+               perror("ERROR: Can't create socket");
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, src);
+
+       if (bind(ctrl_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("ERROR: Can't bind socket");
+               close(ctrl_sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, dst);
+       addr.l2_psm = htobs(ctrl_psm);
+
+       if (connect(ctrl_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("ERROR: Can't connect to device");
+               close(ctrl_sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       if ((data_sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) {
+               perror("ERROR: Can't create socket");
+               close(ctrl_sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, src);
+
+       if (bind(data_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("ERROR: Can't bind socket");
+               close(data_sk);
+               close(ctrl_sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, dst);
+       addr.l2_psm = htobs(data_psm);
+
+       if (connect(data_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("ERROR: Can't connect to device");
+               close(data_sk);
+               close(ctrl_sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       fputs("STATE: -connecting-to-device\n", stderr);
+
+       memset(&opts, 0, sizeof(opts));
+       size = sizeof(opts);
+
+       if (getsockopt(data_sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &size) < 0) {
+               perror("ERROR: Can't get socket options");
+               close(data_sk);
+               close(ctrl_sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       mtu = opts.omtu;
+
+       /* Ignore SIGTERM signals if printing from stdin */
+       if (fd == 0) {
+#ifdef HAVE_SIGSET
+               sigset(SIGTERM, SIG_IGN);
+#elif defined(HAVE_SIGACTION)
+               memset(&action, 0, sizeof(action));
+               sigemptyset(&action.sa_mask);
+               action.sa_handler = SIG_IGN;
+               sigaction(SIGTERM, &action, NULL);
+#else
+               signal(SIGTERM, SIG_IGN);
+#endif /* HAVE_SIGSET */
+       }
+
+       tid = hcrp_get_next_tid(tid);
+       if (hcrp_credit_grant(ctrl_sk, tid, 0) < 0) {
+               fprintf(stderr, "ERROR: Can't grant initial credits\n");
+               close(data_sk);
+               close(ctrl_sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       for (i = 0; i < copies; i++) {
+
+               if (fd != 0) {
+                       fprintf(stderr, "PAGE: 1 1\n");
+                       lseek(fd, 0, SEEK_SET);
+               }
+
+               while (1) {
+                       if (credit < mtu) {
+                               tid = hcrp_get_next_tid(tid);
+                               if (!hcrp_credit_request(ctrl_sk, tid, &tmp)) {
+                                       credit += tmp;
+                                       timeout = 0;
+                               }
+                       }
+
+                       if (!credit) {
+                               if (timeout++ > 300) {
+                                       tid = hcrp_get_next_tid(tid);
+                                       if (!hcrp_get_lpt_status(ctrl_sk, tid, &status))
+                                               fprintf(stderr, "ERROR: LPT status 0x%02x\n", status);
+                                       break;
+                               }
+
+                               sleep(1);
+                               continue;
+                       }
+
+                       count = read(fd, buf, (credit > mtu) ? mtu : credit);
+                       if (count <= 0)
+                               break;
+
+                       len = write(data_sk, buf, count);
+                       if (len < 0) {
+                               perror("ERROR: Error writing to device");
+                               close(data_sk);
+                               close(ctrl_sk);
+                               return CUPS_BACKEND_FAILED;
+                       }
+
+                       if (len != count)
+                               fprintf(stderr, "ERROR: Can't send complete data\n");
+
+                       credit -= len;
+               }
+
+       }
+
+       close(data_sk);
+       close(ctrl_sk);
+
+       return CUPS_BACKEND_OK;
+}
diff --git a/cups/main.c b/cups/main.c
new file mode 100644 (file)
index 0000000..a884c6e
--- /dev/null
@@ -0,0 +1,896 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <gdbus.h>
+
+#include "cups.h"
+
+struct cups_device {
+       char *bdaddr;
+       char *name;
+       char *id;
+};
+
+static GSList *device_list = NULL;
+static GMainLoop *loop = NULL;
+static DBusConnection *conn = NULL;
+static gboolean doing_disco = FALSE;
+
+#define ATTRID_1284ID 0x0300
+
+struct context_data {
+       gboolean found;
+       char *id;
+};
+
+static void element_start(GMarkupParseContext *context,
+                               const gchar *element_name,
+                               const gchar **attribute_names,
+                               const gchar **attribute_values,
+                               gpointer user_data, GError **err)
+{
+       struct context_data *ctx_data = user_data;
+
+       if (!strcmp(element_name, "record"))
+               return;
+
+       if (!strcmp(element_name, "attribute")) {
+               int i;
+               for (i = 0; attribute_names[i]; i++) {
+                       if (strcmp(attribute_names[i], "id") != 0)
+                               continue;
+                       if (strtol(attribute_values[i], 0, 0) == ATTRID_1284ID)
+                               ctx_data->found = TRUE;
+                       break;
+               }
+               return;
+       }
+
+       if (ctx_data->found  && !strcmp(element_name, "text")) {
+               int i;
+               for (i = 0; attribute_names[i]; i++) {
+                       if (!strcmp(attribute_names[i], "value")) {
+                               ctx_data->id = g_strdup(attribute_values[i] + 2);
+                               ctx_data->found = FALSE;
+                       }
+               }
+       }
+}
+
+static GMarkupParser parser = {
+       element_start, NULL, NULL, NULL, NULL
+};
+
+static char *sdp_xml_parse_record(const char *data)
+{
+       GMarkupParseContext *ctx;
+       struct context_data ctx_data;
+       int size;
+
+       size = strlen(data);
+       ctx_data.found = FALSE;
+       ctx_data.id = NULL;
+       ctx = g_markup_parse_context_new(&parser, 0, &ctx_data, NULL);
+
+       if (g_markup_parse_context_parse(ctx, data, size, NULL) == FALSE) {
+               g_markup_parse_context_free(ctx);
+               g_free(ctx_data.id);
+               return NULL;
+       }
+
+       g_markup_parse_context_free(ctx);
+
+       return ctx_data.id;
+}
+
+static char *device_get_ieee1284_id(const char *adapter, const char *device)
+{
+       DBusMessage *message, *reply;
+       DBusMessageIter iter, reply_iter;
+       DBusMessageIter reply_iter_entry;
+       const char *hcr_print = "00001126-0000-1000-8000-00805f9b34fb";
+       const char *xml;
+       char *id = NULL;
+
+       /* Look for the service handle of the HCRP service */
+       message = dbus_message_new_method_call("org.bluez", device,
+                                               "org.bluez.Device",
+                                               "DiscoverServices");
+       dbus_message_iter_init_append(message, &iter);
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &hcr_print);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, NULL);
+
+       dbus_message_unref(message);
+
+       if (!reply)
+               return NULL;
+
+       dbus_message_iter_init(reply, &reply_iter);
+
+       if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) {
+               dbus_message_unref(reply);
+               return NULL;
+       }
+
+       dbus_message_iter_recurse(&reply_iter, &reply_iter_entry);
+
+       /* Hopefully we only get one handle, or take a punt */
+       while (dbus_message_iter_get_arg_type(&reply_iter_entry) ==
+                                                       DBUS_TYPE_DICT_ENTRY) {
+               guint32 key;
+               DBusMessageIter dict_entry;
+
+               dbus_message_iter_recurse(&reply_iter_entry, &dict_entry);
+
+               /* Key ? */
+               dbus_message_iter_get_basic(&dict_entry, &key);
+               if (!key) {
+                       dbus_message_iter_next(&reply_iter_entry);
+                       continue;
+               }
+
+               /* Try to get the value */
+               if (!dbus_message_iter_next(&dict_entry)) {
+                       dbus_message_iter_next(&reply_iter_entry);
+                       continue;
+               }
+
+               dbus_message_iter_get_basic(&dict_entry, &xml);
+
+               id = sdp_xml_parse_record(xml);
+               if (id != NULL)
+                       break;
+               dbus_message_iter_next(&reply_iter_entry);
+       }
+
+       dbus_message_unref(reply);
+
+       return id;
+}
+
+static void print_printer_details(const char *name, const char *bdaddr,
+                                                               const char *id)
+{
+       char *uri, *escaped;
+
+       escaped = g_strdelimit(g_strdup(name), "\"", '\'');
+       uri = g_strdup_printf("bluetooth://%c%c%c%c%c%c%c%c%c%c%c%c",
+                               bdaddr[0], bdaddr[1],
+                               bdaddr[3], bdaddr[4],
+                               bdaddr[6], bdaddr[7],
+                               bdaddr[9], bdaddr[10],
+                               bdaddr[12], bdaddr[13],
+                               bdaddr[15], bdaddr[16]);
+       printf("direct %s \"%s\" \"%s (Bluetooth)\"", uri, escaped, escaped);
+       if (id != NULL)
+               printf(" \"%s\"\n", id);
+       else
+               printf("\n");
+       g_free(escaped);
+       g_free(uri);
+}
+
+static void add_device_to_list(const char *name, const char *bdaddr,
+                                                               const char *id)
+{
+       struct cups_device *device;
+       GSList *l;
+
+       /* Look for the device in the list */
+       for (l = device_list; l != NULL; l = l->next) {
+               device = (struct cups_device *) l->data;
+
+               if (strcmp(device->bdaddr, bdaddr) == 0) {
+                       if (device->name != name) {
+                               g_free(device->name);
+                               device->name = g_strdup(name);
+                       }
+                       g_free(device->id);
+                       device->id = g_strdup(id);
+                       return;
+               }
+       }
+
+       /* Or add it to the list if it's not there */
+       device = g_new0(struct cups_device, 1);
+       device->bdaddr = g_strdup(bdaddr);
+       device->name = g_strdup(name);
+       device->id = g_strdup(id);
+
+       device_list = g_slist_prepend(device_list, device);
+       print_printer_details(device->name, device->bdaddr, device->id);
+}
+
+static gboolean parse_device_properties(DBusMessageIter *reply_iter,
+                                               char **name, char **bdaddr)
+{
+       guint32 class = 0;
+       DBusMessageIter reply_iter_entry;
+
+       if (dbus_message_iter_get_arg_type(reply_iter) != DBUS_TYPE_ARRAY)
+               return FALSE;
+
+       dbus_message_iter_recurse(reply_iter, &reply_iter_entry);
+
+       while (dbus_message_iter_get_arg_type(&reply_iter_entry) ==
+                                                       DBUS_TYPE_DICT_ENTRY) {
+               const char *key;
+               DBusMessageIter dict_entry, iter_dict_val;
+
+               dbus_message_iter_recurse(&reply_iter_entry, &dict_entry);
+
+               /* Key == Class ? */
+               dbus_message_iter_get_basic(&dict_entry, &key);
+               if (!key) {
+                       dbus_message_iter_next(&reply_iter_entry);
+                       continue;
+               }
+
+               if (strcmp(key, "Class") != 0 &&
+                               strcmp(key, "Alias") != 0 &&
+                               strcmp(key, "Address") != 0) {
+                       dbus_message_iter_next(&reply_iter_entry);
+                       continue;
+               }
+
+               /* Try to get the value */
+               if (!dbus_message_iter_next(&dict_entry)) {
+                       dbus_message_iter_next(&reply_iter_entry);
+                       continue;
+               }
+               dbus_message_iter_recurse(&dict_entry, &iter_dict_val);
+               if (strcmp(key, "Class") == 0) {
+                       dbus_message_iter_get_basic(&iter_dict_val, &class);
+               } else {
+                       const char *value;
+                       dbus_message_iter_get_basic(&iter_dict_val, &value);
+                       if (strcmp(key, "Alias") == 0) {
+                               *name = g_strdup(value);
+                       } else if (bdaddr) {
+                               *bdaddr = g_strdup(value);
+                       }
+               }
+               dbus_message_iter_next(&reply_iter_entry);
+       }
+
+       if (class == 0)
+               return FALSE;
+       if (((class & 0x1f00) >> 8) == 0x06 && (class & 0x80))
+               return TRUE;
+
+       return FALSE;
+}
+
+static gboolean device_is_printer(const char *adapter, const char *device_path, char **name, char **bdaddr)
+{
+       DBusMessage *message, *reply;
+       DBusMessageIter reply_iter;
+       gboolean retval;
+
+       message = dbus_message_new_method_call("org.bluez", device_path,
+                                                       "org.bluez.Device",
+                                                       "GetProperties");
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, NULL);
+
+       dbus_message_unref(message);
+
+       if (!reply)
+               return FALSE;
+
+       dbus_message_iter_init(reply, &reply_iter);
+
+       retval = parse_device_properties(&reply_iter, name, bdaddr);
+
+       dbus_message_unref(reply);
+
+       return retval;
+}
+
+static void remote_device_found(const char *adapter, const char *bdaddr,
+                                                       const char *name)
+{
+       DBusMessage *message, *reply, *adapter_reply;
+       DBusMessageIter iter;
+       char *object_path = NULL;
+       char *id;
+
+       adapter_reply = NULL;
+
+       if (adapter == NULL) {
+               message = dbus_message_new_method_call("org.bluez", "/",
+                                                       "org.bluez.Manager",
+                                                       "DefaultAdapter");
+
+               adapter_reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, NULL);
+
+               dbus_message_unref(message);
+
+               if (!adapter_reply)
+                       return;
+
+               if (dbus_message_get_args(adapter_reply, NULL,
+                                       DBUS_TYPE_OBJECT_PATH, &adapter,
+                                       DBUS_TYPE_INVALID) == FALSE) {
+                       dbus_message_unref(adapter_reply);
+                       return;
+               }
+       }
+
+       message = dbus_message_new_method_call("org.bluez", adapter,
+                                                       "org.bluez.Adapter",
+                                                       "FindDevice");
+       dbus_message_iter_init_append(message, &iter);
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr);
+
+       if (adapter_reply != NULL)
+               dbus_message_unref(adapter_reply);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, NULL);
+
+       dbus_message_unref(message);
+
+       if (!reply) {
+               message = dbus_message_new_method_call("org.bluez", adapter,
+                                                       "org.bluez.Adapter",
+                                                       "CreateDevice");
+               dbus_message_iter_init_append(message, &iter);
+               dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr);
+
+               reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, NULL);
+
+               dbus_message_unref(message);
+
+               if (!reply)
+                       return;
+       }
+
+       if (dbus_message_get_args(reply, NULL,
+                                       DBUS_TYPE_OBJECT_PATH, &object_path,
+                                       DBUS_TYPE_INVALID) == FALSE) {
+               dbus_message_unref(reply);
+               return;
+       }
+
+       id = device_get_ieee1284_id(adapter, object_path);
+       add_device_to_list(name, bdaddr, id);
+       g_free(id);
+
+       dbus_message_unref(reply);
+}
+
+static void discovery_completed(void)
+{
+       g_slist_free(device_list);
+       device_list = NULL;
+
+       g_main_loop_quit(loop);
+}
+
+static void remote_device_disappeared(const char *bdaddr)
+{
+       GSList *l;
+
+       for (l = device_list; l != NULL; l = l->next) {
+               struct cups_device *device = l->data;
+
+               if (strcmp(device->bdaddr, bdaddr) == 0) {
+                       g_free(device->name);
+                       g_free(device->bdaddr);
+                       g_free(device);
+                       device_list = g_slist_delete_link(device_list, l);
+                       return;
+               }
+       }
+}
+
+static gboolean list_known_printers(const char *adapter)
+{
+       DBusMessageIter reply_iter, iter_array;
+       DBusError error;
+       DBusMessage *message, *reply;
+
+       message = dbus_message_new_method_call("org.bluez", adapter,
+                                               "org.bluez.Adapter",
+                                               "ListDevices");
+       if (message == NULL)
+               return FALSE;
+
+       dbus_error_init(&error);
+       reply = dbus_connection_send_with_reply_and_block(conn, message,
+                                                               -1, &error);
+
+       dbus_message_unref(message);
+
+       if (dbus_error_is_set(&error))
+               return FALSE;
+
+       dbus_message_iter_init(reply, &reply_iter);
+       if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) {
+               dbus_message_unref(reply);
+               return FALSE;
+       }
+
+       dbus_message_iter_recurse(&reply_iter, &iter_array);
+       while (dbus_message_iter_get_arg_type(&iter_array) ==
+                                               DBUS_TYPE_OBJECT_PATH) {
+               const char *object_path;
+               char *name = NULL;
+               char *bdaddr = NULL;
+
+               dbus_message_iter_get_basic(&iter_array, &object_path);
+               if (device_is_printer(adapter, object_path, &name, &bdaddr)) {
+                       char *id;
+
+                       id = device_get_ieee1284_id(adapter, object_path);
+                       add_device_to_list(name, bdaddr, id);
+                       g_free(id);
+               }
+               g_free(name);
+               g_free(bdaddr);
+               dbus_message_iter_next(&iter_array);
+       }
+
+       dbus_message_unref(reply);
+
+       return FALSE;
+}
+
+static DBusHandlerResult filter_func(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
+{
+       if (dbus_message_is_signal(message, "org.bluez.Adapter",
+                                               "DeviceFound")) {
+               const char *adapter, *bdaddr;
+               char *name;
+               DBusMessageIter iter;
+
+               dbus_message_iter_init(message, &iter);
+               dbus_message_iter_get_basic(&iter, &bdaddr);
+               dbus_message_iter_next(&iter);
+
+               adapter = dbus_message_get_path(message);
+               if (parse_device_properties(&iter, &name, NULL))
+                       remote_device_found(adapter, bdaddr, name);
+               g_free (name);
+       } else if (dbus_message_is_signal(message, "org.bluez.Adapter",
+                                               "DeviceDisappeared")) {
+               const char *bdaddr;
+
+               dbus_message_get_args(message, NULL,
+                                       DBUS_TYPE_STRING, &bdaddr,
+                                       DBUS_TYPE_INVALID);
+               remote_device_disappeared(bdaddr);
+       } else if (dbus_message_is_signal(message, "org.bluez.Adapter",
+                                               "PropertyChanged")) {
+               DBusMessageIter iter, value_iter;
+               const char *name;
+               gboolean discovering;
+
+               dbus_message_iter_init(message, &iter);
+               dbus_message_iter_get_basic(&iter, &name);
+               if (name == NULL || strcmp(name, "Discovering") != 0)
+                       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+               dbus_message_iter_next(&iter);
+               dbus_message_iter_recurse(&iter, &value_iter);
+               dbus_message_iter_get_basic(&value_iter, &discovering);
+
+               if (discovering == FALSE && doing_disco) {
+                       doing_disco = FALSE;
+                       discovery_completed();
+               }
+       }
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static gboolean list_printers(void)
+{
+       /* 1. Connect to the bus
+        * 2. Get the manager
+        * 3. Get the default adapter
+        * 4. Get a list of devices
+        * 5. Get the class of each device
+        * 6. Print the details from each printer device
+        */
+       DBusError error;
+       dbus_bool_t hcid_exists;
+       DBusMessage *reply, *message;
+       DBusMessageIter reply_iter;
+       char *adapter, *match;
+
+       conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL);
+       if (conn == NULL)
+               return TRUE;
+
+       dbus_error_init(&error);
+       hcid_exists = dbus_bus_name_has_owner(conn, "org.bluez", &error);
+       if (dbus_error_is_set(&error))
+               return TRUE;
+
+       if (!hcid_exists)
+               return TRUE;
+
+       /* Get the default adapter */
+       message = dbus_message_new_method_call("org.bluez", "/",
+                                               "org.bluez.Manager",
+                                               "DefaultAdapter");
+       if (message == NULL) {
+               dbus_connection_unref(conn);
+               return FALSE;
+       }
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, &error);
+
+       dbus_message_unref(message);
+
+       if (dbus_error_is_set(&error)) {
+               dbus_connection_unref(conn);
+               /* No adapter */
+               return TRUE;
+       }
+
+       dbus_message_iter_init(reply, &reply_iter);
+       if (dbus_message_iter_get_arg_type(&reply_iter) !=
+                                               DBUS_TYPE_OBJECT_PATH) {
+               dbus_message_unref(reply);
+               dbus_connection_unref(conn);
+               return FALSE;
+       }
+
+       dbus_message_iter_get_basic(&reply_iter, &adapter);
+       adapter = g_strdup(adapter);
+       dbus_message_unref(reply);
+
+       if (!dbus_connection_add_filter(conn, filter_func, adapter, g_free)) {
+               g_free(adapter);
+               dbus_connection_unref(conn);
+               return FALSE;
+       }
+
+#define MATCH_FORMAT                           \
+       "type='signal',"                        \
+       "interface='org.bluez.Adapter',"        \
+       "sender='org.bluez',"                   \
+       "path='%s'"
+
+       match = g_strdup_printf(MATCH_FORMAT, adapter);
+       dbus_bus_add_match(conn, match, &error);
+       g_free(match);
+
+       /* Add the the recent devices */
+       list_known_printers(adapter);
+
+       doing_disco = TRUE;
+       message = dbus_message_new_method_call("org.bluez", adapter,
+                                       "org.bluez.Adapter",
+                                       "StartDiscovery");
+
+       if (!dbus_connection_send_with_reply(conn, message, NULL, -1)) {
+               dbus_message_unref(message);
+               dbus_connection_unref(conn);
+               g_free(adapter);
+               return FALSE;
+       }
+       dbus_message_unref(message);
+
+       loop = g_main_loop_new(NULL, TRUE);
+       g_main_loop_run(loop);
+
+       g_free(adapter);
+       dbus_connection_unref(conn);
+
+       return TRUE;
+}
+
+static gboolean print_ieee1284(const char *bdaddr)
+{
+       DBusMessage *message, *reply, *adapter_reply;
+       DBusMessageIter iter;
+       char *object_path = NULL;
+       char *adapter;
+       char *id;
+
+       adapter_reply = NULL;
+
+       conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL);
+       if (conn == NULL)
+               return FALSE;
+
+       message = dbus_message_new_method_call("org.bluez", "/",
+                       "org.bluez.Manager",
+                       "DefaultAdapter");
+
+       adapter_reply = dbus_connection_send_with_reply_and_block(conn,
+                       message, -1, NULL);
+
+       dbus_message_unref(message);
+
+       if (!adapter_reply)
+               return FALSE;
+
+       if (dbus_message_get_args(adapter_reply, NULL,
+                       DBUS_TYPE_OBJECT_PATH, &adapter,
+                       DBUS_TYPE_INVALID) == FALSE) {
+               dbus_message_unref(adapter_reply);
+               return FALSE;
+       }
+
+       message = dbus_message_new_method_call("org.bluez", adapter,
+                       "org.bluez.Adapter",
+                       "FindDevice");
+       dbus_message_iter_init_append(message, &iter);
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr);
+
+       if (adapter_reply != NULL)
+               dbus_message_unref(adapter_reply);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                       message, -1, NULL);
+
+       dbus_message_unref(message);
+
+       if (!reply) {
+               message = dbus_message_new_method_call("org.bluez", adapter,
+                               "org.bluez.Adapter",
+                               "CreateDevice");
+               dbus_message_iter_init_append(message, &iter);
+               dbus_message_iter_append_basic(&iter,
+                               DBUS_TYPE_STRING, &bdaddr);
+
+               reply = dbus_connection_send_with_reply_and_block(conn,
+                               message, -1, NULL);
+
+               dbus_message_unref(message);
+
+               if (!reply)
+                       return FALSE;
+       }
+
+       if (dbus_message_get_args(reply, NULL,
+                                       DBUS_TYPE_OBJECT_PATH, &object_path,
+                                       DBUS_TYPE_INVALID) == FALSE) {
+               dbus_message_unref(reply);
+               return FALSE;
+       }
+
+       id = device_get_ieee1284_id(adapter, object_path);
+       if (id == NULL) {
+               dbus_message_unref(reply);
+               return FALSE;
+       }
+       printf("%s", id);
+       g_free(id);
+
+       dbus_message_unref(reply);
+
+       return TRUE;
+}
+
+/*
+ *  Usage: printer-uri job-id user title copies options [file]
+ *
+ */
+
+int main(int argc, char *argv[])
+{
+       sdp_session_t *sdp;
+       bdaddr_t bdaddr;
+       unsigned short ctrl_psm, data_psm;
+       uint8_t channel, b[6];
+       char *ptr, str[3], device[18], service[12];
+       const char *uri, *cups_class;
+       int i, err, fd, copies, proto;
+
+       /* Make sure status messages are not buffered */
+       setbuf(stderr, NULL);
+
+       /* Make sure output is not buffered */
+       setbuf(stdout, NULL);
+
+       /* Ignore SIGPIPE signals */
+#ifdef HAVE_SIGSET
+       sigset(SIGPIPE, SIG_IGN);
+#elif defined(HAVE_SIGACTION)
+       memset(&action, 0, sizeof(action));
+       action.sa_handler = SIG_IGN;
+       sigaction(SIGPIPE, &action, NULL);
+#else
+       signal(SIGPIPE, SIG_IGN);
+#endif /* HAVE_SIGSET */
+
+       if (argc == 1) {
+               if (list_printers() == TRUE)
+                       return CUPS_BACKEND_OK;
+               else
+                       return CUPS_BACKEND_FAILED;
+       } else if (argc == 3 && strcmp(argv[1], "--get-deviceid") == 0) {
+               if (bachk(argv[2]) < 0) {
+                       fprintf(stderr, "Invalid Bluetooth address '%s'\n",
+                                       argv[2]);
+                       return CUPS_BACKEND_FAILED;
+               }
+               if (print_ieee1284(argv[2]) == FALSE)
+                       return CUPS_BACKEND_FAILED;
+               return CUPS_BACKEND_OK;
+       }
+
+       if (argc < 6 || argc > 7) {
+               fprintf(stderr, "Usage: bluetooth job-id user title copies"
+                               " options [file]\n");
+               fprintf(stderr, "       bluetooth --get-deviceid [bdaddr]\n");
+               return CUPS_BACKEND_FAILED;
+       }
+
+       if (argc == 6) {
+               fd = 0;
+               copies = 1;
+       } else {
+               if ((fd = open(argv[6], O_RDONLY)) < 0) {
+                       perror("ERROR: Unable to open print file");
+                       return CUPS_BACKEND_FAILED;
+               }
+               copies = atoi(argv[4]);
+       }
+
+       uri = getenv("DEVICE_URI");
+       if (!uri)
+               uri = argv[0];
+
+       if (strncasecmp(uri, "bluetooth://", 12)) {
+               fprintf(stderr, "ERROR: No device URI found\n");
+               return CUPS_BACKEND_FAILED;
+       }
+
+       ptr = argv[0] + 12;
+       for (i = 0; i < 6; i++) {
+               strncpy(str, ptr, 2);
+               b[i] = (uint8_t) strtol(str, NULL, 16);
+               ptr += 2;
+       }
+       sprintf(device, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+                       b[0], b[1], b[2], b[3], b[4], b[5]);
+
+       str2ba(device, &bdaddr);
+
+       ptr = strchr(ptr, '/');
+       if (ptr) {
+               strncpy(service, ptr + 1, 12);
+
+               if (!strncasecmp(ptr + 1, "spp", 3))
+                       proto = 1;
+               else if (!strncasecmp(ptr + 1, "hcrp", 4))
+                       proto = 2;
+               else
+                       proto = 0;
+       } else {
+               strcpy(service, "auto");
+               proto = 0;
+       }
+
+       cups_class = getenv("CLASS");
+
+       fprintf(stderr,
+               "DEBUG: %s device %s service %s fd %d copies %d class %s\n",
+                       argv[0], device, service, fd, copies,
+                       cups_class ? cups_class : "(none)");
+
+       fputs("STATE: +connecting-to-device\n", stderr);
+
+service_search:
+       sdp = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY);
+       if (!sdp) {
+               fprintf(stderr, "ERROR: Can't open Bluetooth connection\n");
+               return CUPS_BACKEND_FAILED;
+       }
+
+       switch (proto) {
+       case 1:
+               err = sdp_search_spp(sdp, &channel);
+               break;
+       case 2:
+               err = sdp_search_hcrp(sdp, &ctrl_psm, &data_psm);
+               break;
+       default:
+               proto = 2;
+               err = sdp_search_hcrp(sdp, &ctrl_psm, &data_psm);
+               if (err) {
+                       proto = 1;
+                       err = sdp_search_spp(sdp, &channel);
+               }
+               break;
+       }
+
+       sdp_close(sdp);
+
+       if (err) {
+               if (cups_class) {
+                       fputs("INFO: Unable to contact printer, queuing on "
+                                       "next printer in class...\n", stderr);
+                       sleep(5);
+                       return CUPS_BACKEND_FAILED;
+               }
+               sleep(20);
+               fprintf(stderr, "ERROR: Can't get service information\n");
+               goto service_search;
+       }
+
+connect:
+       switch (proto) {
+       case 1:
+               err = spp_print(BDADDR_ANY, &bdaddr, channel,
+                                               fd, copies, cups_class);
+               break;
+       case 2:
+               err = hcrp_print(BDADDR_ANY, &bdaddr, ctrl_psm, data_psm,
+                                               fd, copies, cups_class);
+               break;
+       default:
+               err = CUPS_BACKEND_FAILED;
+               fprintf(stderr, "ERROR: Unsupported protocol\n");
+               break;
+       }
+
+       if (err == CUPS_BACKEND_FAILED && cups_class) {
+               fputs("INFO: Unable to contact printer, queuing on "
+                                       "next printer in class...\n", stderr);
+               sleep(5);
+               return CUPS_BACKEND_FAILED;
+       } else if (err == CUPS_BACKEND_RETRY) {
+               sleep(20);
+               goto connect;
+       }
+
+       if (fd != 0)
+               close(fd);
+
+       if (!err)
+               fprintf(stderr, "INFO: Ready to print\n");
+
+       return err;
+}
diff --git a/cups/sdp.c b/cups/sdp.c
new file mode 100644 (file)
index 0000000..c7f17a4
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "cups.h"
+
+int sdp_search_hcrp(sdp_session_t *sdp, unsigned short *ctrl_psm, unsigned short *data_psm)
+{
+       sdp_list_t *srch, *attrs, *rsp;
+       uuid_t svclass;
+       uint16_t attr1, attr2;
+       int err;
+
+       if (!sdp)
+               return -1;
+
+       sdp_uuid16_create(&svclass, HCR_PRINT_SVCLASS_ID);
+       srch = sdp_list_append(NULL, &svclass);
+
+       attr1 = SDP_ATTR_PROTO_DESC_LIST;
+       attrs = sdp_list_append(NULL, &attr1);
+       attr2 = SDP_ATTR_ADD_PROTO_DESC_LIST;
+       attrs = sdp_list_append(attrs, &attr2);
+
+       err = sdp_service_search_attr_req(sdp, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp);
+       if (err)
+               return -1;
+
+       for (; rsp; rsp = rsp->next) {
+               sdp_record_t *rec = (sdp_record_t *) rsp->data;
+               sdp_list_t *protos;
+
+               if (!sdp_get_access_protos(rec, &protos)) {
+                       unsigned short psm = sdp_get_proto_port(protos, L2CAP_UUID);
+                       if (psm > 0) {
+                               *ctrl_psm = psm;
+                       }
+               }
+
+               if (!sdp_get_add_access_protos(rec, &protos)) {
+                       unsigned short psm = sdp_get_proto_port(protos, L2CAP_UUID);
+                       if (psm > 0 && *ctrl_psm > 0) {
+                               *data_psm = psm;
+                               return 0;
+                       }
+               }
+       }
+
+       return -1;
+}
+
+int sdp_search_spp(sdp_session_t *sdp, uint8_t *channel)
+{
+       sdp_list_t *srch, *attrs, *rsp;
+       uuid_t svclass;
+       uint16_t attr;
+       int err;
+
+       if (!sdp)
+               return -1;
+
+       sdp_uuid16_create(&svclass, SERIAL_PORT_SVCLASS_ID);
+       srch = sdp_list_append(NULL, &svclass);
+
+       attr = SDP_ATTR_PROTO_DESC_LIST;
+       attrs = sdp_list_append(NULL, &attr);
+
+       err = sdp_service_search_attr_req(sdp, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp);
+       if (err)
+               return -1;
+
+       for (; rsp; rsp = rsp->next) {
+               sdp_record_t *rec = (sdp_record_t *) rsp->data;
+               sdp_list_t *protos;
+
+               if (!sdp_get_access_protos(rec, &protos)) {
+                       uint8_t ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+                       if (ch > 0) {
+                               *channel = ch;
+                               return 0;
+                       }
+               }
+       }
+
+       return -1;
+}
diff --git a/cups/spp.c b/cups/spp.c
new file mode 100644 (file)
index 0000000..d906ed2
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "cups.h"
+
+int spp_print(bdaddr_t *src, bdaddr_t *dst, uint8_t channel, int fd, int copies, const char *cups_class)
+{
+       struct sockaddr_rc addr;
+       unsigned char buf[2048];
+       int i, sk, err, len;
+
+       if ((sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
+               perror("ERROR: Can't create socket");
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, src);
+       addr.rc_channel = 0;
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("ERROR: Can't bind socket");
+               close(sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, dst);
+       addr.rc_channel = channel;
+
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("ERROR: Can't connect to device");
+               close(sk);
+               if (cups_class)
+                       return CUPS_BACKEND_FAILED;
+               else
+                       return CUPS_BACKEND_RETRY;
+       }
+
+       fputs("STATE: -connecting-to-device\n", stderr);
+
+       /* Ignore SIGTERM signals if printing from stdin */
+       if (fd == 0) {
+#ifdef HAVE_SIGSET
+               sigset(SIGTERM, SIG_IGN);
+#elif defined(HAVE_SIGACTION)
+               memset(&action, 0, sizeof(action));
+               sigemptyset(&action.sa_mask);
+               action.sa_handler = SIG_IGN;
+               sigaction(SIGTERM, &action, NULL);
+#else
+               signal(SIGTERM, SIG_IGN);
+#endif /* HAVE_SIGSET */
+       }
+
+       for (i = 0; i < copies; i++) {
+
+               if (fd != 0) {
+                       fprintf(stderr, "PAGE: 1 1\n");
+                       lseek(fd, 0, SEEK_SET);
+               }
+
+               while ((len = read(fd, buf, sizeof(buf))) > 0) {
+                       err = write(sk, buf, len);
+                       if (err < 0) {
+                               perror("ERROR: Error writing to device");
+                               close(sk);
+                               return CUPS_BACKEND_FAILED;
+                       }
+               }
+
+       }
+
+       close(sk);
+
+       return CUPS_BACKEND_OK;
+}
diff --git a/debian/bluez.install.in b/debian/bluez.install.in
new file mode 100644 (file)
index 0000000..ef564e8
--- /dev/null
@@ -0,0 +1,7 @@
+@PREFIX@/etc/bluetooth/*
+@PREFIX@/etc/dbus*/*
+@PREFIX@/sbin/*
+@PREFIX@/bin/*
+@PREFIX@/share/man/man*
+@PREFIX@/lib/bluetooth/plugins/
+@DATADIR@/var/lib/bluetooth/
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..fcec51c
--- /dev/null
@@ -0,0 +1,27 @@
+bluez (4.90-slp2+18-2) unstable; urgency=low
+
+  * Change "SAMSUNG_PATCH" -> "TIZEN_PATCH"
+  * Git: pkgs/b/bluez
+  * Tag: bluez_4.90-slp2+18-2
+
+ -- Hocheol Seo <hocheol.seo@samsung.com>  Fri, 23 Dec 2011 11:26:09 +0900
+
+bluez (4.90-slp2+18-1) unstable; urgency=low
+
+  * Change "com.samsung" -> "org.tizen"
+  * Git: pkgs/b/bluez
+  * Tag: bluez_4.90-slp2+18-1
+
+ -- DoHyun Pyun <dh79.pyun@samsung.com>  Thu, 22 Dec 2011 18:03:02 +0900
+
+bluez (4.90-slp2+18) unstable; urgency=low
+
+  * Update Version for build
+
+ -- Chanyeol Park <chanyeol.park@samsung.com>  Thu, 15 Dec 2011 12:56:21 +0900
+
+bluez (4.90-slp2+1) unstable; urgency=low
+
+  * Initial Release
+
+ -- Chanyeol Park <chanyeol.park@samsung.com>  Wed, 07 Dec 2011 12:53:10 +0900
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..7ed6ff8
--- /dev/null
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..d1df43c
--- /dev/null
@@ -0,0 +1,56 @@
+Source: bluez
+Section: admin
+Priority: optional
+Maintainer: Hocheol Seo <hocheol.seo@samsung.com>, DoHyun Pyun <dh79.pyun@samsung.com>, ChanYeol Park <chanyeol.park@samsung.com>, Byeongho Shim <bh.shim@samsung.com>, Seungyoun Ju <sy39.ju@samsung.com>, Girish A J <girish.joshi@samsung.com>, Chethan T N <chethan.tn@samsung.com>
+Uploaders: Sunil Behera <sunil.behera@samsung.com>, Syam Sidhardhan <s.syam@samsung.com>
+Build-Depends: debhelper (>= 5), autotools-dev,
+               libdbus-1-dev,
+               libglib2.0-dev,
+               autotools-dev,
+               autoconf,
+               automake,
+               libtool,
+               bison,
+               flex
+Standards-Version: 3.7.2
+Homepage: http://www.bluez.org
+
+Package: libbluetooth3
+Section: libs
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Architecture: any
+Description: Library to use the BlueZ Linux Bluetooth stack
+ BlueZ is the official Linux Bluetooth protocol stack. It is an Open Source
+ project distributed under GNU General Public License (GPL).
+
+Package: libbluetooth-dev
+Section: libdevel
+Provides: libbluetooth3-dev
+Depends: libbluetooth3 (= ${binary:Version}), libc6-dev | libc-dev, ${misc:Depends}
+XB-Public-Package: yes
+XB-Generate-Docs: no
+Priority: extra
+Architecture: any
+Description: Development files for using the BlueZ Linux Bluetooth library
+ BlueZ is the official Linux Bluetooth protocol stack. It is an Open Source
+ project distributed under GNU General Public License (GPL).
+
+Package: bluez
+Architecture: any
+Depends: ${shlibs:Depends}, dbus, ${misc:Depends}
+Suggests: python-gobject, python-dbus
+Description: Bluetooth tools and daemons
+ This package contains tools and system daemons for using Bluetooth devices.
+ .
+ BlueZ is the official Linux Bluetooth protocol stack. It is an Open Source
+ project distributed under GNU General Public License (GPL).
+
+Package: bluez-dbg
+Section: debug
+Priority: extra
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, libbluetooth3 (= ${binary:Version})
+Description: Library to use the BlueZ Linux Bluetooth stack and  Bluetooth tools and daemons (unstripped)
+ The package contains detached debugging symbols for the binary packages
+ produced by the bluez soruce.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..2dbabe9
--- /dev/null
@@ -0,0 +1,112 @@
+This package was debianized by Seung-Woo Kim <sw0312.kim@samsung.com> on
+Thu, 12 Nov 2009 09:50:16 +0900.
+
+It was downloaded from http://www.bluez.org/
+
+BlueZ - Bluetooth protocol stack for Linux
+
+Copyright:
+       Copyright (C) 2001-2009  Marcel Holtmann <marcel@holtmann.org>
+       Copyright (C) 2006-2009  Nokia Corporation
+       Copyright (C) 2000-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+       Copyright (C) 2002-2003  Jean Tourrilhes <jt@hpl.hp.com>
+       Copyright (C) 2007-2008  Frederic Dalleau <fdalleau@free.fr>
+       Copyright (C) 2007-2008  Texas Instruments, Inc.
+       Copyright (C) 2000-2005  CSR Ltd.
+       Copyright (C) 2005-2008  Brad Midgley <bmidgley@xmission.com>
+       Copyright (C) 2007-2008  David Stockwell <dstockwell@frequency-one.com>
+       Copyright (C) 2007-2008  Fabien Chevalier <fabchevalier@free.fr>
+       Copyright (C) 2005-2006  Claudio Takahasi <claudio.takahasi@indt.org.br>
+       Copyright (C) 2006-2007  Luiz von Dentz <luiz.dentz@indt.org.br>
+       Copyright (C) 2005-2007  Johan Hedberg <johan.hedberg@nokia.com>
+       Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+       Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+       Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+       Copyright (C) 2000-2001  Qualcomm Incorporated
+       Copyright (C) 2008       Joao Paulo Rechi Vita
+       Copyright (C) 2009       Lennart Poettering
+       Copyright (C) 2004       Scott James Remnant <scott@netsplit.com>
+       Copyright (C) 2009       Intel Corporation        
+
+License:
+       audio/
+               a2dp.c a2dp.h avdtp.c avdtp.h control.c control.h device.c device.h
+               gateway.c gateway.h headset.c headset.h main.c manager.c manager.h
+               sink.c sink.h telephony-dummy.c telephony-maemo.c telephony.h
+               unix.c unix.h
+
+               License: GPL version 2 or any later version
+
+               gsta2dpsink.c gsta2dpsink.h gstavdtpsink.c
+               gstavdtpsink.h gstbluetooth.c gstrtpsbcpay.c gstrtpsbcpay.h
+               gstsbcdec.c gstsbcdec.h gstsbcenc.c gstsbcenc.h
+               gstsbcparse.c gstsbcparse.h gstsbcutil.c gstsbcutil.h
+               pcm_bluetooth.c gstbluetooth.c: ctl_bluetooth.c
+               ipc.c ipc.h rtp.h
+               ipctest.c
+
+               License: LGPL version 2.1 or any later version
+
+       sbc/
+               formats.h sbcdec.c sbcenc.c sbcinfo.c
+               sbctester.c
+
+               License: GPL version 2 or any later version
+               
+               sbc.c sbc.h
+               sbc_math.h sbc_primitives.c sbc_primitives.h
+               sbc_primitives_mmx.c sbc_primitives_mmx.h sbc_primitives_neon.c
+               sbc_primitives_neon.h sbc_tables.h
+
+               License: LGPL version 2.1 or any later version
+
+       tools/
+               ubcsp.c ubcsp.h
+
+               License: MIT License
+   
+               Permission is hereby granted, free of charge, to any person
+               obtaining a copy of this software and associated documentation
+               files (the "Software"), to deal in the Software without
+               restriction, including without limitation the rights to use,
+               copy, modify, merge, publish, distribute, sublicense, and/or sell
+               copies of the Software, and to permit persons to whom the 
+               Software is furnished to do so, subject to the following
+               conditions:
+    
+               The above copyright notice and this permission notice shall be
+               included in all copies or substantial portions of the Software.
+
+               THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+               EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+               OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+               NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+               HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+               WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+               FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+               OTHER DEALINGS IN THE SOFTWARE.
+
+               avctrl.c bccmd.c avinfo.c
+               ciptool.c csr.c csr.h csr_3wire.c csr_bcsp.c csr_h4.c
+               csr_hci.c csr_usb.c dfu.c dfu.h dfubabel.c dfutool.c
+               hciattach.h hciattach_st.c hciattach_tialt.c hid2hci.c ppporc.c
+               hciattach_ti.c
+               hciattach.c hciconfig.c hcitool.c l2ping.c
+               hcisecfilter.c sdptool.c
+
+               License: GPL version 2 or any later version
+
+       doc/
+               All files.
+               
+               License: GFDL version 1.1 or any later version
+               No Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+
+       other:
+               License: GPL version 2 or any later version
+
+On Debian GNU/Linux systems, the complete text of the GNU General Public
+License can be found in `/usr/share/common-licenses/GPL', the GNU Lesser
+General Public License can be found in `/usr/share/common-licenses/LGPL' and
+the GNU Free Documentation License can be found in
+`/usr/share/common-licenses/GFDL'.
diff --git a/debian/libbluetooth-dev.install.in b/debian/libbluetooth-dev.install.in
new file mode 100644 (file)
index 0000000..ba86add
--- /dev/null
@@ -0,0 +1,4 @@
+@PREFIX@/include/*
+@PREFIX@/lib/*.la
+@PREFIX@/lib/pkgconfig/*
+#@PREFIX@/lib/bluetooth/plugins/*.la
diff --git a/debian/libbluetooth3.install.in b/debian/libbluetooth3.install.in
new file mode 100644 (file)
index 0000000..daeeae9
--- /dev/null
@@ -0,0 +1 @@
+@PREFIX@/lib/lib*.so*
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..cd49ab0
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/make -f
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+
+# These are used for cross-compiling and for saving the configure script
+# from having to guess our platform (since we know it already)
+DEB_HOST_GNU_TYPE   ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE)
+DEB_BUILD_GNU_TYPE  ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)
+
+CFLAGS ?= -Wall -g
+LDFLAGS ?= 
+PREFIX ?= /usr
+DATADIR ?= /opt
+
+LDFLAGS += -Wl,--as-needed
+
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+       CFLAGS += -O0
+else
+       CFLAGS += -O2
+endif
+
+MACHINE="aquila"
+BT_CHIP_CFLAGS=-D__BROADCOM_PATCH__
+CHIP_OPTIONS=--disable-bccmd 
+
+configure: configure.ac
+       aclocal
+       autoheader
+       libtoolize
+       automake --add-missing
+       autoconf
+
+config.status: configure
+       dh_testdir
+
+       # Add here commands to configure the package.
+       CFLAGS="$(CFLAGS) -D__TIZEN_PATCH__ $(BT_CHIP_CFLAGS)" \
+       LDFLAGS="$(LDFLAGS) -Wl,--warn-unresolved-symbols" \
+       ./configure     --prefix=$(PREFIX) \
+                       --sysconfdir=$(PREFIX)/etc \
+                       --localstatedir=$(DATADIR)/var \
+                       --enable-pie \
+                       --enable-network \
+                       --enable-serial \
+                       --enable-input \
+                       --enable-usb=no \
+                       --enable-tools \
+                       $(CHIP_OPTIONS) \
+                       --enable-pcmcia=no \
+                       --enable-hid2hci=no \
+                       --enable-alsa=no \
+                       --enable-gstreamer=no \
+                       --disable-dfutool \
+                       --disable-cups \
+                       --disable-test \
+                       --enable-health \
+                       --disable-udevrules \
+                       --with-telephony=tizen
+
+build: build-stamp
+
+build-stamp:  config.status
+       dh_testdir
+
+       # Add here commands to compile the package.
+       $(MAKE)
+       #docbook-to-man debian/ncurses.sgml > ncurses.1
+
+       for f in `find $(CURDIR)/debian/ -name "*.in"`; do \
+               cat $$f > $${f%.in}; \
+               sed -i -e "s#@PREFIX@#$(PREFIX)#g" $${f%.in}; \
+               sed -i -e "s#@DATADIR@#$(DATADIR)#g" $${f%.in}; \
+       done
+
+       touch $@
+
+clean:
+       dh_testdir
+       dh_testroot
+       rm -f build-stamp
+
+       # Add here commands to clean up after the build process.
+       -$(MAKE) distclean
+
+       for f in `find $(CURDIR)/debian/ -name "*.in"`; do \
+               rm -f $${f%.in}; \
+       done
+
+       dh_clean 
+
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k 
+       dh_installdirs
+
+       # Add here commands to install the package into debian/tmp.
+       install -D -m 0644 $(CURDIR)/audio/audio.conf $(CURDIR)/debian/tmp/usr/etc/bluetooth/audio.conf
+       install -D -m 0644 $(CURDIR)/network/network.conf $(CURDIR)/debian/tmp/usr/etc/bluetooth/network.conf
+
+       $(MAKE) DESTDIR=$(CURDIR)/debian/tmp install
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+       dh_testdir
+       dh_testroot
+       dh_installchangelogs 
+       dh_installdocs
+       dh_installexamples
+       dh_install --sourcedir=debian/tmp
+       dh_installman
+       dh_link
+       dh_strip --dbg-package=bluez-dbg
+       dh_compress
+       dh_fixperms
+#      dh_perl
+       dh_makeshlibs
+       dh_installdeb
+       dh_shlibdeps
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install 
diff --git a/doc/adapter-api.txt b/doc/adapter-api.txt
new file mode 100644 (file)
index 0000000..67fe9d0
--- /dev/null
@@ -0,0 +1,284 @@
+BlueZ D-Bus Adapter API description
+***********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2005-2006  Johan Hedberg <johan.hedberg@nokia.com>
+Copyright (C) 2005-2006  Claudio Takahasi <claudio.takahasi@indt.org.br>
+Copyright (C) 2006-2007  Luiz von Dentz <luiz.dentz@indt.org.br>
+
+
+Adapter hierarchy
+=================
+
+Service                org.bluez
+Interface      org.bluez.Adapter
+Object path    [variable prefix]/{hci0,hci1,...}
+
+Methods                dict GetProperties()
+
+                       Returns all properties for the adapter. See the
+                       properties section for available properties.
+
+                       Possible Errors: org.bluez.Error.NotReady
+
+               void SetProperty(string name, variant value)
+
+                       Changes the value of the specified property. Only
+                       properties that are listed a read-write are changeable.
+                       On success this will emit a PropertyChanged signal.
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+
+               void RequestSession()
+
+                       This method will request a client session that
+                       provides operational Bluetooth. A possible mode
+                       change must be confirmed by the user via the agent.
+
+                       Possible Errors: org.bluez.Error.Rejected
+
+               void ReleaseSession()
+
+                       Release a previous requested session.
+
+                       Possible Errors: org.bluez.Error.DoesNotExist
+
+               void StartDiscovery()
+
+                       This method starts the device discovery session. This
+                       includes an inquiry procedure and remote device name
+                       resolving. Use StopDiscovery to release the sessions
+                       acquired.
+
+                       This process will start emitting DeviceFound and
+                       PropertyChanged "Discovering" signals.
+
+                       Possible errors: org.bluez.Error.NotReady
+                                        org.bluez.Error.Failed
+
+               void StopDiscovery()
+
+                       This method will cancel any previous StartDiscovery
+                       transaction.
+
+                       Note that a discovery procedure is shared between all
+                       discovery sessions thus calling StopDiscovery will only
+                       release a single session.
+
+                       Possible errors: org.bluez.Error.NotReady
+                                        org.bluez.Error.Failed
+                                        org.bluez.Error.NotAuthorized
+
+               object FindDevice(string address)
+
+                       Returns the object path of device for given address.
+                       The device object needs to be first created via
+                       CreateDevice or CreatePairedDevice.
+
+                       Possible Errors: org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.InvalidArguments
+
+               array{object} ListDevices() {deprecated}
+
+                       Returns list of device object paths.
+                       This method is deprecated, instead use the Devices
+                       Property to get the list of devices object paths.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.Failed
+                                        org.bluez.Error.OutOfMemory
+
+               object CreateDevice(string address)
+
+                       Creates a new object path for a remote device. This
+                       method will connect to the remote device and retrieve
+                       all SDP records.
+
+                       If the object for the remote device already exists
+                       this method will fail.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.Failed
+
+               object CreatePairedDevice(string address, object agent,
+                                                       string capability)
+
+                       Creates a new object path for a remote device. This
+                       method will connect to the remote device and retrieve
+                       all SDP records and then initiate the pairing.
+
+                       If previously CreateDevice was used successfully,
+                       this method will only initiate the pairing.
+
+                       Compared to CreateDevice this method will fail if
+                       the pairing already exists, but not if the object
+                       path already has been created. This allows applications
+                       to use CreateDevice first and the if needed use
+                       CreatePairedDevice to initiate pairing.
+
+                       The agent object path is assumed to reside within the
+                       process (D-Bus connection instance) that calls this
+                       method. No separate registration procedure is needed
+                       for it and it gets automatically released once the
+                       pairing operation is complete.
+
+                       The capability parameter is the same as for the
+                       RegisterAgent method.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.Failed
+
+               void CancelDeviceCreation(string address)
+
+                       Aborts either a CreateDevice call or a
+                       CreatePairedDevice call.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NotInProgress
+
+               void RemoveDevice(object device)
+
+                       This removes the remote device object at the given
+                       path. It will remove also the pairing information.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.Failed
+
+               void RegisterAgent(object agent, string capability)
+
+                       This registers the adapter wide agent.
+
+                       The object path defines the path of the agent
+                       that will be called when user input is needed.
+
+                       If an application disconnects from the bus all
+                       of its registered agents will be removed.
+
+                       The capability parameter can have the values
+                       "DisplayOnly", "DisplayYesNo", "KeyboardOnly" and
+                       "NoInputNoOutput" which reflects the input and output
+                       capabilities of the agent. If an empty string is
+                       used it will fallback to "DisplayYesNo".
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.AlreadyExists
+
+               void UnregisterAgent(object agent)
+
+                       This unregisters the agent that has been previously
+                       registered. The object path parameter must match the
+                       same value that has been used on registration.
+
+                       Possible errors: org.bluez.Error.DoesNotExist
+
+Signals                PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+               DeviceFound(string address, dict values)
+
+                       This signal will be sent every time an inquiry result
+                       has been found by the service daemon. In general they
+                       only appear during a device discovery.
+
+                       The dictionary can contain basically the same values
+                       that are returned by the GetProperties method
+                       from the org.bluez.Device interface. In addition there
+                       can be values for the RSSI, the TX power level and
+                       Broadcaster role.
+
+               DeviceDisappeared(string address)
+
+                       This signal will be sent when an inquiry session for
+                       a periodic discovery finishes and previously found
+                       devices are no longer in range or visible.
+
+               DeviceCreated(object device)
+
+                       Parameter is object path of created device.
+
+               DeviceRemoved(object device)
+
+                       Parameter is object path of removed device.
+
+Properties     string Address [readonly]
+
+                       The Bluetooth device address.
+
+               string Name [readwrite]
+
+                       The Bluetooth friendly name. This value can be
+                       changed and a PropertyChanged signal will be emitted.
+
+               uint32 Class [readonly]
+
+                       The Bluetooth class of device.
+
+               boolean Powered [readwrite]
+
+                       Switch an adapter on or off. This will also set the
+                       appropiate connectable state.
+
+               boolean Discoverable [readwrite]
+
+                       Switch an adapter to discoverable or non-discoverable
+                       to either make it visible or hide it. This is a global
+                       setting and should only be used by the settings
+                       application.
+
+                       If the DiscoverableTimeout is set to a non-zero
+                       value then the system will set this value back to
+                       false after the timer expired.
+
+                       In case the adapter is switched off, setting this
+                       value will fail.
+
+                       When changing the Powered property the new state of
+                       this property will be updated via a PropertyChanged
+                       signal.
+
+               # __TIZEN_PATCH__
+               boolean Limited [readwrite]
+
+                       Switch an adapter to limited discoverable or non-limited. This is
+                       a global setting and should only be used by the
+                       settings application.
+               # __TIZEN_PATCH__
+
+               boolean Pairable [readwrite]
+
+                       Switch an adapter to pairable or non-pairable. This is
+                       a global setting and should only be used by the
+                       settings application.
+
+                       Note that this property only affects incoming pairing
+                       requests.
+
+               uint32 PaireableTimeout [readwrite]
+
+                       The pairable timeout in seconds. A value of zero
+                       means that the timeout is disabled and it will stay in
+                       pareable mode forever.
+
+               uint32 DiscoverableTimeout [readwrite]
+
+                       The discoverable timeout in seconds. A value of zero
+                       means that the timeout is disabled and it will stay in
+                       discoverable/limited mode forever.
+
+                       The default value for the discoverable timeout should
+                       be 180 seconds (3 minutes).
+
+               boolean Discovering [readonly]
+
+                       Indicates that a device discovery procedure is active.
+
+               array{object} Devices [readonly]
+
+                       List of device object paths.
+
+               array{string} UUIDs [readonly]
+
+                       List of 128-bit UUIDs that represents the available
+                       local services.
diff --git a/doc/agent-api.txt b/doc/agent-api.txt
new file mode 100644 (file)
index 0000000..d8d35c0
--- /dev/null
@@ -0,0 +1,91 @@
+BlueZ D-Bus Agent API description
+**********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2005-2006  Johan Hedberg <johan.hedberg@nokia.com>
+
+
+Agent hierarchy
+===============
+
+Service                unique name
+Interface      org.bluez.Agent
+Object path    freely definable
+
+Methods                void Release()
+
+                       This method gets called when the service daemon
+                       unregisters the agent. An agent can use it to do
+                       cleanup tasks. There is no need to unregister the
+                       agent, because when this method gets called it has
+                       already been unregistered.
+
+               string RequestPinCode(object device)
+
+                       This method gets called when the service daemon
+                       needs to get the passkey for an authentication.
+
+                       The return value should be a string of 1-16 characters
+                       length. The string can be alphanumeric.
+
+                       Possible errors: org.bluez.Error.Rejected
+                                        org.bluez.Error.Canceled
+
+               uint32 RequestPasskey(object device)
+
+                       This method gets called when the service daemon
+                       needs to get the passkey for an authentication.
+
+                       The return value should be a numeric value
+                       between 0-999999.
+
+                       Possible errors: org.bluez.Error.Rejected
+                                        org.bluez.Error.Canceled
+
+               void DisplayPasskey(object device, uint32 passkey, uint8 entered)
+
+                       This method gets called when the service daemon
+                       needs to display a passkey for an authentication.
+
+                       The entered parameter indicates the number of already
+                       typed keys on the remote side.
+
+                       An empty reply should be returned. When the passkey
+                       needs no longer to be displayed, the Cancel method
+                       of the agent will be called.
+
+                       During the pairing process this method might be
+                       called multiple times to update the entered value.
+
+               void RequestConfirmation(object device, uint32 passkey)
+
+                       This method gets called when the service daemon
+                       needs to confirm a passkey for an authentication.
+
+                       To confirm the value it should return an empty reply
+                       or an error in case the passkey is invalid.
+
+                       Possible errors: org.bluez.Error.Rejected
+                                        org.bluez.Error.Canceled
+
+               void Authorize(object device, string uuid)
+
+                       This method gets called when the service daemon
+                       needs to authorize a connection/service request.
+
+                       Possible errors: org.bluez.Error.Rejected
+                                        org.bluez.Error.Canceled
+
+               void ConfirmModeChange(string mode)
+
+                       This method gets called if a mode change is requested
+                       that needs to be confirmed by the user. An example
+                       would be leaving flight mode.
+
+                       Possible errors: org.bluez.Error.Rejected
+                                        org.bluez.Error.Canceled
+
+               void Cancel()
+
+                       This method gets called to indicate that the agent
+                       request failed before a reply was returned.
diff --git a/doc/assigned-numbers.txt b/doc/assigned-numbers.txt
new file mode 100644 (file)
index 0000000..cda934c
--- /dev/null
@@ -0,0 +1,23 @@
+RFCOMM Channels
+===============
+
+Since there are a limited amount of possible RFCOMM channels (1-31)
+they've been pre-allocated for currently known profiles in order to
+avoid conflicts.
+
+Profile                Channel
+-----------------------
+DUN            1
+HFP HF         7
+OPP            9
+FTP            10
+BIP            11
+HSP AG         12
+HFP AG         13
+SYNCH (IrMC)   14
+PBAP           15
+MAP            16
+SyncEvolution  19
+PC/Ovi Suite   24
+SyncML Client  25
+SyncML Server  26
diff --git a/doc/attribute-api.txt b/doc/attribute-api.txt
new file mode 100644 (file)
index 0000000..f2778f3
--- /dev/null
@@ -0,0 +1,164 @@
+BlueZ D-Bus Attribute API description
+*************************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+
+Service details
+---------------
+
+One service object path for every remote SDP record or service in the
+attribute database. One service object path for every local SDP record
+or service from attribute database.
+
+Local services are children of the adapter object path. Remote services
+are children of the remote device object path. This doesn't solve the
+problem where local atttributes can have different instances based on
+the remote device.
+
+In general the idea is to also represent SDP records as services so that
+new style application can just use the service interfaces to retrieve the
+needed information. That way the usage of SDP and GATT would be mostly
+fully transparent and a differentiation becomes unimportant in the future.
+
+A service consists of some generic service information and a set of
+characteristics. All characteristic are presented as object path as well.
+
+
+Local Service hierarchy
+=======================
+
+Service                org.bluez
+Interface      org.bluez.Service
+               org.bluez.Characteristic
+Object path    [prefix]/{hci0}/{service0, service1, ...}
+
+Methods
+
+Properties
+
+
+Device Service hierarchy
+========================
+
+Service                org.bluez
+Interface      org.bluez.Characteristic
+Object path    [prefix]/{hci0}/{device0}/{service0, service1, ...}
+               [prefix]/{hci0}/{device1}/{service0, service1, ...}
+
+Methods        dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       Properties section for the available properties.
+
+               array{object} DiscoverCharacteristics()
+
+                       Discover all characteristics that belongs in this service.
+                       When it returns all the characteristics paths will be
+                       already registered. It will return the characteristics paths
+                       as soon as they are discovered. After that it will try to
+                       read all values.
+
+               RegisterCharacteristicsWatcher(object agent)
+
+                       Register a watcher to monitor characteristic changes.
+
+                       A watcher will be registered for this service and will
+                       notify about any changed characteristics in the service.
+                       This also notifies about any included characteristics.
+
+               UnregisterCharacteristicsWatcher(object agent)
+
+                       Unregister a watcher.
+
+Properties     string Name (mandatory) [readonly]
+
+                       General name of service
+
+               string Description (optional) [readonly]
+
+                       Description of service
+
+               string UUID (mandatory) [readonly]
+
+                       UUID of service. Service class value for SDP and GATT
+                       UUID for attribute based services.
+
+               array{object} Characteristics [readonly]
+
+                       This list contains the characteristics owned by this
+                       specific service and other characteristics from service
+                       includes. That way no complicated service includes array
+                       is needed.
+
+
+Device Characteristic hierarchy
+===============================
+
+Service                org.bluez
+Interface      org.bluez.Characteristic
+Object path    [prefix]/{hci0}/{device0}/{service0}/{characteristic0,...}
+               [prefix]/{hci0}/{device0}/{service1}/{characteristic0,...}
+
+Methods                dict GetProperties()
+
+                       Returns all properties for the characteristic. See the
+                       properties section for available properties.
+
+               void SetProperty(string name, variant value)
+
+                       Changes the value of the specified property. Only
+                       read-write properties can be changed. On success
+                       this will emit a PropertyChanged signal.
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+
+Properties     string UUID [readonly]
+
+                       UUID128 of this characteristic.
+
+               string Name [readonly]
+
+                       Optional field containing a friendly name for the
+                       Characteristic UUID.
+
+               string Description [readonly]
+
+                       Textual optional characteristic descriptor describing
+                       the Characteristic Value.
+
+               struct Format [readonly]
+
+                       Optional Characteristic descriptor which defines the
+                       format of the Characteristic Value. For numeric
+                       values, the actual value can be value * 10^Exponent.
+                       NameSpace and Description are defined on the Assigned
+                       Number Specification.
+
+                         uint8  | Format: format of the value
+                         uint8  | Exponent: Field to determine how the value is
+                                | further formatted.
+                         uint16 | Unit: unit of the characteristic
+                         uint8  | NameSpace: Name space of description.
+                         uint16 | Description: Description of the characteristic defined
+                                | in a high layer profile.
+
+               array{byte} Value [readwrite]
+
+                       Raw value of the Characteristic Value attribute.
+
+               string Representation (of the binary Value) [readonly]
+
+                       Friendly representation of the Characteristic Value
+                       based on the format attribute.
+
+
+Characteristic Watcher hierarchy
+===============================
+
+Service                unique name
+Interface      org.bluez.Watcher
+Object path    freely definable
+
+Methods                void ValueChanged(object characteristic, array{byte})
+
+                       New raw value of the Characteristic Value attribute.
diff --git a/doc/audio-api.txt b/doc/audio-api.txt
new file mode 100644 (file)
index 0000000..b85400b
--- /dev/null
@@ -0,0 +1,458 @@
+BlueZ D-Bus Audio API description
+*********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2005-2007  Johan Hedberg <johan.hedberg@nokia.com>
+Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+
+Audio hierarchy
+===============
+
+Service                org.bluez
+Interface      org.bluez.Audio
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+This is a generic audio interface that abstracts the different audio profiles.
+
+Methods                void Connect()
+
+                       Connect all supported audio profiles on the device.
+
+               void Disconnect()
+
+                       Disconnect all audio profiles on the device
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+Signals                void PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+Properties     string State
+
+                       Possible values: "disconnected", "connecting",
+                       "connected"
+
+                       "disconnected" -> "connecting"
+                               Either an incoming or outgoing connection
+                               attempt ongoing.
+
+                       "connecting" -> "disconnected"
+                               Connection attempt failed
+
+                       "connecting" -> "connected"
+                               Successfully connected
+
+                       "connected" -> "disconnected"
+                               Disconnected from the remote device
+
+Headset hierarchy
+=================
+
+Service                org.bluez
+Interface      org.bluez.Headset
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods                void Connect()
+
+                       Connect to the HSP/HFP service on the remote device.
+
+               void Disconnect()
+
+                       Disconnect from the HSP/HFP service on the remote
+                       device.
+
+               boolean IsConnected() {deprecated}
+
+                       Returns TRUE if there is a active connection to the
+                       HSP/HFP connection on the remote device.
+
+               void IndicateCall()
+
+                       Indicate an incoming call on the headset
+                       connected to the stream. Will continue to
+                       ring the headset about every 3 seconds.
+
+               void CancelCall()
+
+                       Cancel the incoming call indication.
+
+               void Play() {deprecated}
+
+                       Open the audio connection to the headset.
+
+               void Stop()
+
+                       Close the audio connection.
+
+               boolean IsPlaying() {deprecated}
+
+                       Returns true if an audio connection to the headset
+                       is active.
+
+               uint16 GetSpeakerGain() {deprecated}
+
+                       Returns the current speaker gain if available,
+                       otherwise returns the error NotAvailable.
+
+               uint16 GetMicrophoneGain() {deprecated}
+
+                       Returns the current microphone gain if available,
+                       otherwise returns the error NotAvailable.
+
+               void SetSpeakerGain(uint16 gain) {deprecated}
+
+                       Changes the current speaker gain if possible.
+
+               void SetMicrophoneGain(uint16 gain) {deprecated}
+
+                       Changes the current speaker gain if possible.
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+
+               void SetProperty(string name, variant value)
+
+                       Changes the value of the specified property. Only
+                       properties that are listed a read-write are changeable.
+                       On success this will emit a PropertyChanged signal.
+
+                       Possible Errors: org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.InvalidArguments
+
+Signals                void AnswerRequested()
+
+                       Sent when the answer button is pressed on the headset
+
+               void Connected() {deprecated}
+
+                       Sent when the device has been connected to.
+
+               void Disconnected() {deprecated}
+
+                       Sent when the device has been disconnected from.
+
+               void Stopped() {deprecated}
+
+                       Sent when the audio connection is closed
+
+               void Playing() {deprecated}
+
+                       Sent when the audio connection is opened
+
+               void SpeakerGainChanged(uint16 gain) {deprecated}
+
+                       The speaker gain changed.
+
+               void MicrophoneGainChanged(uint16 gain) {deprecated}
+
+                       The microphone gain changed.
+
+               PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+properties     string State [readonly]
+
+                       Possible values: "disconnected", "connecting",
+                       "connected", "playing"
+
+                       "disconnected" -> "connecting"
+                               Either an incoming or outgoing connection
+                               attempt ongoing.
+
+                       "connecting" -> "disconnected"
+                               Connection attempt failed
+
+                       "connecting" -> "connected"
+                               Successfully connected
+
+                       "connected" -> "playing"
+                               SCO audio connection successfully opened
+
+                       "playing" -> "connected"
+                               SCO audio connection closed
+
+                       "connected" -> "disconnected"
+                       "playing" -> "disconnected"
+                               Disconnected from the remote device
+
+               boolean Connected [readonly]
+
+                       Indicates if there is a active connection to the
+                       HSP/HFP connection on the remote device.
+
+               boolean Playing  [readonly]
+
+                       Indicates if an audio connection to the headset
+                       is active.
+
+               uint16 SpeakerGain  [readwrite]
+
+                       The speaker gain when available.
+
+               uint16 MicrophoneGain  [readwrite]
+
+                       The speaker gain when available.
+
+
+AudioSink hierarchy
+===================
+
+Service                org.bluez
+Interface      org.bluez.AudioSink
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods                void Connect()
+
+                       Connect and setup a stream to a A2DP sink on the
+                       remote device.
+
+               void Disconnect()
+
+                       Disconnect from the remote device.
+
+               boolean IsConnected() {deprecated}
+
+                       Returns TRUE if a stream is setup to a A2DP sink on
+                       the remote device.
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+
+Signals                void Connected() {deprecated}
+
+                       Sent when a successful connection has been made to the
+                       remote A2DP Sink
+
+               void Disconnected() {deprecated}
+
+                       Sent when the device has been disconnected from.
+
+               void Playing() {deprecated}
+
+                       Sent when a stream with remote device is started.
+
+               void Stopped() {deprecated}
+
+                       Sent when a stream with remote device is suspended.
+
+               PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+properties     string State [readonly]
+
+                       Possible values: "disconnected", "connecting",
+                       "connected", "playing"
+
+                       "disconnected" -> "connecting"
+                               Either an incoming or outgoing connection
+                               attempt ongoing.
+
+                       "connecting" -> "disconnected"
+                               Connection attempt failed
+
+                       "connecting" -> "connected"
+                               Successfully connected
+
+                       "connected" -> "playing"
+                               Audio stream active
+
+                       "playing" -> "connected"
+                               Audio stream suspended
+
+                       "connected" -> "disconnected"
+                       "playing" -> "disconnected"
+                               Disconnected from the remote device
+
+               boolean Connected [readonly]
+
+                       Indicates if a stream is setup to a A2DP sink on
+                       the remote device.
+
+               boolean Playing  [readonly]
+
+                       Indicates if a stream is active to a A2DP sink on
+                       the remote device.
+
+AudioSource hierarchy
+=====================
+
+Service                org.bluez
+Interface      org.bluez.AudioSource
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods                void Connect()
+
+                       Connect and setup a stream to a A2DP source on the
+                       remote device.
+
+               void Disconnect()
+
+                       Disconnect from the remote device.
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+
+Signals                PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+properties     string State [readonly]
+
+                       Possible values: "disconnected", "connecting",
+                       "connected", "playing"
+
+                       "disconnected" -> "connecting"
+                               Either an incoming or outgoing connection
+                               attempt ongoing.
+
+                       "connecting" -> "disconnected"
+                               Connection attempt failed
+
+                       "connecting" -> "connected"
+                               Successfully connected
+
+                       "connected" -> "playing"
+                               Audio stream active
+
+                       "playing" -> "connected"
+                               Audio stream suspended
+
+                       "connected" -> "disconnected"
+                       "playing" -> "disconnected"
+                               Disconnected from the remote device
+
+
+HeadsetGateway hierarchy
+========================
+
+Service                org.bluez
+Interface      org.bluez.HeadsetGateway
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+This interface is available for remote devices which can function in the Audio
+Gateway role of the HFP profiles.
+
+Methods                void Connect()
+
+                       Connect to the AG service on the remote device.
+
+               void Disconnect()
+
+                       Disconnect from the AG service on the remote device
+
+               void AnswerCall()
+
+                       It has to called only after Ring signal received.
+
+               void TerminateCall()
+
+                       Terminate call which is running or reject an incoming
+                       call. This has nothing with any 3-way situation incl.
+                       RaH. Just plain old PDH.
+
+               void Call(string number)
+
+                       Dial a number 'number'. No number processing is done
+                       thus if AG would reject to dial it don't blame me :)
+
+               string GetOperatorName()
+
+                       Find out the name of the currently selected network
+                       operator by AG.
+
+               void SendDTMF(string digits)
+
+                       Will send each digit in the 'digits' sequentially. Would
+                       send nothing if there is non-dtmf digit.
+
+               string GetSubscriberNumber()
+
+                       Get the voicecall subscriber number of AG
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+Signals                void Ring(string number)
+
+                       Someone's calling from 'number'.
+                       Caller number is provided as received from AG.
+
+               void CallTerminated()
+
+                       Call failed to set up. It means that we tried to call
+                       someone or someone tried to call us but call was not
+                       accepted.
+
+               void CallStarted()
+
+                       Call set up successfully.
+
+               void CallEnded()
+
+                       Call was started and now ended. In contrast with
+                       CallTerminated where call didn't started
+
+               PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+properties     boolean Connected [readonly]
+
+                       Indicates if there is an active connection to the
+                       AG service on the remote device.
+
+               uint16 RegistrationStatus [readonly]
+
+                       Service availability indicatior of AG, where:
+                       0 implies no service. No Home/Roam network available.
+                       1 implies presence of service. Home/Roam network
+                       available.
+
+               uint16 SignalStrength [readonly]
+
+                       Signal strength indicator of AG, the value ranges from
+                       0 to 5.
+
+               uint16 RoamingStatus [readonly]
+
+                       Roaming status indicator of AG, where:
+                       0 means roaming is not active
+                       1 means a roaming is active
+
+               uint16 BatteryCharge [readonly]
+
+                       Battery Charge indicator of AG, the value ranges from
+                       0 to 5.
+
+               uint16 SpeakerGain  [readonly]
+
+                       The speaker gain when available.
+
+               uint16 MicrophoneGain  [readonly]
+
+                       The speaker gain when available.
diff --git a/doc/control-api.txt b/doc/control-api.txt
new file mode 100644 (file)
index 0000000..eacfbcd
--- /dev/null
@@ -0,0 +1,44 @@
+BlueZ D-Bus Control API description
+***********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2007-2008  David Stockwell <dstockwell@frequency-one.com>
+
+
+Control hierarchy
+=================
+
+Service                org.bluez
+Interface      org.bluez.Control
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods                boolean IsConnected() {deprecated}
+
+                       Returns True if connected, otherwise FALSE.
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+               void VolumeUp()
+
+                       Adjust remote volume one step up
+
+               void VolumeDown()
+
+                       Adjust remote volume one step down
+
+Signals                Connected() {deprecated}
+
+                       Sent when a successful AVRCP connection has been made
+                       to the remote device.
+
+               Disconnected() {deprecated}
+
+                       Sent when the AVRCP connection to the remote device
+                       has been disconnected.
+
+Properties
+
+               boolean Connected [readonly]
diff --git a/doc/device-api.txt b/doc/device-api.txt
new file mode 100644 (file)
index 0000000..60bcc29
--- /dev/null
@@ -0,0 +1,266 @@
+BlueZ D-Bus Device API description
+**********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2005-2006  Johan Hedberg <johan.hedberg@nokia.com>
+Copyright (C) 2005-2006  Claudio Takahasi <claudio.takahasi@indt.org.br>
+Copyright (C) 2006-2007  Luiz von Dentz <luiz.dentz@indt.org.br>
+
+
+Device hierarchy
+================
+
+Service                org.bluez
+Interface      org.bluez.Device
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods                dict GetProperties()
+
+                       Returns all properties for the device. See the
+                       properties section for available properties.
+
+                       Possible Errors: org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.InvalidArguments
+
+               # __TIZEN_PATCH__
+               dict GetVersionProperties()
+
+                       Returns all version properties for the device.
+                       See the version properties section for available
+                       properties.
+
+                       Possible Errors: org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.InvalidArguments
+               # __TIZEN_PATCH__
+
+               void SetProperty(string name, variant value)
+
+                       Changes the value of the specified property. Only
+                       properties that are listed a read-write are changeable.
+                       On success this will emit a PropertyChanged signal.
+
+                       Possible Errors: org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.InvalidArguments
+
+               dict DiscoverServices(string pattern)
+
+                       This method starts the service discovery to retrieve
+                       remote service records. The pattern parameter can
+                       be used to specify specific UUIDs. And empty string
+                       will look for the public browse group.
+
+                       The return value is a dictionary with the record
+                       handles as keys and the service record in XML format
+                       as values. The key is uint32 and the value a string
+                       for this dictionary.
+
+                       Possible errors: org.bluez.Error.NotReady
+                                        org.bluez.Error.Failed
+                                        org.bluez.Error.InProgress
+
+               void CancelDiscovery()
+
+                       This method will cancel any previous DiscoverServices
+                       transaction.
+
+                       Possible errors: org.bluez.Error.NotReady
+                                        org.bluez.Error.Failed
+                                        org.bluez.Error.NotAuthorized
+
+               void Disconnect()
+
+                       This method disconnects a specific remote device by
+                       terminating the low-level ACL connection. The use of
+                       this method should be restricted to administrator
+                       use.
+
+                       A DisconnectRequested signal will be sent and the
+                       actual disconnection will only happen 2 seconds later.
+                       This enables upper-level applications to terminate
+                       their connections gracefully before the ACL connection
+                       is terminated.
+
+                       Possible errors: org.bluez.Error.NotConnected
+
+               array{object} ListNodes()
+
+                       Returns list of device node object paths.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.Failed
+                                        org.bluez.Error.OutOfMemory
+
+               object CreateNode(string uuid)
+
+                       Creates a persistent device node binding with a
+                       remote device. The actual support for the specified
+                       UUID depends if the device driver has support for
+                       persistent binding. At the moment only RFCOMM TTY
+                       nodes are supported.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NotSupported
+
+               void RemoveNode(object node)
+
+                       Removes a persistent device node binding.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.DoesNotExist
+
+Signals                PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+               DisconnectRequested()
+
+                       This signal will be sent when a low level
+                       disconnection to a remote device has been requested.
+                       The actual disconnection will happen 2 seconds later.
+
+               NodeCreated(object node)
+
+                       Parameter is object path of created device node.
+
+               NodeRemoved(object node)
+
+                       Parameter is object path of removed device node.
+
+Properties     string Address [readonly]
+
+                       The Bluetooth device address of the remote device.
+
+               string Name [readonly]
+
+                       The Bluetooth remote name. This value can not be
+                       changed. Use the Alias property instead.
+
+               string Icon [readonly]
+
+                       Proposed icon name according to the freedesktop.org
+                       icon naming specification.
+
+               uint32 Class [readonly]
+
+                       The Bluetooth class of device of the remote device.
+
+               array{string} UUIDs [readonly]
+
+                       List of 128-bit UUIDs that represents the available
+                       remote services.
+
+               array{object} Services [readonly]
+
+                       List of characteristics based services.
+
+               boolean Paired [readonly]
+
+                       Indicates if the remote device is paired.
+
+               # __TIZEN_PATCH__
+               uint32 PinLength [readonly]
+
+                       he PIN code length that was used in the pairing
+                       process.
+               # __TIZEN_PATCH__
+
+               boolean Connected [readonly]
+
+                       Indicates if the remote device is currently connected.
+                       A PropertyChanged signal indicate changes to this
+                       status.
+
+               boolean Trusted [readwrite]
+
+                       Indicates if the remote is seen as trusted. This
+                       setting can be changed by the application.
+
+               boolean Blocked [readwrite]
+
+                       If set to true any incoming connections from the
+                       device will be immediately rejected. Any device
+                       drivers will also be removed and no new ones will
+                       be probed as long as the device is blocked.
+
+               string Alias [readwrite]
+
+                       The name alias for the remote device. The alias can
+                       be used to have a different friendly name for the
+                       remote device.
+
+                       In case no alias is set, it will return the remote
+                       device name. Setting an empty string as alias will
+                       convert it back to the remote device name.
+
+                       When reseting the alias with an empty string, the
+                       emitted PropertyChanged signal will show the remote
+                       name again.
+
+               array{object} Nodes [readonly]
+
+                       List of device node object paths.
+
+               object Adapter [readonly]
+
+                       The object path of the adapter the device belongs to.
+
+               boolean LegacyPairing [readonly]
+
+                       Set to true if the device only supports the pre-2.1
+                       pairing mechanism. This property is useful in the
+                       Adapter.DeviceFound signal to anticipate whether
+                       legacy or simple pairing will occur.
+
+                       Note that this property can exhibit false-positives
+                       in the case of Bluetooth 2.1 (or newer) devices that
+                       have disabled Extended Inquiry Response support.
+
+               # __TIZEN_PATCH__
+VersionProperties      string Address [readonly]
+
+                       The Bluetooth device address of the remote device.
+
+               string Name [readonly]
+
+                       The Bluetooth remote name. This value can not be
+                       changed. Use the Alias property instead.
+
+               string Icon [readonly]
+
+                       Proposed icon name according to the freedesktop.org
+                       icon naming specification.
+
+               uint32 Class [readonly]
+
+                       The Bluetooth class of device of the remote device.
+
+               string Company [readonly]
+
+                       the company name from the OUI database of the
+                       Bluetooth device address. This function will need a
+                       valid and up-to-date oui.txt from the IEEE. This value
+                       will be different from the manufacturer string in the
+                       most cases.
+
+               string Manufacturer [readonly]
+
+                       the manufacturer of the chip for a remote device.
+
+               string Revision [readonly]
+
+                       the revision of the Bluetooth chip. This is a
+                       vendor specific value and in most cases it represents
+                       the firmware version. This derives only from the LMP
+                       subversion value.
+
+               string Version [readonly]
+
+                       the version info for a remote device. The base for this
+                       string is the LMP version value and the features for
+                       EDR support.
+                       Not available can be received if the remote device was
+                       not contacted(connected) previously. Remote data is
+                       automatically retrieved in the first connection.
+
+               # __TIZEN_PATCH__
diff --git a/doc/health-api.txt b/doc/health-api.txt
new file mode 100644 (file)
index 0000000..3d0a717
--- /dev/null
@@ -0,0 +1,166 @@
+BlueZ D-Bus Health API description
+**********************************
+
+       Santiago Carot-Nemesio <sancane@gmail.com>
+       José Antonio Santos-Cadenas <santoscadenas@gmail.com>
+       Elvis Pfützenreuter <epx@signove.com>
+
+Health Device Profile hierarchy
+===============================
+
+Service                org.bluez
+Interface      org.bluez.HealthManager
+Object path    /org/bluez/
+
+Methods:
+
+       object  CreateApplication(dict config)
+
+               Returns the path of the new registered application.
+
+               Dict is defined as below:
+               {
+                       "DataType": uint16, (mandatory)
+                       "Role" : ("Source" or "Sink"), (mandatory)
+                       "Description" : string, (optional)
+                       "ChannelType" : ("Reliable" or "Streaming")
+                                               (just for Sources, optional)
+               }
+
+               Application will be closed by the call or implicitly when the
+               programs leaves the bus.
+
+               Possible errors: org.bluez.Error.InvalidArguments
+
+       void    DestroyApplication(object application)
+
+               Closes the HDP application identified by the object path. Also
+               application will be closed if the process that started it leaves
+               the bus. Only the creator of the application will be able to
+               destroy it.
+
+               Possible errors: org.bluez.Error.InvalidArguments
+                               org.bluez.Error.NotFound
+                               org.bluez.Error.NotAllowed
+
+--------------------------------------------------------------------------------
+
+Service                org.bluez
+Interface      org.bluez.HealthDevice
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods:
+
+       dict GetProperties()
+
+               Returns all properties for the interface. See the properties
+               section for available properties.
+
+               Posible errors: org.bluez.Error.NotAllowed
+
+       Boolean Echo()
+
+               Sends an echo petition to the remote service. Returns True if
+               response matches with the buffer sent. If some error is detected
+               False value is returned.
+
+               Possible errors: org.bluez.Error.InvalidArguments
+                               org.bluez.Error.OutOfRange
+
+       object CreateChannel(object application, string configuration)
+
+               Creates a new data channel.
+               The configuration should indicate the channel quality of
+               service using one of this values "Reliable", "Streaming", "Any".
+
+               Returns the object path that identifies the data channel that
+               is already connected.
+
+               Possible errors: org.bluez.Error.InvalidArguments
+                               org.bluez.Error.HealthError
+
+       void DestroyChannel(object channel)
+
+               Destroys the data channel object. Only the creator of the
+               channel or the creator of the HealtApplication that received the
+               data channel will be able to destroy it
+
+               Possible errors: org.bluez.Error.InvalidArguments
+                               org.bluez.Error.NotFound
+                               org.bluez.Error.NotAllowed
+
+Signals:
+
+       void ChannelConnected(object channel)
+
+               This signal is launched when a new data channel is created or
+               when a known data channel is reconnected.
+
+       void ChannelDeleted(object channel)
+
+               This signal is launched when a data channel is deleted.
+
+               After this signal the data channel path will not be valid and
+               its path can be reused for future data channels.
+
+       void PropertyChanged(string name, variant value)
+
+               This signal indicates a changed value of the given property.
+
+Properties:
+
+       object MainChannel [readonly]
+
+               The first reliable channel opened. It is needed by upper
+               applications in order to send specific protocol data units. The
+               first reliable can change after a reconnection.
+
+--------------------------------------------------------------------------------
+
+Service                org.bluez
+Interface      org.bluez.HealthChannel
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/chanZZZ
+
+Only the process that created the data channel or the creator of the
+HealthApplication that received it will be able to call this methods.
+
+Methods:
+
+       dict GetProperties()
+
+               Returns all properties for the interface. See the properties
+               section for available properties.
+
+               Posible errors: org.bluez.Error.NotAllowed
+
+       fd Acquire()
+
+               Returns the file descriptor for this data channel. If the data
+               channel is not connected it will also reconnect.
+
+               Possible errors: org.bluez.Error.NotConnected
+                               org.bluez.Error.NotAllowed
+
+       void Release()
+
+               Releases the fd. Application should also need to close() it.
+
+               Possible errors: org.bluez.Error.NotAcquired
+                               org.bluez.Error.NotAllowed
+
+Properties:
+
+       string Type [readonly]
+
+               The quality of service of the data channel. ("Reliable" or
+               "Streaming")
+
+       object Device [readonly]
+
+               Identifies the Remote Device that is connected with. Maps with
+               a HealthDevice object.
+
+       object Application [readonly]
+
+               Identifies the HealthApplication to which this channel is
+               related to (which indirectly defines its role and data type).
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644 (file)
index 0000000..cf2e730
--- /dev/null
@@ -0,0 +1,86 @@
+Gateway hierarchy
+========================
+
+Service                org.bluez
+Interface      org.bluez.HandsfreeGateway
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+This interface is available for remote devices which can function in the Audio
+Gateway role of the HFP profiles.  It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods                void Connect()
+
+                       Connect to the AG service on the remote device.
+
+               void Disconnect()
+
+                       Disconnect from the AG service on the remote device
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+               void RegisterAgent(object path)
+
+                       The object path defines the path the of the agent
+                       that will be called when a new Handsfree connection
+                       is established.
+
+                       If an application disconnects from the bus all of its
+                       registered agents will be removed.
+
+               void UnregisterAgent(object path)
+
+                       This unregisters the agent that has been previously
+                       registered. The object path parameter must match the
+                       same value that has been used on registration.
+
+                       Possible Errors: org.bluez.Error.Failed
+                                        org.bluez.Error.InvalidArguments
+
+
+Signals                PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+Properties     string State [readonly]
+
+                       Indicates the state of the connection.  Possible
+                       values are:
+                               "disconnected"
+                               "connecting"
+                               "connected"
+                               "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service         unique name
+Interface       org.bluez.HandsfreeAgent
+Object path     freely definable
+
+Methods                void NewConnection(filedescriptor fd, uint16 version)
+
+                       This method gets called whenever a new handsfree
+                       connection has been established.  The objectpath
+                       contains the object path of the remote device.  This
+                       method assumes that DBus daemon with file descriptor
+                       passing capability is being used.
+
+                       The agent should only return successfully once the
+                       establishment of the service level connection (SLC)
+                       has been completed.  In the case of Handsfree this
+                       means that BRSF exchange has been performed and
+                       necessary initialization has been done.
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.Failed
+
+               void Release()
+
+                       This method gets called whenever the service daemon
+                       unregisters the agent or whenever the Adapter where
+                       the HandsfreeAgent registers itself is removed.
diff --git a/doc/input-api.txt b/doc/input-api.txt
new file mode 100644 (file)
index 0000000..7c3a4b2
--- /dev/null
@@ -0,0 +1,44 @@
+BlueZ D-Bus Input API description
+*********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+
+
+Input hierarchy
+===============
+
+Service                org.bluez
+Interface      org.bluez.Input
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods                void Connect()
+
+                       Connect to the input device.
+
+                       Possible errors: org.bluez.Error.AlreadyConnected
+                                        org.bluez.Error.ConnectionAttemptFailed
+
+               void Disconnect()
+
+                       Disconnect from the input device.
+
+                       To abort a connection attempt in case of errors or
+                       timeouts in the client it is fine to call this method.
+
+                       Possible errors: org.bluez.Error.Failed
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+
+Signals                PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+Properties     boolean Connected [readonly]
+
+                       Indicates if the device is connected.
diff --git a/doc/manager-api.txt b/doc/manager-api.txt
new file mode 100644 (file)
index 0000000..d2c1caf
--- /dev/null
@@ -0,0 +1,74 @@
+BlueZ D-Bus Manager API description
+***********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2005-2006  Johan Hedberg <johan.hedberg@nokia.com>
+Copyright (C) 2005-2006  Claudio Takahasi <claudio.takahasi@indt.org.br>
+Copyright (C) 2006-2007  Luiz von Dentz <luiz.dentz@indt.org.br>
+
+
+Manager hierarchy
+=================
+
+Service                org.bluez
+Interface      org.bluez.Manager
+Object path    /
+
+Methods                dict GetProperties()
+
+                       Returns all global properties. See the
+                       properties section for available properties.
+
+                       Possible Errors: org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.InvalidArguments
+
+               object DefaultAdapter()
+
+                       Returns object path for the default adapter.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NoSuchAdapter
+
+               object FindAdapter(string pattern)
+
+                       Returns object path for the specified adapter. Valid
+                       patterns are "hci0" or "00:11:22:33:44:55".
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NoSuchAdapter
+
+               array{object} ListAdapters() {deprecated}
+
+                       Returns list of adapter object paths under /org/bluez.
+                       This method is deprecated, instead use the Adapters
+                       Property to get the list of adapter object paths.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.Failed
+                                        org.bluez.Error.OutOfMemory
+
+Signals                PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+               AdapterAdded(object adapter)
+
+                       Parameter is object path of added adapter.
+
+               AdapterRemoved(object adapter)
+
+                       Parameter is object path of removed adapter.
+
+               DefaultAdapterChanged(object adapter)
+
+                       Parameter is object path of the new default adapter.
+
+                       In case all adapters are removed this signal will not
+                       be emitted. The AdapterRemoved signal has to be used
+                       to detect that no default adapter is selected or
+                       available anymore.
+
+Properties     array{object} Adapters [readonly]
+
+                       List of adapter object paths.
diff --git a/doc/media-api.txt b/doc/media-api.txt
new file mode 100644 (file)
index 0000000..690f373
--- /dev/null
@@ -0,0 +1,336 @@
+BlueZ D-Bus Media API description
+*********************************
+
+Media hierarchy
+===============
+
+Service                org.bluez
+Interface      org.bluez.Media
+Object path    [variable prefix]/{hci0,hci1,...}
+
+Methods                void RegisterEndpoint(object endpoint, dict properties)
+
+                       Register a local end point to sender, the sender can
+                       register as many end points as it likes.
+
+                       Note: If the sender disconnects the end points are
+                       automatically unregistered.
+
+                       possible properties:
+
+                               string UUID:
+
+                                       UUID of the profile which the endpoint
+                                       is for.
+
+                               byte Codec:
+
+                                       Assigned mumber of codec that the
+                                       endpoint implements. The values should
+                                       match the profile specification which
+                                       is indicated by the UUID.
+
+                               array{byte} Capabilities:
+
+                                       Capabilities blob, it is used as it is
+                                       so the size and byte order must match.
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NotSupported - emitted
+                                        when interface for the end-point is
+                                        disabled.
+
+               void UnregisterEndpoint(object endpoint)
+
+                       Unregister sender end point.
+
+               void RegisterPlayer(object player, dict properties,
+                                                               dict metadata)
+
+                       Register a media player object to sender, the sender
+                       can register as many objets as it likes.
+
+                       Note: If the sender disconnects its objects are
+                       automatically unregistered.
+
+                       Properties:
+
+                               string Equalizer:
+
+                                       Possible values: "off" or "on"
+
+                               string Repeat:
+
+                                       Possible values: "off", "singletrack",
+                                                       "alltracks" or "group"
+
+                               string Shuffle:
+
+                                       Possible values: "off", "alltracks" or
+                                                       "group"
+
+                               string Scan:
+
+                                       Possible values: "off", "alltracks" or
+                                                       "group"
+
+                               string Status:
+
+                                       Possible values: "playing", "stopped",
+                                                       "paused",
+                                                       "forward-seek",
+                                                       "reverse-seek" or
+                                                       "error"
+
+                               uint32 Position
+
+                                       Playback position in milliseconds
+
+                       Metadata:
+
+                               string Title:
+
+                                       Track title name
+
+                               string Artist:
+
+                                       Track artist name
+
+                               string Album:
+
+                                       Track album name
+
+                               string Genre:
+
+                                       Track genre name
+
+                               uint32 NumberOfTracks:
+
+                                       Number of tracks in total
+
+                               uint32 Number:
+
+                                       Track number
+
+                               uint32 Duration:
+
+                                       Track duration in milliseconds
+
+                       Possible Errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NotSupported
+
+               void UnregisterPlayer(object player)
+
+                       Unregister sender media player.
+
+MediaPlayer hierarchy
+=====================
+
+Service                unique name
+Interface      org.bluez.MediaPlayer
+Object path    freely definable
+
+Methods                void SetProperty(string property, variant value)
+
+                       Changes the value of the specified property. Only
+                       properties that are listed as read-write can be changed.
+
+                       On success this will emit a PropertyChanged signal.
+
+               void Release()
+
+                       This method gets called when the service daemon
+                       unregisters the player which can then perform
+                       cleanup tasks. There is no need to unregister the
+                       player, because when this method gets called it has
+                       already been unregistered.
+
+Signals                PropertyChanged(string setting, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+               TrackChanged(dict metadata)
+
+                       This signal indicates that current track has changed.
+                       All available metadata for the new track shall be set
+                       at once in the metadata argument. Metadata cannot be
+                       updated in parts, otherwise it will be interpreted as
+                       multiple track changes.
+
+                       Possible values:
+
+                               string Title:
+
+                                       Track title name
+
+                               string Artist:
+
+                                       Track artist name
+
+                               string Album:
+
+                                       Track album name
+
+                               string Genre:
+
+                                       Track genre name
+
+                               uint32 NumberOfTracks:
+
+                                       Number of tracks in total
+
+                               uint32 Number:
+
+                                       Track number
+
+                               uint32 Duration:
+
+                                       Track duration in milliseconds
+
+Properties     string Equalizer [readwrite]
+
+                       Possible values: "off" or "on"
+
+               string Repeat [readwrite]
+
+                       Possible values: "off", "singletrack", "alltracks" or
+                                       "group"
+
+               string Shuffle [readwrite]
+
+                       Possible values: "off", "alltracks" or "group"
+
+               string Scan [readwrite]
+
+                       Possible values: "off", "alltracks" or "group"
+
+               string Status [readonly]
+
+                       Possible status: "playing", "stopped", "paused",
+                                       "forward-seek", "reverse-seek" or
+                                       "error"
+
+               uint32 Position [readonly]
+
+                       Playback position in milliseconds
+
+MediaEndpoint hierarchy
+=======================
+
+Service                unique name
+Interface      org.bluez.MediaEndpoint
+Object path    freely definable
+
+Methods                void SetConfiguration(object transport, dict properties)
+
+                       Set configuration for the transport.
+
+               array{byte} SelectConfiguration(array{byte} capabilities)
+
+                       Select preferable configuration from the supported
+                       capabilities.
+
+                       Returns a configuration which can be used to setup
+                       a transport.
+
+                       Note: There is no need to cache the selected
+                       configuration since on success the configuration is
+                       send back as parameter of SetConfiguration.
+
+               void ClearConfiguration(object transport)
+
+                       Clear transport configuration.
+
+               void Release()
+
+                       This method gets called when the service daemon
+                       unregisters the endpoint. An endpoint can use it to do
+                       cleanup tasks. There is no need to unregister the
+                       endpoint, because when this method gets called it has
+                       already been unregistered.
+
+MediaTransport hierarchy
+========================
+
+Service                org.bluez
+Interface      org.bluez.MediaTransport
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/fdX
+
+Methods                dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+               fd, uint16, uint16 Acquire(string accesstype)
+
+                       Acquire transport file descriptor and the MTU for read
+                       and write respectively.
+
+                       possible accesstype:
+
+                               "r" : Read only access
+
+                               "w" : Write only access
+
+                               "rw": Read and write access
+
+               void Release(string accesstype)
+
+                       Releases file descriptor.
+
+               void SetProperty(string name, variant value)
+
+                       Changes the value of the specified property. Only
+                       properties that are listed a read-write can be changed.
+
+                       On success this will emit a PropertyChanged signal.
+
+Signals                void PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+Properties     object Device [readonly]
+
+                       Device object which the transport is connected to.
+
+               string UUID [readonly]
+
+                       UUID of the profile which the transport is for.
+
+               byte Codec [readonly]
+
+                       Assigned mumber of codec that the transport support.
+                       The values should match the profile specification which
+                       is indicated by the UUID.
+
+               array{byte} Configuration [readonly]
+
+                       Configuration blob, it is used as it is so the size and
+                       byte order must match.
+
+               uint16 Delay [readwrite]
+
+                       Optional. Transport delay in 1/10 of milisecond, this
+                       property is only writeable when the transport was
+                       acquired by the sender.
+
+               boolean NREC [readwrite]
+
+                       Optional. Indicates if echo cancelling and noise
+                       reduction functions are active in the transport, this
+                       property is only writeable when the transport was
+                       acquired by the sender.
+
+               boolean InbandRingtone [readwrite]
+
+                       Optional. Indicates if the transport support sending
+                       ringtones, this property is only writeable when the
+                       transport was acquired by the sender.
+
+               string Routing [readonly]
+
+                       Optional. Indicates where is the transport being routed
+
+                       Possible Values: "HCI" or "PCM"
diff --git a/doc/network-api.txt b/doc/network-api.txt
new file mode 100644 (file)
index 0000000..4dd3e58
--- /dev/null
@@ -0,0 +1,88 @@
+BlueZ D-Bus Network API description
+***********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+
+
+Network hierarchy
+=================
+
+Service                org.bluez
+Interface      org.bluez.Network
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods                string Connect(string uuid)
+
+                       Connect to the network device and return the network
+                       interface name. Examples of the interface name are
+                       bnep0, bnep1 etc.
+
+                       uuid can be either one of "gn", "panu" or "nap" (case
+                       insensitive) or a traditional string representation of
+                       UUID or a hexadecimal number.
+
+                       The connection will be closed and network device
+                       released either upon calling Disconnect() or when
+                       the client disappears from the message bus.
+
+                       Possible errors: org.bluez.Error.AlreadyConnected
+                                        org.bluez.Error.ConnectionAttemptFailed
+
+               void Disconnect()
+
+                       Disconnect from the network device.
+
+                       To abort a connection attempt in case of errors or
+                       timeouts in the client it is fine to call this method.
+
+                       Possible errors: org.bluez.Error.Failed
+
+               dict GetProperties()
+
+                       Returns all properties for the interface. See the
+                       properties section for available properties.
+
+Signals                PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+Properties     boolean Connected [readonly]
+
+                       Indicates if the device is connected.
+
+               string Interface [readonly]
+
+                       Indicates the network interface name when available.
+
+               string UUID [readonly]
+
+                       Indicates the connection role when available.
+
+
+Network server hierarchy
+========================
+
+Service                org.bluez
+Interface      org.bluez.NetworkServer
+Object path    /org/bluez/{hci0,hci1,...}
+
+Methods                void Register(string uuid, string bridge)
+
+                       Register server for the provided UUID. Every new
+                       connection to this server will be added the bridge
+                       interface.
+
+                       Valid UUIDs are "gn", "panu" or "nap".
+
+                       Initially no network server SDP is provided. Only
+                       after this method a SDP record will be available
+                       and the BNEP server will be ready for incoming
+                       connections.
+
+               void Unregister(string uuid)
+
+                       Unregister the server for provided UUID.
+
+                       All servers will be automatically unregistered when
+                       the calling application terminates.
diff --git a/doc/sap-api.txt b/doc/sap-api.txt
new file mode 100644 (file)
index 0000000..b8b7253
--- /dev/null
@@ -0,0 +1,34 @@
+BlueZ D-Bus Sim Access Profile API description
+***********************************
+
+Copyright (C) 2010 ST-Ericsson SA
+
+
+Sim Access Profile hierarchy
+============================
+
+Service                org.bluez
+Interface      org.bluez.SimAccess
+Object path    [variable prefix]/{hci0,hci1,...}
+
+Methods                void Disconnect()
+
+                       Disconnects SAP client from the server.
+
+                       Possible errors: org.bluez.Error.Failed
+
+               dict GetProperties()
+
+                       Return all properties for the interface. See the
+                       properties section for available properties.
+
+                       Possible Errors: org.bluez.Error.Failed
+
+Signals                PropertyChanged(string name, variant value)
+
+                       This signal indicates a changed value of the given
+                       property.
+
+Properties     boolean Connected [readonly]
+
+                       Indicates if SAP client is connected to the server.
diff --git a/doc/serial-api.txt b/doc/serial-api.txt
new file mode 100644 (file)
index 0000000..5f9bd5f
--- /dev/null
@@ -0,0 +1,41 @@
+BlueZ D-Bus Serial API description
+**********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+
+
+Serial hierarchy
+================
+
+Service                org.bluez
+Interface      org.bluez.Serial
+Object path    [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods                string Connect(string pattern)
+
+                       Connects to a specific RFCOMM based service on a
+                       remote device and then creates a RFCOMM TTY
+                       device for it. The RFCOMM TTY device is returned.
+
+                       Possible patterns: UUID 128 bit as string
+                                          Profile short names, e.g: spp, dun
+                                          RFCOMM channel as string, 1-30
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.InProgress
+                                        org.bluez.Error.ConnectionAttemptFailed
+                                        org.bluez.Error.NotSupported
+
+               void Disconnect(string device)
+
+                       Disconnect a RFCOMM TTY device that has been
+                       created by Connect method.
+
+                       To abort a connection attempt in case of errors or
+                       timeouts in the client it is fine to call this method.
+
+                       In that case one of patterns of the Connect method should
+                       be suplied instead of the TTY device.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.DoesNotExist
diff --git a/doc/service-api.txt b/doc/service-api.txt
new file mode 100644 (file)
index 0000000..5c8c7f3
--- /dev/null
@@ -0,0 +1,62 @@
+BlueZ D-Bus Adapter API description
+***********************************
+
+Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2005-2006  Johan Hedberg <johan.hedberg@nokia.com>
+Copyright (C) 2005-2006  Claudio Takahasi <claudio.takahasi@indt.org.br>
+Copyright (C) 2006-2007  Luiz von Dentz <luiz.dentz@indt.org.br>
+
+
+Service hierarchy
+=================
+
+Service                org.bluez
+Interface      org.bluez.Service
+Object path    [variable prefix]/{hci0,hci1,...}
+
+Methods                uint32 AddRecord(string record)
+
+                       Adds a new service record from the XML description
+                       and returns the assigned record handle.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.Failed
+
+               void UpdateRecord(uint32 handle, string record)
+
+                       Updates a given service record provided in the
+                       XML format.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NotAvailable
+                                        org.bluez.Error.Failed
+
+               void RemoveRecord(uint32 handle)
+
+                       Remove a service record identified by its handle.
+
+                       It is only possible to remove service records that
+                       where added by the current connection.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NotAuthorized
+                                        org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.Failed
+
+               void RequestAuthorization(string address, uint32 handle)
+
+                       Request an authorization for an incoming connection
+                       for a specific service record. The service record
+                       needs to be registered via AddRecord first.
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NotAuthorized
+                                        org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.Failed
+
+               void CancelAuthorization()
+
+                       Possible errors: org.bluez.Error.InvalidArguments
+                                        org.bluez.Error.NotAuthorized
+                                        org.bluez.Error.DoesNotExist
+                                        org.bluez.Error.Failed
diff --git a/doc/version.xml.in b/doc/version.xml.in
new file mode 100644 (file)
index 0000000..d78bda9
--- /dev/null
@@ -0,0 +1 @@
+@VERSION@
diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h
new file mode 100644 (file)
index 0000000..a0583e6
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  Copyright (C) 2004-2011  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __GDBUS_H
+#define __GDBUS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+typedef void (* GDBusWatchFunction) (DBusConnection *connection,
+                                                       void *user_data);
+
+typedef gboolean (* GDBusSignalFunction) (DBusConnection *connection,
+                                       DBusMessage *message, void *user_data);
+
+DBusConnection *g_dbus_setup_bus(DBusBusType type, const char *name,
+                                                       DBusError *error);
+
+DBusConnection *g_dbus_setup_private(DBusBusType type, const char *name,
+                                                       DBusError *error);
+
+gboolean g_dbus_request_name(DBusConnection *connection, const char *name,
+                                                       DBusError *error);
+
+gboolean g_dbus_set_disconnect_function(DBusConnection *connection,
+                               GDBusWatchFunction function,
+                               void *user_data, DBusFreeFunction destroy);
+
+typedef void (* GDBusDestroyFunction) (void *user_data);
+
+typedef DBusMessage * (* GDBusMethodFunction) (DBusConnection *connection,
+                                       DBusMessage *message, void *user_data);
+
+typedef guint32 GDBusPendingReply;
+
+typedef void (* GDBusSecurityFunction) (DBusConnection *connection,
+                                               const char *action,
+                                               gboolean interaction,
+                                               GDBusPendingReply pending);
+
+typedef enum {
+       G_DBUS_METHOD_FLAG_DEPRECATED = (1 << 0),
+       G_DBUS_METHOD_FLAG_NOREPLY    = (1 << 1),
+       G_DBUS_METHOD_FLAG_ASYNC      = (1 << 2),
+} GDBusMethodFlags;
+
+typedef enum {
+       G_DBUS_SIGNAL_FLAG_DEPRECATED = (1 << 0),
+} GDBusSignalFlags;
+
+typedef enum {
+       G_DBUS_PROPERTY_FLAG_DEPRECATED = (1 << 0),
+} GDBusPropertyFlags;
+
+typedef enum {
+       G_DBUS_SECURITY_FLAG_DEPRECATED        = (1 << 0),
+       G_DBUS_SECURITY_FLAG_BUILTIN           = (1 << 1),
+       G_DBUS_SECURITY_FLAG_ALLOW_INTERACTION = (1 << 2),
+} GDBusSecurityFlags;
+
+typedef struct {
+       const char *name;
+       const char *signature;
+       const char *reply;
+       GDBusMethodFunction function;
+       GDBusMethodFlags flags;
+       unsigned int privilege;
+} GDBusMethodTable;
+
+typedef struct {
+       const char *name;
+       const char *signature;
+       GDBusSignalFlags flags;
+} GDBusSignalTable;
+
+typedef struct {
+       const char *name;
+       const char *type;
+       GDBusPropertyFlags flags;
+} GDBusPropertyTable;
+
+typedef struct {
+       unsigned int privilege;
+       const char *action;
+       GDBusSecurityFlags flags;
+       GDBusSecurityFunction function;
+} GDBusSecurityTable;
+
+gboolean g_dbus_register_interface(DBusConnection *connection,
+                                       const char *path, const char *name,
+                                       const GDBusMethodTable *methods,
+                                       const GDBusSignalTable *signals,
+                                       const GDBusPropertyTable *properties,
+                                       void *user_data,
+                                       GDBusDestroyFunction destroy);
+gboolean g_dbus_unregister_interface(DBusConnection *connection,
+                                       const char *path, const char *name);
+
+gboolean g_dbus_register_security(const GDBusSecurityTable *security);
+gboolean g_dbus_unregister_security(const GDBusSecurityTable *security);
+
+void g_dbus_pending_success(DBusConnection *connection,
+                                       GDBusPendingReply pending);
+void g_dbus_pending_error(DBusConnection *connection,
+                               GDBusPendingReply pending,
+                               const char *name, const char *format, ...)
+                                       __attribute__((format(printf, 4, 5)));
+void g_dbus_pending_error_valist(DBusConnection *connection,
+                               GDBusPendingReply pending, const char *name,
+                                       const char *format, va_list args);
+
+DBusMessage *g_dbus_create_error(DBusMessage *message, const char *name,
+                                               const char *format, ...)
+                                       __attribute__((format(printf, 3, 4)));
+DBusMessage *g_dbus_create_error_valist(DBusMessage *message, const char *name,
+                                       const char *format, va_list args);
+DBusMessage *g_dbus_create_reply(DBusMessage *message, int type, ...);
+DBusMessage *g_dbus_create_reply_valist(DBusMessage *message,
+                                               int type, va_list args);
+
+gboolean g_dbus_send_message(DBusConnection *connection, DBusMessage *message);
+gboolean g_dbus_send_reply(DBusConnection *connection,
+                               DBusMessage *message, int type, ...);
+gboolean g_dbus_send_reply_valist(DBusConnection *connection,
+                               DBusMessage *message, int type, va_list args);
+
+gboolean g_dbus_emit_signal(DBusConnection *connection,
+                               const char *path, const char *interface,
+                               const char *name, int type, ...);
+gboolean g_dbus_emit_signal_valist(DBusConnection *connection,
+                               const char *path, const char *interface,
+                               const char *name, int type, va_list args);
+
+guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
+                               GDBusWatchFunction connect,
+                               GDBusWatchFunction disconnect,
+                               void *user_data, GDBusDestroyFunction destroy);
+guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
+                               GDBusWatchFunction function,
+                               void *user_data, GDBusDestroyFunction destroy);
+guint g_dbus_add_signal_watch(DBusConnection *connection,
+                               const char *sender, const char *path,
+                               const char *interface, const char *member,
+                               GDBusSignalFunction function, void *user_data,
+                               GDBusDestroyFunction destroy);
+gboolean g_dbus_remove_watch(DBusConnection *connection, guint tag);
+void g_dbus_remove_all_watches(DBusConnection *connection);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GDBUS_H */
diff --git a/gdbus/mainloop.c b/gdbus/mainloop.c
new file mode 100644 (file)
index 0000000..8718da0
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  Copyright (C) 2004-2011  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#ifdef NEED_DBUS_WATCH_GET_UNIX_FD
+#define dbus_watch_get_unix_fd dbus_watch_get_fd
+#endif
+
+#include "gdbus.h"
+
+#define DISPATCH_TIMEOUT  0
+
+#define info(fmt...)
+#define error(fmt...)
+#define debug(fmt...)
+
+struct timeout_handler {
+       guint id;
+       DBusTimeout *timeout;
+};
+
+struct watch_info {
+       guint id;
+       DBusWatch *watch;
+       DBusConnection *conn;
+};
+
+struct disconnect_data {
+       GDBusWatchFunction function;
+       void *user_data;
+};
+
+static gboolean disconnected_signal(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct disconnect_data *dc_data = data;
+
+       error("Got disconnected from the system message bus");
+
+       dc_data->function(conn, dc_data->user_data);
+
+       dbus_connection_unref(conn);
+
+       return TRUE;
+}
+
+static gboolean message_dispatch(void *data)
+{
+       DBusConnection *conn = data;
+
+       dbus_connection_ref(conn);
+
+       /* Dispatch messages */
+       while (dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS);
+
+       dbus_connection_unref(conn);
+
+       return FALSE;
+}
+
+static inline void queue_dispatch(DBusConnection *conn,
+                                               DBusDispatchStatus status)
+{
+       if (status == DBUS_DISPATCH_DATA_REMAINS)
+               g_timeout_add(DISPATCH_TIMEOUT, message_dispatch, conn);
+}
+
+static gboolean watch_func(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       struct watch_info *info = data;
+       unsigned int flags = 0;
+       DBusDispatchStatus status;
+
+       dbus_connection_ref(info->conn);
+
+       if (cond & G_IO_IN)  flags |= DBUS_WATCH_READABLE;
+       if (cond & G_IO_OUT) flags |= DBUS_WATCH_WRITABLE;
+       if (cond & G_IO_HUP) flags |= DBUS_WATCH_HANGUP;
+       if (cond & G_IO_ERR) flags |= DBUS_WATCH_ERROR;
+
+       dbus_watch_handle(info->watch, flags);
+
+       status = dbus_connection_get_dispatch_status(info->conn);
+       queue_dispatch(info->conn, status);
+
+       dbus_connection_unref(info->conn);
+
+       return TRUE;
+}
+
+static void watch_info_free(void *data)
+{
+       struct watch_info *info = data;
+
+       if (info->id > 0) {
+               g_source_remove(info->id);
+               info->id = 0;
+       }
+
+       dbus_connection_unref(info->conn);
+
+       g_free(info);
+}
+
+static dbus_bool_t add_watch(DBusWatch *watch, void *data)
+{
+       DBusConnection *conn = data;
+       GIOCondition cond = G_IO_HUP | G_IO_ERR;
+       GIOChannel *chan;
+       struct watch_info *info;
+       unsigned int flags;
+       int fd;
+
+       if (!dbus_watch_get_enabled(watch))
+               return TRUE;
+
+       info = g_new0(struct watch_info, 1);
+
+       fd = dbus_watch_get_unix_fd(watch);
+       chan = g_io_channel_unix_new(fd);
+
+       info->watch = watch;
+       info->conn = dbus_connection_ref(conn);
+
+       dbus_watch_set_data(watch, info, watch_info_free);
+
+       flags = dbus_watch_get_flags(watch);
+
+       if (flags & DBUS_WATCH_READABLE) cond |= G_IO_IN;
+       if (flags & DBUS_WATCH_WRITABLE) cond |= G_IO_OUT;
+
+       info->id = g_io_add_watch(chan, cond, watch_func, info);
+
+       g_io_channel_unref(chan);
+
+       return TRUE;
+}
+
+static void remove_watch(DBusWatch *watch, void *data)
+{
+       if (dbus_watch_get_enabled(watch))
+               return;
+
+       /* will trigger watch_info_free() */
+       dbus_watch_set_data(watch, NULL, NULL);
+}
+
+static void watch_toggled(DBusWatch *watch, void *data)
+{
+       /* Because we just exit on OOM, enable/disable is
+        * no different from add/remove */
+       if (dbus_watch_get_enabled(watch))
+               add_watch(watch, data);
+       else
+               remove_watch(watch, data);
+}
+
+static gboolean timeout_handler_dispatch(gpointer data)
+{
+       struct timeout_handler *handler = data;
+
+       handler->id = 0;
+
+       /* if not enabled should not be polled by the main loop */
+       if (!dbus_timeout_get_enabled(handler->timeout))
+               return FALSE;
+
+       dbus_timeout_handle(handler->timeout);
+
+       return FALSE;
+}
+
+static void timeout_handler_free(void *data)
+{
+       struct timeout_handler *handler = data;
+
+       if (handler->id > 0) {
+               g_source_remove(handler->id);
+               handler->id = 0;
+       }
+
+       g_free(handler);
+}
+
+static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data)
+{
+       int interval = dbus_timeout_get_interval(timeout);
+       struct timeout_handler *handler;
+
+       if (!dbus_timeout_get_enabled(timeout))
+               return TRUE;
+
+       handler = g_new0(struct timeout_handler, 1);
+
+       handler->timeout = timeout;
+
+       dbus_timeout_set_data(timeout, handler, timeout_handler_free);
+
+       handler->id = g_timeout_add(interval, timeout_handler_dispatch,
+                                                               handler);
+
+       return TRUE;
+}
+
+static void remove_timeout(DBusTimeout *timeout, void *data)
+{
+       /* will trigger timeout_handler_free() */
+       dbus_timeout_set_data(timeout, NULL, NULL);
+}
+
+static void timeout_toggled(DBusTimeout *timeout, void *data)
+{
+       if (dbus_timeout_get_enabled(timeout))
+               add_timeout(timeout, data);
+       else
+               remove_timeout(timeout, data);
+}
+
+static void dispatch_status(DBusConnection *conn,
+                                       DBusDispatchStatus status, void *data)
+{
+       if (!dbus_connection_get_is_connected(conn))
+               return;
+
+       queue_dispatch(conn, status);
+}
+
+static inline void setup_dbus_with_main_loop(DBusConnection *conn)
+{
+       dbus_connection_set_watch_functions(conn, add_watch, remove_watch,
+                                               watch_toggled, conn, NULL);
+
+       dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout,
+                                               timeout_toggled, NULL, NULL);
+
+       dbus_connection_set_dispatch_status_function(conn, dispatch_status,
+                                                               NULL, NULL);
+}
+
+static gboolean setup_bus(DBusConnection *conn, const char *name,
+                                               DBusError *error)
+{
+       gboolean result;
+       DBusDispatchStatus status;
+
+       if (name != NULL) {
+               result = g_dbus_request_name(conn, name, error);
+
+               if (error != NULL) {
+                       if (dbus_error_is_set(error) == TRUE)
+                               return FALSE;
+               }
+
+               if (result == FALSE)
+                       return FALSE;
+       }
+
+       setup_dbus_with_main_loop(conn);
+
+       status = dbus_connection_get_dispatch_status(conn);
+       queue_dispatch(conn, status);
+
+       return TRUE;
+}
+
+DBusConnection *g_dbus_setup_bus(DBusBusType type, const char *name,
+                                                       DBusError *error)
+{
+       DBusConnection *conn;
+
+       conn = dbus_bus_get(type, error);
+
+       if (error != NULL) {
+               if (dbus_error_is_set(error) == TRUE)
+                       return NULL;
+       }
+
+       if (conn == NULL)
+               return NULL;
+
+       if (setup_bus(conn, name, error) == FALSE) {
+               dbus_connection_unref(conn);
+               return NULL;
+       }
+
+       return conn;
+}
+
+DBusConnection *g_dbus_setup_private(DBusBusType type, const char *name,
+                                                       DBusError *error)
+{
+       DBusConnection *conn;
+
+       conn = dbus_bus_get_private(type, error);
+
+       if (error != NULL) {
+               if (dbus_error_is_set(error) == TRUE)
+                       return NULL;
+       }
+
+       if (conn == NULL)
+               return NULL;
+
+       if (setup_bus(conn, name, error) == FALSE) {
+               dbus_connection_unref(conn);
+               return NULL;
+       }
+
+       return conn;
+}
+
+gboolean g_dbus_request_name(DBusConnection *connection, const char *name,
+                                                       DBusError *error)
+{
+       int result;
+
+       result = dbus_bus_request_name(connection, name,
+                                       DBUS_NAME_FLAG_DO_NOT_QUEUE, error);
+
+       if (error != NULL) {
+               if (dbus_error_is_set(error) == TRUE)
+                       return FALSE;
+       }
+
+       if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+               if (error != NULL)
+                       dbus_set_error(error, name, "Name already in use");
+
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+gboolean g_dbus_set_disconnect_function(DBusConnection *connection,
+                               GDBusWatchFunction function,
+                               void *user_data, DBusFreeFunction destroy)
+{
+       struct disconnect_data *dc_data;
+
+       dc_data = g_new0(struct disconnect_data, 1);
+
+       dc_data->function = function;
+       dc_data->user_data = user_data;
+
+       dbus_connection_set_exit_on_disconnect(connection, FALSE);
+
+       if (g_dbus_add_signal_watch(connection, NULL, NULL,
+                               DBUS_INTERFACE_LOCAL, "Disconnected",
+                               disconnected_signal, dc_data, g_free) == 0) {
+               error("Failed to add watch for D-Bus Disconnected signal");
+               g_free(dc_data);
+               return FALSE;
+       }
+
+       return TRUE;
+}
diff --git a/gdbus/object.c b/gdbus/object.c
new file mode 100644 (file)
index 0000000..d714947
--- /dev/null
@@ -0,0 +1,877 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  Copyright (C) 2004-2011  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "gdbus.h"
+
+#ifdef __TIZEN_PATCH__
+#include "log.h"
+#else
+#define info(fmt...)
+#define error(fmt...)
+#define debug(fmt...)
+#endif
+
+struct generic_data {
+       unsigned int refcount;
+       GSList *interfaces;
+       char *introspect;
+};
+
+struct interface_data {
+       char *name;
+       const GDBusMethodTable *methods;
+       const GDBusSignalTable *signals;
+       const GDBusPropertyTable *properties;
+       void *user_data;
+       GDBusDestroyFunction destroy;
+};
+
+struct security_data {
+       GDBusPendingReply pending;
+       DBusMessage *message;
+       const GDBusMethodTable *method;
+       void *iface_user_data;
+};
+
+static void print_arguments(GString *gstr, const char *sig,
+                                               const char *direction)
+{
+       int i;
+
+       for (i = 0; sig[i]; i++) {
+               char type[32];
+               int struct_level, dict_level;
+               unsigned int len;
+               gboolean complete;
+
+               complete = FALSE;
+               struct_level = dict_level = 0;
+               memset(type, 0, sizeof(type));
+
+               /* Gather enough data to have a single complete type */
+               for (len = 0; len < (sizeof(type) - 1) && sig[i]; len++, i++) {
+                       switch (sig[i]){
+                       case '(':
+                               struct_level++;
+                               break;
+                       case ')':
+                               struct_level--;
+                               if (struct_level <= 0 && dict_level <= 0)
+                                       complete = TRUE;
+                               break;
+                       case '{':
+                               dict_level++;
+                               break;
+                       case '}':
+                               dict_level--;
+                               if (struct_level <= 0 && dict_level <= 0)
+                                       complete = TRUE;
+                               break;
+                       case 'a':
+                               break;
+                       default:
+                               if (struct_level <= 0 && dict_level <= 0)
+                                       complete = TRUE;
+                               break;
+                       }
+
+                       type[len] = sig[i];
+
+                       if (complete)
+                               break;
+               }
+
+
+               if (direction)
+                       g_string_append_printf(gstr,
+                                       "\t\t\t<arg type=\"%s\" direction=\"%s\"/>\n",
+                                       type, direction);
+               else
+                       g_string_append_printf(gstr,
+                                       "\t\t\t<arg type=\"%s\"/>\n",
+                                       type);
+       }
+}
+
+static void generate_interface_xml(GString *gstr, struct interface_data *iface)
+{
+       const GDBusMethodTable *method;
+       const GDBusSignalTable *signal;
+
+       for (method = iface->methods; method && method->name; method++) {
+               if (!strlen(method->signature) && !strlen(method->reply))
+                       g_string_append_printf(gstr, "\t\t<method name=\"%s\"/>\n",
+                                                               method->name);
+               else {
+                       g_string_append_printf(gstr, "\t\t<method name=\"%s\">\n",
+                                                               method->name);
+                       print_arguments(gstr, method->signature, "in");
+                       print_arguments(gstr, method->reply, "out");
+                       g_string_append_printf(gstr, "\t\t</method>\n");
+               }
+       }
+
+       for (signal = iface->signals; signal && signal->name; signal++) {
+               if (!strlen(signal->signature))
+                       g_string_append_printf(gstr, "\t\t<signal name=\"%s\"/>\n",
+                                                               signal->name);
+               else {
+                       g_string_append_printf(gstr, "\t\t<signal name=\"%s\">\n",
+                                                               signal->name);
+                       print_arguments(gstr, signal->signature, NULL);
+                       g_string_append_printf(gstr, "\t\t</signal>\n");
+               }
+       }
+}
+
+static void generate_introspection_xml(DBusConnection *conn,
+                               struct generic_data *data, const char *path)
+{
+       GSList *list;
+       GString *gstr;
+       char **children;
+       int i;
+
+       g_free(data->introspect);
+
+       gstr = g_string_new(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE);
+
+       g_string_append_printf(gstr, "<node>\n");
+
+       for (list = data->interfaces; list; list = list->next) {
+               struct interface_data *iface = list->data;
+
+               g_string_append_printf(gstr, "\t<interface name=\"%s\">\n",
+                                                               iface->name);
+
+               generate_interface_xml(gstr, iface);
+
+               g_string_append_printf(gstr, "\t</interface>\n");
+       }
+
+       if (!dbus_connection_list_registered(conn, path, &children))
+               goto done;
+
+       for (i = 0; children[i]; i++)
+               g_string_append_printf(gstr, "\t<node name=\"%s\"/>\n",
+                                                               children[i]);
+
+       dbus_free_string_array(children);
+
+done:
+       g_string_append_printf(gstr, "</node>\n");
+
+       data->introspect = g_string_free(gstr, FALSE);
+}
+
+static DBusMessage *introspect(DBusConnection *connection,
+                               DBusMessage *message, void *user_data)
+{
+       struct generic_data *data = user_data;
+       DBusMessage *reply;
+
+       if (!dbus_message_has_signature(message, DBUS_TYPE_INVALID_AS_STRING)) {
+               error("Unexpected signature to introspect call");
+               return NULL;
+       }
+
+       if (data->introspect == NULL)
+               generate_introspection_xml(connection, data,
+                                               dbus_message_get_path(message));
+
+       reply = dbus_message_new_method_return(message);
+       if (reply == NULL)
+               return NULL;
+
+       dbus_message_append_args(reply, DBUS_TYPE_STRING, &data->introspect,
+                                       DBUS_TYPE_INVALID);
+
+       return reply;
+}
+
+static DBusHandlerResult process_message(DBusConnection *connection,
+                       DBusMessage *message, const GDBusMethodTable *method,
+                                                       void *iface_user_data)
+{
+       DBusMessage *reply;
+
+       reply = method->function(connection, message, iface_user_data);
+
+       if (method->flags & G_DBUS_METHOD_FLAG_NOREPLY) {
+               if (reply != NULL)
+                       dbus_message_unref(reply);
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+
+       if (method->flags & G_DBUS_METHOD_FLAG_ASYNC) {
+               if (reply == NULL)
+                       return DBUS_HANDLER_RESULT_HANDLED;
+       }
+
+       if (reply == NULL)
+               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+       dbus_connection_send(connection, reply, NULL);
+       dbus_message_unref(reply);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static GDBusPendingReply next_pending = 1;
+static GSList *pending_security = NULL;
+
+static const GDBusSecurityTable *security_table = NULL;
+
+void g_dbus_pending_success(DBusConnection *connection,
+                                       GDBusPendingReply pending)
+{
+       GSList *list;
+
+        for (list = pending_security; list; list = list->next) {
+               struct security_data *secdata = list->data;
+               DBusHandlerResult result;
+
+               if (secdata->pending != pending)
+                       continue;
+
+               pending_security = g_slist_remove(pending_security, secdata);
+
+               result = process_message(connection, secdata->message,
+                               secdata->method, secdata->iface_user_data);
+
+               dbus_message_unref(secdata->message);
+               g_free(secdata);
+               return;
+        }
+}
+
+void g_dbus_pending_error_valist(DBusConnection *connection,
+                               GDBusPendingReply pending, const char *name,
+                                       const char *format, va_list args)
+{
+       GSList *list;
+
+        for (list = pending_security; list; list = list->next) {
+               struct security_data *secdata = list->data;
+               DBusMessage *reply;
+
+               if (secdata->pending != pending)
+                       continue;
+
+               pending_security = g_slist_remove(pending_security, secdata);
+
+               reply = g_dbus_create_error_valist(secdata->message,
+                                                       name, format, args);
+               if (reply != NULL) {
+                       dbus_connection_send(connection, reply, NULL);
+                       dbus_message_unref(reply);
+               }
+
+               dbus_message_unref(secdata->message);
+               g_free(secdata);
+               return;
+        }
+}
+
+void g_dbus_pending_error(DBusConnection *connection,
+                               GDBusPendingReply pending,
+                               const char *name, const char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+
+       g_dbus_pending_error_valist(connection, pending, name, format, args);
+
+       va_end(args);
+}
+
+int polkit_check_authorization(DBusConnection *conn,
+                               const char *action, gboolean interaction,
+                               void (*function) (dbus_bool_t authorized,
+                                                       void *user_data),
+                                               void *user_data, int timeout);
+
+struct builtin_security_data {
+       DBusConnection *conn;
+       GDBusPendingReply pending;
+};
+
+static void builtin_security_result(dbus_bool_t authorized, void *user_data)
+{
+       struct builtin_security_data *data = user_data;
+
+       if (authorized == TRUE)
+               g_dbus_pending_success(data->conn, data->pending);
+       else
+               g_dbus_pending_error(data->conn, data->pending,
+                                               DBUS_ERROR_AUTH_FAILED, NULL);
+
+       g_free(data);
+}
+
+static void builtin_security_function(DBusConnection *conn,
+                                               const char *action,
+                                               gboolean interaction,
+                                               GDBusPendingReply pending)
+{
+       struct builtin_security_data *data;
+
+       data = g_new0(struct builtin_security_data, 1);
+       data->conn = conn;
+       data->pending = pending;
+
+       if (polkit_check_authorization(conn, action, interaction,
+                               builtin_security_result, data, 30000) < 0)
+               g_dbus_pending_error(conn, pending, NULL, NULL);
+}
+
+static gboolean check_privilege(DBusConnection *conn, DBusMessage *msg,
+                       const GDBusMethodTable *method, void *iface_user_data)
+{
+       const GDBusSecurityTable *security;
+
+       for (security = security_table; security && security->privilege;
+                                                               security++) {
+               struct security_data *secdata;
+               gboolean interaction;
+
+               if (security->privilege != method->privilege)
+                       continue;
+
+               secdata = g_new(struct security_data, 1);
+               secdata->pending = next_pending++;
+               secdata->message = dbus_message_ref(msg);
+               secdata->method = method;
+               secdata->iface_user_data = iface_user_data;
+
+               pending_security = g_slist_prepend(pending_security, secdata);
+
+               if (security->flags & G_DBUS_SECURITY_FLAG_ALLOW_INTERACTION)
+                       interaction = TRUE;
+               else
+                       interaction = FALSE;
+
+               if (!(security->flags & G_DBUS_SECURITY_FLAG_BUILTIN) &&
+                                                       security->function)
+                       security->function(conn, security->action,
+                                               interaction, secdata->pending);
+               else
+                       builtin_security_function(conn, security->action,
+                                               interaction, secdata->pending);
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void generic_unregister(DBusConnection *connection, void *user_data)
+{
+       struct generic_data *data = user_data;
+
+       g_free(data->introspect);
+       g_free(data);
+}
+
+static struct interface_data *find_interface(GSList *interfaces,
+                                               const char *name)
+{
+       GSList *list;
+
+       if (name == NULL)
+               return NULL;
+
+       for (list = interfaces; list; list = list->next) {
+               struct interface_data *iface = list->data;
+               if (!strcmp(name, iface->name))
+                       return iface;
+       }
+
+       return NULL;
+}
+
+static DBusHandlerResult generic_message(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
+{
+       struct generic_data *data = user_data;
+       struct interface_data *iface;
+       const GDBusMethodTable *method;
+       const char *interface;
+
+       interface = dbus_message_get_interface(message);
+
+       iface = find_interface(data->interfaces, interface);
+       if (iface == NULL)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       for (method = iface->methods; method &&
+                       method->name && method->function; method++) {
+               if (dbus_message_is_method_call(message, iface->name,
+                                                       method->name) == FALSE)
+                       continue;
+
+               if (dbus_message_has_signature(message,
+                                               method->signature) == FALSE)
+                       continue;
+
+               if (check_privilege(connection, message, method,
+                                               iface->user_data) == TRUE)
+                       return DBUS_HANDLER_RESULT_HANDLED;
+
+#ifdef __TIZEN_PATCH__
+               DBG("%s: %s.%s()",dbus_message_get_path(message),
+                                                                       iface->name,method->name);
+#endif
+               return process_message(connection, message, method,
+                                                       iface->user_data);
+       }
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusObjectPathVTable generic_table = {
+       .unregister_function    = generic_unregister,
+       .message_function       = generic_message,
+};
+
+static void invalidate_parent_data(DBusConnection *conn, const char *child_path)
+{
+       struct generic_data *data = NULL;
+       char *parent_path, *slash;
+
+       parent_path = g_strdup(child_path);
+       slash = strrchr(parent_path, '/');
+       if (slash == NULL)
+               goto done;
+
+       if (slash == parent_path && parent_path[1] != '\0')
+               parent_path[1] = '\0';
+       else
+               *slash = '\0';
+
+       if (!strlen(parent_path))
+               goto done;
+
+       if (dbus_connection_get_object_path_data(conn, parent_path,
+                                                       (void *) &data) == FALSE) {
+               goto done;
+       }
+
+       invalidate_parent_data(conn, parent_path);
+
+       if (data == NULL)
+               goto done;
+
+       g_free(data->introspect);
+       data->introspect = NULL;
+
+done:
+       g_free(parent_path);
+}
+
+static GDBusMethodTable introspect_methods[] = {
+       { "Introspect", "",     "s", introspect },
+       { }
+};
+
+static void add_interface(struct generic_data *data, const char *name,
+                               const GDBusMethodTable *methods,
+                               const GDBusSignalTable *signals,
+                               const GDBusPropertyTable *properties,
+                               void *user_data,
+                               GDBusDestroyFunction destroy)
+{
+       struct interface_data *iface;
+
+       iface = g_new0(struct interface_data, 1);
+       iface->name = g_strdup(name);
+       iface->methods = methods;
+       iface->signals = signals;
+       iface->properties = properties;
+       iface->user_data = user_data;
+       iface->destroy = destroy;
+
+       data->interfaces = g_slist_append(data->interfaces, iface);
+}
+
+static struct generic_data *object_path_ref(DBusConnection *connection,
+                                                       const char *path)
+{
+       struct generic_data *data;
+
+       if (dbus_connection_get_object_path_data(connection, path,
+                                               (void *) &data) == TRUE) {
+               if (data != NULL) {
+                       data->refcount++;
+                       return data;
+               }
+       }
+
+       data = g_new0(struct generic_data, 1);
+       data->refcount = 1;
+
+       data->introspect = g_strdup(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE "<node></node>");
+
+       if (!dbus_connection_register_object_path(connection, path,
+                                               &generic_table, data)) {
+               g_free(data->introspect);
+               g_free(data);
+               return NULL;
+       }
+
+       invalidate_parent_data(connection, path);
+
+       add_interface(data, DBUS_INTERFACE_INTROSPECTABLE,
+                       introspect_methods, NULL, NULL, data, NULL);
+
+       return data;
+}
+
+static gboolean remove_interface(struct generic_data *data, const char *name)
+{
+       struct interface_data *iface;
+
+       iface = find_interface(data->interfaces, name);
+       if (iface == NULL)
+               return FALSE;
+
+       data->interfaces = g_slist_remove(data->interfaces, iface);
+
+       if (iface->destroy)
+               iface->destroy(iface->user_data);
+
+       g_free(iface->name);
+       g_free(iface);
+
+       return TRUE;
+}
+
+static void object_path_unref(DBusConnection *connection, const char *path)
+{
+       struct generic_data *data = NULL;
+
+       if (dbus_connection_get_object_path_data(connection, path,
+                                               (void *) &data) == FALSE)
+               return;
+
+       if (data == NULL)
+               return;
+
+       data->refcount--;
+
+       if (data->refcount > 0)
+               return;
+
+       remove_interface(data, DBUS_INTERFACE_INTROSPECTABLE);
+
+       invalidate_parent_data(connection, path);
+
+       dbus_connection_unregister_object_path(connection, path);
+}
+
+static gboolean check_signal(DBusConnection *conn, const char *path,
+                               const char *interface, const char *name,
+                               const char **args)
+{
+       struct generic_data *data = NULL;
+       struct interface_data *iface;
+       const GDBusSignalTable *signal;
+
+       *args = NULL;
+       if (!dbus_connection_get_object_path_data(conn, path,
+                                       (void *) &data) || data == NULL) {
+               error("dbus_connection_emit_signal: path %s isn't registered",
+                               path);
+               return FALSE;
+       }
+
+       iface = find_interface(data->interfaces, interface);
+       if (iface == NULL) {
+               error("dbus_connection_emit_signal: %s does not implement %s",
+                               path, interface);
+               return FALSE;
+       }
+
+       for (signal = iface->signals; signal && signal->name; signal++) {
+               if (!strcmp(signal->name, name)) {
+                       *args = signal->signature;
+                       break;
+               }
+       }
+
+       if (*args == NULL) {
+               error("No signal named %s on interface %s", name, interface);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static dbus_bool_t emit_signal_valist(DBusConnection *conn,
+                                               const char *path,
+                                               const char *interface,
+                                               const char *name,
+                                               int first,
+                                               va_list var_args)
+{
+       DBusMessage *signal;
+       dbus_bool_t ret;
+       const char *signature, *args;
+
+       if (!check_signal(conn, path, interface, name, &args))
+               return FALSE;
+
+       signal = dbus_message_new_signal(path, interface, name);
+       if (signal == NULL) {
+               error("Unable to allocate new %s.%s signal", interface,  name);
+               return FALSE;
+       }
+
+       ret = dbus_message_append_args_valist(signal, first, var_args);
+       if (!ret)
+               goto fail;
+
+       signature = dbus_message_get_signature(signal);
+       if (strcmp(args, signature) != 0) {
+               error("%s.%s: expected signature'%s' but got '%s'",
+                               interface, name, args, signature);
+               ret = FALSE;
+               goto fail;
+       }
+
+       ret = dbus_connection_send(conn, signal, NULL);
+
+fail:
+       dbus_message_unref(signal);
+
+       return ret;
+}
+
+gboolean g_dbus_register_interface(DBusConnection *connection,
+                                       const char *path, const char *name,
+                                       const GDBusMethodTable *methods,
+                                       const GDBusSignalTable *signals,
+                                       const GDBusPropertyTable *properties,
+                                       void *user_data,
+                                       GDBusDestroyFunction destroy)
+{
+       struct generic_data *data;
+
+       data = object_path_ref(connection, path);
+       if (data == NULL)
+               return FALSE;
+
+       if (find_interface(data->interfaces, name)) {
+               object_path_unref(connection, path);
+               return FALSE;
+       }
+
+       add_interface(data, name, methods, signals,
+                       properties, user_data, destroy);
+
+       g_free(data->introspect);
+       data->introspect = NULL;
+
+       return TRUE;
+}
+
+gboolean g_dbus_unregister_interface(DBusConnection *connection,
+                                       const char *path, const char *name)
+{
+       struct generic_data *data = NULL;
+
+       if (path == NULL)
+               return FALSE;
+
+       if (dbus_connection_get_object_path_data(connection, path,
+                                               (void *) &data) == FALSE)
+               return FALSE;
+
+       if (data == NULL)
+               return FALSE;
+
+       if (remove_interface(data, name) == FALSE)
+               return FALSE;
+
+       g_free(data->introspect);
+       data->introspect = NULL;
+
+       object_path_unref(connection, path);
+
+       return TRUE;
+}
+
+gboolean g_dbus_register_security(const GDBusSecurityTable *security)
+{
+       if (security_table != NULL)
+               return FALSE;
+
+       security_table = security;
+
+       return TRUE;
+}
+
+gboolean g_dbus_unregister_security(const GDBusSecurityTable *security)
+{
+       security_table = NULL;
+
+       return TRUE;
+}
+
+DBusMessage *g_dbus_create_error_valist(DBusMessage *message, const char *name,
+                                       const char *format, va_list args)
+{
+       char str[1024];
+
+       vsnprintf(str, sizeof(str), format, args);
+
+       return dbus_message_new_error(message, name, str);
+}
+
+DBusMessage *g_dbus_create_error(DBusMessage *message, const char *name,
+                                               const char *format, ...)
+{
+       va_list args;
+       DBusMessage *reply;
+
+       va_start(args, format);
+
+       reply = g_dbus_create_error_valist(message, name, format, args);
+
+       va_end(args);
+
+       return reply;
+}
+
+DBusMessage *g_dbus_create_reply_valist(DBusMessage *message,
+                                               int type, va_list args)
+{
+       DBusMessage *reply;
+
+       reply = dbus_message_new_method_return(message);
+       if (reply == NULL)
+               return NULL;
+
+       if (dbus_message_append_args_valist(reply, type, args) == FALSE) {
+               dbus_message_unref(reply);
+               return NULL;
+       }
+
+       return reply;
+}
+
+DBusMessage *g_dbus_create_reply(DBusMessage *message, int type, ...)
+{
+       va_list args;
+       DBusMessage *reply;
+
+       va_start(args, type);
+
+       reply = g_dbus_create_reply_valist(message, type, args);
+
+       va_end(args);
+
+       return reply;
+}
+
+gboolean g_dbus_send_message(DBusConnection *connection, DBusMessage *message)
+{
+       dbus_bool_t result;
+
+       if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL)
+               dbus_message_set_no_reply(message, TRUE);
+
+       result = dbus_connection_send(connection, message, NULL);
+
+       dbus_message_unref(message);
+
+       return result;
+}
+
+gboolean g_dbus_send_reply_valist(DBusConnection *connection,
+                               DBusMessage *message, int type, va_list args)
+{
+       DBusMessage *reply;
+
+       reply = dbus_message_new_method_return(message);
+       if (reply == NULL)
+               return FALSE;
+
+       if (dbus_message_append_args_valist(reply, type, args) == FALSE) {
+               dbus_message_unref(reply);
+               return FALSE;
+       }
+
+       return g_dbus_send_message(connection, reply);
+}
+
+gboolean g_dbus_send_reply(DBusConnection *connection,
+                               DBusMessage *message, int type, ...)
+{
+       va_list args;
+       gboolean result;
+
+       va_start(args, type);
+
+       result = g_dbus_send_reply_valist(connection, message, type, args);
+
+       va_end(args);
+
+       return result;
+}
+
+gboolean g_dbus_emit_signal(DBusConnection *connection,
+                               const char *path, const char *interface,
+                               const char *name, int type, ...)
+{
+       va_list args;
+       gboolean result;
+
+       va_start(args, type);
+
+       result = emit_signal_valist(connection, path, interface,
+                                                       name, type, args);
+
+       va_end(args);
+
+       return result;
+}
+
+gboolean g_dbus_emit_signal_valist(DBusConnection *connection,
+                               const char *path, const char *interface,
+                               const char *name, int type, va_list args)
+{
+       return emit_signal_valist(connection, path, interface,
+                                                       name, type, args);
+}
diff --git a/gdbus/polkit.c b/gdbus/polkit.c
new file mode 100644 (file)
index 0000000..9e95fa3
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  Copyright (C) 2004-2011  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <dbus/dbus.h>
+
+#include <glib.h>
+
+int polkit_check_authorization(DBusConnection *conn,
+                               const char *action, gboolean interaction,
+                               void (*function) (dbus_bool_t authorized,
+                                                       void *user_data),
+                                               void *user_data, int timeout);
+
+static void add_dict_with_string_value(DBusMessageIter *iter,
+                                       const char *key, const char *str)
+{
+       DBusMessageIter dict, entry, value;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                       DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+       dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY,
+                                                               NULL, &entry);
+
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+       dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+                                       DBUS_TYPE_STRING_AS_STRING, &value);
+       dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &str);
+       dbus_message_iter_close_container(&entry, &value);
+
+       dbus_message_iter_close_container(&dict, &entry);
+       dbus_message_iter_close_container(iter, &dict);
+}
+
+static void add_empty_string_dict(DBusMessageIter *iter)
+{
+       DBusMessageIter dict;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                       DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+       dbus_message_iter_close_container(iter, &dict);
+}
+
+static void add_arguments(DBusConnection *conn, DBusMessageIter *iter,
+                               const char *action, dbus_uint32_t flags)
+{
+       const char *busname = dbus_bus_get_unique_name(conn);
+       const char *kind = "system-bus-name";
+       const char *cancel = "";
+       DBusMessageIter subject;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT,
+                                                       NULL, &subject);
+       dbus_message_iter_append_basic(&subject, DBUS_TYPE_STRING, &kind);
+       add_dict_with_string_value(&subject, "name", busname);
+       dbus_message_iter_close_container(iter, &subject);
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &action);
+       add_empty_string_dict(iter);
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &flags);
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &cancel);
+}
+
+static dbus_bool_t parse_result(DBusMessageIter *iter)
+{
+       DBusMessageIter result;
+       dbus_bool_t authorized, challenge;
+
+       dbus_message_iter_recurse(iter, &result);
+
+       dbus_message_iter_get_basic(&result, &authorized);
+       dbus_message_iter_get_basic(&result, &challenge);
+
+       return authorized;
+}
+
+struct authorization_data {
+       void (*function) (dbus_bool_t authorized, void *user_data);
+       void *user_data;
+};
+
+static void authorization_reply(DBusPendingCall *call, void *user_data)
+{
+       struct authorization_data *data = user_data;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       dbus_bool_t authorized = FALSE;
+
+       reply = dbus_pending_call_steal_reply(call);
+
+       if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR)
+               goto done;
+
+       if (dbus_message_has_signature(reply, "(bba{ss})") == FALSE)
+               goto done;
+
+       dbus_message_iter_init(reply, &iter);
+
+       authorized = parse_result(&iter);
+
+done:
+       if (data->function != NULL)
+               data->function(authorized, data->user_data);
+
+       dbus_message_unref(reply);
+
+       dbus_pending_call_unref(call);
+}
+
+#define AUTHORITY_DBUS "org.freedesktop.PolicyKit1"
+#define AUTHORITY_INTF "org.freedesktop.PolicyKit1.Authority"
+#define AUTHORITY_PATH "/org/freedesktop/PolicyKit1/Authority"
+
+int polkit_check_authorization(DBusConnection *conn,
+                               const char *action, gboolean interaction,
+                               void (*function) (dbus_bool_t authorized,
+                                                       void *user_data),
+                                               void *user_data, int timeout)
+{
+       struct authorization_data *data;
+       DBusMessage *msg;
+       DBusMessageIter iter;
+       DBusPendingCall *call;
+       dbus_uint32_t flags = 0x00000000;
+
+       if (conn == NULL)
+               return -EINVAL;
+
+       data = dbus_malloc0(sizeof(*data));
+       if (data == NULL)
+               return -ENOMEM;
+
+       msg = dbus_message_new_method_call(AUTHORITY_DBUS, AUTHORITY_PATH,
+                               AUTHORITY_INTF, "CheckAuthorization");
+       if (msg == NULL) {
+               dbus_free(data);
+               return -ENOMEM;
+       }
+
+       if (interaction == TRUE)
+               flags |= 0x00000001;
+
+       if (action == NULL)
+               action = "org.freedesktop.policykit.exec";
+
+       dbus_message_iter_init_append(msg, &iter);
+       add_arguments(conn, &iter, action, flags);
+
+       if (dbus_connection_send_with_reply(conn, msg,
+                                               &call, timeout) == FALSE) {
+               dbus_message_unref(msg);
+               dbus_free(data);
+               return -EIO;
+       }
+
+       if (call == NULL) {
+               dbus_message_unref(msg);
+               dbus_free(data);
+               return -EIO;
+       }
+
+       data->function = function;
+       data->user_data = user_data;
+
+       dbus_pending_call_set_notify(call, authorization_reply,
+                                                       data, dbus_free);
+
+       dbus_message_unref(msg);
+
+       return 0;
+}
diff --git a/gdbus/watch.c b/gdbus/watch.c
new file mode 100644 (file)
index 0000000..558a47c
--- /dev/null
@@ -0,0 +1,747 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  Copyright (C) 2004-2011  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "gdbus.h"
+
+#define info(fmt...)
+#define error(fmt...)
+#define debug(fmt...)
+
+static DBusHandlerResult message_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data);
+
+static guint listener_id = 0;
+static GSList *listeners = NULL;
+
+struct service_data {
+       DBusConnection *conn;
+       DBusPendingCall *call;
+       char *name;
+       const char *owner;
+       guint id;
+       struct filter_callback *callback;
+};
+
+struct filter_callback {
+       GDBusWatchFunction conn_func;
+       GDBusWatchFunction disc_func;
+       GDBusSignalFunction signal_func;
+       GDBusDestroyFunction destroy_func;
+       struct service_data *data;
+       void *user_data;
+       guint id;
+};
+
+struct filter_data {
+       DBusConnection *connection;
+       DBusHandleMessageFunction handle_func;
+       char *name;
+       char *owner;
+       char *path;
+       char *interface;
+       char *member;
+       char *argument;
+       GSList *callbacks;
+       GSList *processed;
+       guint name_watch;
+       gboolean lock;
+       gboolean registered;
+};
+
+static struct filter_data *filter_data_find(DBusConnection *connection,
+                                                       const char *name,
+                                                       const char *owner,
+                                                       const char *path,
+                                                       const char *interface,
+                                                       const char *member,
+                                                       const char *argument)
+{
+       GSList *current;
+
+       for (current = listeners;
+                       current != NULL; current = current->next) {
+               struct filter_data *data = current->data;
+
+               if (connection != data->connection)
+                       continue;
+
+               if (name && data->name &&
+                               g_str_equal(name, data->name) == FALSE)
+                       continue;
+
+               if (owner && data->owner &&
+                               g_str_equal(owner, data->owner) == FALSE)
+                       continue;
+
+               if (path && data->path &&
+                               g_str_equal(path, data->path) == FALSE)
+                       continue;
+
+               if (interface && data->interface &&
+                               g_str_equal(interface, data->interface) == FALSE)
+                       continue;
+
+               if (member && data->member &&
+                               g_str_equal(member, data->member) == FALSE)
+                       continue;
+
+               if (argument && data->argument &&
+                               g_str_equal(argument, data->argument) == FALSE)
+                       continue;
+
+               return data;
+       }
+
+       return NULL;
+}
+
+static void format_rule(struct filter_data *data, char *rule, size_t size)
+{
+       const char *sender;
+       int offset;
+
+       offset = snprintf(rule, size, "type='signal'");
+       sender = data->name ? : data->owner;
+
+       if (sender)
+               offset += snprintf(rule + offset, size - offset,
+                               ",sender='%s'", sender);
+       if (data->path)
+               offset += snprintf(rule + offset, size - offset,
+                               ",path='%s'", data->path);
+       if (data->interface)
+               offset += snprintf(rule + offset, size - offset,
+                               ",interface='%s'", data->interface);
+       if (data->member)
+               offset += snprintf(rule + offset, size - offset,
+                               ",member='%s'", data->member);
+       if (data->argument)
+               snprintf(rule + offset, size - offset,
+                               ",arg0='%s'", data->argument);
+}
+
+static gboolean add_match(struct filter_data *data,
+                               DBusHandleMessageFunction filter)
+{
+       DBusError err;
+       char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
+
+       format_rule(data, rule, sizeof(rule));
+       dbus_error_init(&err);
+
+       dbus_bus_add_match(data->connection, rule, &err);
+       if (dbus_error_is_set(&err)) {
+               error("Adding match rule \"%s\" failed: %s", rule,
+                               err.message);
+               dbus_error_free(&err);
+               return FALSE;
+       }
+
+       data->handle_func = filter;
+       data->registered = TRUE;
+
+       return TRUE;
+}
+
+static gboolean remove_match(struct filter_data *data)
+{
+       DBusError err;
+       char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
+
+       format_rule(data, rule, sizeof(rule));
+
+       dbus_error_init(&err);
+
+       dbus_bus_remove_match(data->connection, rule, &err);
+       if (dbus_error_is_set(&err)) {
+               error("Removing owner match rule for %s failed: %s",
+                               rule, err.message);
+               dbus_error_free(&err);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static struct filter_data *filter_data_get(DBusConnection *connection,
+                                       DBusHandleMessageFunction filter,
+                                       const char *sender,
+                                       const char *path,
+                                       const char *interface,
+                                       const char *member,
+                                       const char *argument)
+{
+       struct filter_data *data;
+       const char *name = NULL, *owner = NULL;
+
+       if (filter_data_find(connection, NULL, NULL, NULL, NULL, NULL, NULL) == NULL) {
+               if (!dbus_connection_add_filter(connection,
+                                       message_filter, NULL, NULL)) {
+                       error("dbus_connection_add_filter() failed");
+                       return NULL;
+               }
+       }
+
+       if (sender == NULL)
+               goto proceed;
+
+       if (sender[0] == ':')
+               owner = sender;
+       else
+               name = sender;
+
+proceed:
+       data = filter_data_find(connection, name, owner, path, interface,
+                                       member, argument);
+       if (data)
+               return data;
+
+       data = g_new0(struct filter_data, 1);
+
+       data->connection = dbus_connection_ref(connection);
+       data->name = name ? g_strdup(name) : NULL;
+       data->owner = owner ? g_strdup(owner) : NULL;
+       data->path = g_strdup(path);
+       data->interface = g_strdup(interface);
+       data->member = g_strdup(member);
+       data->argument = g_strdup(argument);
+
+       if (!add_match(data, filter)) {
+               g_free(data);
+               return NULL;
+       }
+
+       listeners = g_slist_append(listeners, data);
+
+       return data;
+}
+
+static struct filter_callback *filter_data_find_callback(
+                                               struct filter_data *data,
+                                               guint id)
+{
+       GSList *l;
+
+       for (l = data->callbacks; l; l = l->next) {
+               struct filter_callback *cb = l->data;
+               if (cb->id == id)
+                       return cb;
+       }
+       for (l = data->processed; l; l = l->next) {
+               struct filter_callback *cb = l->data;
+               if (cb->id == id)
+                       return cb;
+       }
+
+       return NULL;
+}
+
+static void filter_data_free(struct filter_data *data)
+{
+       GSList *l;
+
+       for (l = data->callbacks; l != NULL; l = l->next)
+               g_free(l->data);
+
+       g_slist_free(data->callbacks);
+       g_dbus_remove_watch(data->connection, data->name_watch);
+       g_free(data->name);
+       g_free(data->owner);
+       g_free(data->path);
+       g_free(data->interface);
+       g_free(data->member);
+       g_free(data->argument);
+       dbus_connection_unref(data->connection);
+       g_free(data);
+}
+
+static void filter_data_call_and_free(struct filter_data *data)
+{
+       GSList *l;
+
+       for (l = data->callbacks; l != NULL; l = l->next) {
+               struct filter_callback *cb = l->data;
+               if (cb->disc_func)
+                       cb->disc_func(data->connection, cb->user_data);
+               if (cb->destroy_func)
+                       cb->destroy_func(cb->user_data);
+               g_free(cb);
+       }
+
+       filter_data_free(data);
+}
+
+static struct filter_callback *filter_data_add_callback(
+                                               struct filter_data *data,
+                                               GDBusWatchFunction connect,
+                                               GDBusWatchFunction disconnect,
+                                               GDBusSignalFunction signal,
+                                               GDBusDestroyFunction destroy,
+                                               void *user_data)
+{
+       struct filter_callback *cb = NULL;
+
+       cb = g_new0(struct filter_callback, 1);
+
+       cb->conn_func = connect;
+       cb->disc_func = disconnect;
+       cb->signal_func = signal;
+       cb->destroy_func = destroy;
+       cb->user_data = user_data;
+       cb->id = ++listener_id;
+
+       if (data->lock)
+               data->processed = g_slist_append(data->processed, cb);
+       else
+               data->callbacks = g_slist_append(data->callbacks, cb);
+
+       return cb;
+}
+
+static void service_data_free(struct service_data *data)
+{
+       struct filter_callback *callback = data->callback;
+
+       dbus_connection_unref(data->conn);
+
+       if (data->call)
+               dbus_pending_call_unref(data->call);
+
+       if (data->id)
+               g_source_remove(data->id);
+
+       g_free(data->name);
+       g_free(data);
+
+       callback->data = NULL;
+}
+
+static gboolean filter_data_remove_callback(struct filter_data *data,
+                                               struct filter_callback *cb)
+{
+       DBusConnection *connection;
+
+       data->callbacks = g_slist_remove(data->callbacks, cb);
+       data->processed = g_slist_remove(data->processed, cb);
+
+       /* Cancel pending operations */
+       if (cb->data) {
+               if (cb->data->call)
+                       dbus_pending_call_cancel(cb->data->call);
+               service_data_free(cb->data);
+       }
+
+       if (cb->destroy_func)
+               cb->destroy_func(cb->user_data);
+
+       g_free(cb);
+
+       /* Don't remove the filter if other callbacks exist or data is lock
+        * processing callbacks */
+       if (data->callbacks || data->lock)
+               return TRUE;
+
+       if (data->registered && !remove_match(data))
+               return FALSE;
+
+       connection = dbus_connection_ref(data->connection);
+       listeners = g_slist_remove(listeners, data);
+       filter_data_free(data);
+
+       /* Remove filter if there are no listeners left for the connection */
+       data = filter_data_find(connection, NULL, NULL, NULL, NULL, NULL,
+                                       NULL);
+       if (data == NULL)
+               dbus_connection_remove_filter(connection, message_filter,
+                                               NULL);
+
+       dbus_connection_unref(connection);
+
+       return TRUE;
+}
+
+static DBusHandlerResult signal_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
+{
+       struct filter_data *data = user_data;
+       struct filter_callback *cb;
+
+       while (data->callbacks) {
+               cb = data->callbacks->data;
+
+               if (cb->signal_func && !cb->signal_func(connection, message,
+                                                       cb->user_data)) {
+                       filter_data_remove_callback(data, cb);
+                       continue;
+               }
+
+               /* Check if the watch was removed/freed by the callback
+                * function */
+               if (!g_slist_find(data->callbacks, cb))
+                       continue;
+
+               data->callbacks = g_slist_remove(data->callbacks, cb);
+               data->processed = g_slist_append(data->processed, cb);
+       }
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static void update_name_cache(const char *name, const char *owner)
+{
+       GSList *l;
+
+       for (l = listeners; l != NULL; l = l->next) {
+               struct filter_data *data = l->data;
+
+               if (g_strcmp0(data->name, name) != 0)
+                       continue;
+
+               g_free(data->owner);
+               data->owner = g_strdup(owner);
+       }
+}
+
+static const char *check_name_cache(const char *name)
+{
+       GSList *l;
+
+       for (l = listeners; l != NULL; l = l->next) {
+               struct filter_data *data = l->data;
+
+               if (g_strcmp0(data->name, name) != 0)
+                       continue;
+
+               return data->owner;
+       }
+
+       return NULL;
+}
+
+static DBusHandlerResult service_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
+{
+       struct filter_data *data = user_data;
+       struct filter_callback *cb;
+       char *name, *old, *new;
+
+       if (!dbus_message_get_args(message, NULL,
+                               DBUS_TYPE_STRING, &name,
+                               DBUS_TYPE_STRING, &old,
+                               DBUS_TYPE_STRING, &new,
+                               DBUS_TYPE_INVALID)) {
+               error("Invalid arguments for NameOwnerChanged signal");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       update_name_cache(name, new);
+
+       while (data->callbacks) {
+               cb = data->callbacks->data;
+
+               if (*new == '\0') {
+                       if (cb->disc_func)
+                               cb->disc_func(connection, cb->user_data);
+               } else {
+                       if (cb->conn_func)
+                               cb->conn_func(connection, cb->user_data);
+               }
+
+               /* Check if the watch was removed/freed by the callback
+                * function */
+               if (!g_slist_find(data->callbacks, cb))
+                       continue;
+
+               /* Only auto remove if it is a bus name watch */
+               if (data->argument[0] == ':' &&
+                               (cb->conn_func == NULL || cb->disc_func == NULL)) {
+                       filter_data_remove_callback(data, cb);
+                       continue;
+               }
+
+               data->callbacks = g_slist_remove(data->callbacks, cb);
+               data->processed = g_slist_append(data->processed, cb);
+       }
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+static DBusHandlerResult message_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
+{
+       struct filter_data *data;
+       const char *sender, *path, *iface, *member, *arg = NULL;
+
+       /* Only filter signals */
+       if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       sender = dbus_message_get_sender(message);
+       path = dbus_message_get_path(message);
+       iface = dbus_message_get_interface(message);
+       member = dbus_message_get_member(message);
+       dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID);
+
+       /* Sender is always bus name */
+       data = filter_data_find(connection, NULL, sender, path, iface, member,
+                                       arg);
+       if (data == NULL) {
+               error("Got %s.%s signal which has no listeners", iface, member);
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (data->handle_func) {
+               data->lock = TRUE;
+
+               data->handle_func(connection, message, data);
+
+               data->callbacks = data->processed;
+               data->processed = NULL;
+               data->lock = FALSE;
+       }
+
+       if (data->callbacks)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       remove_match(data);
+
+       listeners = g_slist_remove(listeners, data);
+       filter_data_free(data);
+
+       /* Remove filter if there no listener left for the connection */
+       data = filter_data_find(connection, NULL, NULL, NULL, NULL, NULL,
+                                       NULL);
+       if (data == NULL)
+               dbus_connection_remove_filter(connection, message_filter,
+                                               NULL);
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static gboolean update_service(void *user_data)
+{
+       struct service_data *data = user_data;
+       struct filter_callback *cb = data->callback;
+
+       update_name_cache(data->name, data->owner);
+       if (cb->conn_func)
+               cb->conn_func(data->conn, cb->user_data);
+
+       service_data_free(data);
+
+       return FALSE;
+}
+
+static void service_reply(DBusPendingCall *call, void *user_data)
+{
+       struct service_data *data = user_data;
+       DBusMessage *reply;
+       DBusError err;
+
+       reply = dbus_pending_call_steal_reply(call);
+       if (reply == NULL)
+               return;
+       dbus_error_init(&err);
+
+       if (dbus_set_error_from_message(&err, reply))
+               goto fail;
+
+       if (dbus_message_get_args(reply, &err,
+                                       DBUS_TYPE_STRING, &data->owner,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               goto fail;
+
+       update_service(data);
+
+       goto done;
+
+fail:
+       error("%s", err.message);
+       dbus_error_free(&err);
+       service_data_free(data);
+done:
+       dbus_message_unref(reply);
+}
+
+static void check_service(DBusConnection *connection,
+                                       const char *name,
+                                       struct filter_callback *callback)
+{
+       DBusMessage *message;
+       struct service_data *data;
+
+       data = g_try_malloc0(sizeof(*data));
+       if (data == NULL) {
+               error("Can't allocate data structure");
+               return;
+       }
+
+       data->conn = dbus_connection_ref(connection);
+       data->name = g_strdup(name);
+       data->callback = callback;
+       callback->data = data;
+
+       data->owner = check_name_cache(name);
+       if (data->owner != NULL) {
+               data->id = g_idle_add(update_service, data);
+               return;
+       }
+
+       message = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
+                       DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner");
+       if (message == NULL) {
+               error("Can't allocate new message");
+               g_free(data);
+               return;
+       }
+
+       dbus_message_append_args(message, DBUS_TYPE_STRING, &name,
+                                                       DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(connection, message,
+                                                       &data->call, -1) == FALSE) {
+               error("Failed to execute method call");
+               g_free(data);
+               goto done;
+       }
+
+       if (data->call == NULL) {
+               error("D-Bus connection not available");
+               g_free(data);
+               goto done;
+       }
+
+       dbus_pending_call_set_notify(data->call, service_reply, data, NULL);
+
+done:
+       dbus_message_unref(message);
+}
+
+guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
+                               GDBusWatchFunction connect,
+                               GDBusWatchFunction disconnect,
+                               void *user_data, GDBusDestroyFunction destroy)
+{
+       struct filter_data *data;
+       struct filter_callback *cb;
+
+       if (name == NULL)
+               return 0;
+
+       data = filter_data_get(connection, service_filter, NULL, NULL,
+                               DBUS_INTERFACE_DBUS, "NameOwnerChanged",
+                               name);
+       if (data == NULL)
+               return 0;
+
+       cb = filter_data_add_callback(data, connect, disconnect, NULL, NULL,
+                                       user_data);
+       if (cb == NULL)
+               return 0;
+
+       if (connect)
+               check_service(connection, name, cb);
+
+       return cb->id;
+}
+
+guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
+                               GDBusWatchFunction func,
+                               void *user_data, GDBusDestroyFunction destroy)
+{
+       return g_dbus_add_service_watch(connection, name, NULL, func,
+                                                       user_data, destroy);
+}
+
+guint g_dbus_add_signal_watch(DBusConnection *connection,
+                               const char *sender, const char *path,
+                               const char *interface, const char *member,
+                               GDBusSignalFunction function, void *user_data,
+                               GDBusDestroyFunction destroy)
+{
+       struct filter_data *data;
+       struct filter_callback *cb;
+
+       data = filter_data_get(connection, signal_filter, sender, path,
+                               interface, member, NULL);
+       if (data == NULL)
+               return 0;
+
+       cb = filter_data_add_callback(data, NULL, NULL, function, destroy,
+                                       user_data);
+       if (cb == NULL)
+               return 0;
+
+       if (data->name != NULL && data->name_watch == 0)
+               data->name_watch = g_dbus_add_service_watch(connection,
+                                                       data->name, NULL,
+                                                       NULL, NULL, NULL);
+
+       return cb->id;
+}
+
+gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
+{
+       struct filter_data *data;
+       struct filter_callback *cb;
+       GSList *ldata;
+
+       if (id == 0)
+               return FALSE;
+
+       for (ldata = listeners; ldata; ldata = ldata->next) {
+               data = ldata->data;
+
+               cb = filter_data_find_callback(data, id);
+               if (cb) {
+                       filter_data_remove_callback(data, cb);
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+void g_dbus_remove_all_watches(DBusConnection *connection)
+{
+       struct filter_data *data;
+
+       while ((data = filter_data_find(connection, NULL, NULL, NULL, NULL,
+                                       NULL, NULL))) {
+               listeners = g_slist_remove(listeners, data);
+               filter_data_call_and_free(data);
+       }
+
+       dbus_connection_remove_filter(connection, message_filter, NULL);
+}
diff --git a/health/hdp.c b/health/hdp.c
new file mode 100644 (file)
index 0000000..7491fa0
--- /dev/null
@@ -0,0 +1,2222 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Authors:
+ *  Santiago Carot Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 <gdbus.h>
+
+#include "log.h"
+#include "error.h"
+#include <stdlib.h>
+#include <stdint.h>
+#include <hdp_types.h>
+#include <hdp_util.h>
+#include <adapter.h>
+#include <device.h>
+#include <hdp.h>
+#include <mcap.h>
+#include <btio.h>
+#include <mcap_lib.h>
+#include <l2cap.h>
+#include <sdpd.h>
+#include "../src/dbus-common.h"
+#include <unistd.h>
+
+#ifndef DBUS_TYPE_UNIX_FD
+       #define DBUS_TYPE_UNIX_FD -1
+#endif
+
+#define ECHO_TIMEOUT   1 /* second */
+#define HDP_ECHO_LEN   15
+
+static DBusConnection *connection = NULL;
+
+static GSList *applications = NULL;
+static GSList *devices = NULL;
+static uint8_t next_app_id = HDP_MDEP_INITIAL;
+
+static GSList *adapters;
+
+static gboolean update_adapter(struct hdp_adapter *adapter);
+static struct hdp_device *create_health_device(DBusConnection *conn,
+                                               struct btd_device *device);
+static void free_echo_data(struct hdp_echo_data *edata);
+
+struct hdp_create_dc {
+       DBusConnection                  *conn;
+       DBusMessage                     *msg;
+       struct hdp_application          *app;
+       struct hdp_device               *dev;
+       uint8_t                         config;
+       uint8_t                         mdep;
+       guint                           ref;
+       mcap_mdl_operation_cb           cb;
+};
+
+struct hdp_tmp_dc_data {
+       DBusConnection                  *conn;
+       DBusMessage                     *msg;
+       struct hdp_channel              *hdp_chann;
+       guint                           ref;
+       mcap_mdl_operation_cb           cb;
+};
+
+struct hdp_echo_data {
+       gboolean                echo_done;      /* Is a echo was already done */
+       gpointer                buf;            /* echo packet sent */
+       uint                    tid;            /* echo timeout */
+};
+
+static struct hdp_channel *hdp_channel_ref(struct hdp_channel *chan)
+{
+       if (!chan)
+               return NULL;
+
+       chan->ref++;
+
+       DBG("health_channel_ref(%p): ref=%d", chan, chan->ref);
+       return chan;
+}
+
+static void free_health_channel(struct hdp_channel *chan)
+{
+       if (chan->mdep == HDP_MDEP_ECHO) {
+               free_echo_data(chan->edata);
+               chan->edata = NULL;
+       }
+
+       mcap_mdl_unref(chan->mdl);
+       hdp_application_unref(chan->app);
+       health_device_unref(chan->dev);
+       g_free(chan->path);
+       g_free(chan);
+}
+
+static void hdp_channel_unref(struct hdp_channel *chan)
+{
+       if (!chan)
+               return;
+
+       chan->ref --;
+       DBG("health_channel_unref(%p): ref=%d", chan, chan->ref);
+
+       if (chan->ref > 0)
+               return;
+
+       free_health_channel(chan);
+}
+
+static void free_hdp_create_dc(struct hdp_create_dc *dc_data)
+{
+       dbus_message_unref(dc_data->msg);
+       dbus_connection_unref(dc_data->conn);
+       hdp_application_unref(dc_data->app);
+       health_device_unref(dc_data->dev);
+
+       g_free(dc_data);
+}
+
+static struct hdp_create_dc *hdp_create_data_ref(struct hdp_create_dc *dc_data)
+{
+       dc_data->ref++;
+
+       DBG("hdp_create_data_ref(%p): ref=%d", dc_data, dc_data->ref);
+
+       return dc_data;
+}
+
+static void hdp_create_data_unref(struct hdp_create_dc *dc_data)
+{
+       dc_data->ref--;
+
+       DBG("hdp_create_data_unref(%p): ref=%d", dc_data, dc_data->ref);
+
+       if (dc_data->ref > 0)
+               return;
+
+       free_hdp_create_dc(dc_data);
+}
+
+static void free_hdp_conn_dc(struct hdp_tmp_dc_data *data)
+{
+       dbus_message_unref(data->msg);
+       dbus_connection_unref(data->conn);
+       hdp_channel_unref(data->hdp_chann);
+
+       g_free(data);
+}
+
+static struct hdp_tmp_dc_data *hdp_tmp_dc_data_ref(struct hdp_tmp_dc_data *data)
+{
+       data->ref++;
+
+       DBG("hdp_conn_data_ref(%p): ref=%d", data, data->ref);
+
+       return data;
+}
+
+static void hdp_tmp_dc_data_unref(struct hdp_tmp_dc_data *data)
+{
+       data->ref--;
+
+       DBG("hdp_conn_data_unref(%p): ref=%d", data, data->ref);
+
+       if (data->ref > 0)
+               return;
+
+       free_hdp_conn_dc(data);
+}
+
+static int cmp_app_id(gconstpointer a, gconstpointer b)
+{
+       const struct hdp_application *app = a;
+       const uint8_t *id = b;
+
+       return app->id - *id;
+}
+
+static int cmp_adapter(gconstpointer a, gconstpointer b)
+{
+       const struct hdp_adapter *hdp_adapter = a;
+       const struct btd_adapter *adapter = b;
+
+       if (hdp_adapter->btd_adapter == adapter)
+               return 0;
+
+       return -1;
+}
+
+static int cmp_device(gconstpointer a, gconstpointer b)
+{
+       const struct hdp_device *hdp_device = a;
+       const struct btd_device *device = b;
+
+       if (hdp_device->dev == device)
+               return 0;
+
+       return -1;
+}
+
+static gint cmp_dev_addr(gconstpointer a, gconstpointer dst)
+{
+       const struct hdp_device *device = a;
+       bdaddr_t addr;
+
+       device_get_address(device->dev, &addr);
+       return bacmp(&addr, dst);
+}
+
+static gint cmp_dev_mcl(gconstpointer a, gconstpointer mcl)
+{
+       const struct hdp_device *device = a;
+
+       if (mcl == device->mcl)
+               return 0;
+       return -1;
+}
+
+static gint cmp_chan_mdlid(gconstpointer a, gconstpointer b)
+{
+       const struct hdp_channel *chan = a;
+       const uint16_t *mdlid = b;
+
+       return chan->mdlid - *mdlid;
+}
+
+static gint cmp_chan_path(gconstpointer a, gconstpointer b)
+{
+       const struct hdp_channel *chan = a;
+       const char *path = b;
+
+       return g_ascii_strcasecmp(chan->path, path);
+}
+
+static gint cmp_chan_mdl(gconstpointer a, gconstpointer mdl)
+{
+       const struct hdp_channel *chan = a;
+
+       if (chan->mdl == mdl)
+               return 0;
+       return -1;
+}
+
+static uint8_t get_app_id()
+{
+       uint8_t id = next_app_id;
+
+       do {
+               GSList *l = g_slist_find_custom(applications, &id, cmp_app_id);
+
+               if (!l) {
+                       next_app_id = (id % HDP_MDEP_FINAL) + 1;
+                       return id;
+               } else
+                       id = (id % HDP_MDEP_FINAL) + 1;
+       } while (id != next_app_id);
+
+       /* No more ids available */
+       return 0;
+}
+
+static int cmp_app(gconstpointer a, gconstpointer b)
+{
+       const struct hdp_application *app = a;
+
+       return g_strcmp0(app->path, b);
+}
+
+static gboolean set_app_path(struct hdp_application *app)
+{
+       app->id = get_app_id();
+       if (!app->id)
+               return FALSE;
+       app->path = g_strdup_printf(MANAGER_PATH "/health_app_%d", app->id);
+
+       return TRUE;
+};
+
+static void device_unref_mcl(struct hdp_device *hdp_device)
+{
+       if (!hdp_device->mcl)
+               return;
+
+       mcap_close_mcl(hdp_device->mcl, FALSE);
+       mcap_mcl_unref(hdp_device->mcl);
+       hdp_device->mcl = NULL;
+       hdp_device->mcl_conn = FALSE;
+}
+
+static void free_health_device(struct hdp_device *device)
+{
+       if (device->conn) {
+               dbus_connection_unref(device->conn);
+               device->conn = NULL;
+       }
+
+       if (device->dev) {
+               btd_device_unref(device->dev);
+               device->dev = NULL;
+       }
+
+       device_unref_mcl(device);
+
+       g_free(device);
+}
+
+static void remove_application(struct hdp_application *app)
+{
+       DBG("Application %s deleted", app->path);
+       hdp_application_unref(app);
+
+       g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
+}
+
+static void client_disconnected(DBusConnection *conn, void *user_data)
+{
+       struct hdp_application *app = user_data;
+
+       DBG("Client disconnected from the bus, deleting hdp application");
+       applications = g_slist_remove(applications, app);
+
+       app->dbus_watcher = 0; /* Watcher shouldn't be freed in this case */
+       remove_application(app);
+}
+
+static DBusMessage *manager_create_application(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct hdp_application *app;
+       const char *name;
+       DBusMessageIter iter;
+       GError *err = NULL;
+
+       dbus_message_iter_init(msg, &iter);
+       app = hdp_get_app_config(&iter, &err);
+       if (err) {
+               g_error_free(err);
+               return btd_error_invalid_args(msg);
+       }
+
+       name = dbus_message_get_sender(msg);
+       if (!name) {
+               hdp_application_unref(app);
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".HealthError",
+                                       "Can't get sender name");
+       }
+
+       if (!set_app_path(app)) {
+               hdp_application_unref(app);
+               return g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".HealthError",
+                               "Can't get a valid id for the application");
+       }
+
+       app->oname = g_strdup(name);
+       app->conn = dbus_connection_ref(conn);
+
+       applications = g_slist_prepend(applications, app);
+
+       app->dbus_watcher = g_dbus_add_disconnect_watch(conn, name,
+                                               client_disconnected, app, NULL);
+       g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
+
+       DBG("Health application created with id %s", app->path);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &app->path,
+                                                       DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *manager_destroy_application(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       const char *path;
+       struct hdp_application *app;
+       GSList *l;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                               DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       l = g_slist_find_custom(applications, path, cmp_app);
+
+       if (!l)
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid arguments in method call, "
+                                       "no such application");
+
+       app = l->data;
+       applications = g_slist_remove(applications, app);
+
+       remove_application(app);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void manager_path_unregister(gpointer data)
+{
+       g_slist_foreach(applications, (GFunc) hdp_application_unref, NULL);
+
+       g_slist_free(applications);
+       applications = NULL;
+
+       g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
+}
+
+static GDBusMethodTable health_manager_methods[] = {
+       {"CreateApplication", "a{sv}", "o", manager_create_application},
+       {"DestroyApplication", "o", "", manager_destroy_application},
+       { NULL }
+};
+
+static DBusMessage *channel_get_properties(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct hdp_channel *chan = user_data;
+       DBusMessageIter iter, dict;
+       DBusMessage *reply;
+       const char *path;
+       char *type;
+
+       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);
+
+       path = device_get_path(chan->dev->dev);
+       dict_append_entry(&dict, "Device", DBUS_TYPE_OBJECT_PATH, &path);
+
+       path = chan->app->path;
+       dict_append_entry(&dict, "Application", DBUS_TYPE_OBJECT_PATH, &path);
+
+       if (chan->config == HDP_RELIABLE_DC)
+               type = g_strdup("Reliable");
+       else
+               type = g_strdup("Streaming");
+
+       dict_append_entry(&dict, "Type", DBUS_TYPE_STRING, &type);
+
+       g_free(type);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static void hdp_tmp_dc_data_destroy(gpointer data)
+{
+       struct hdp_tmp_dc_data *hdp_conn = data;
+
+       hdp_tmp_dc_data_unref(hdp_conn);
+}
+
+static void abort_mdl_cb(GError *err, gpointer data)
+{
+       if (err)
+               error("Aborting error: %s", err->message);
+}
+
+static void hdp_mdl_reconn_cb(struct mcap_mdl *mdl, GError *err, gpointer data)
+{
+       struct hdp_tmp_dc_data *dc_data = data;
+       DBusMessage *reply;
+       int fd;
+
+       if (err) {
+               struct hdp_channel *chan = dc_data->hdp_chann;
+               GError *gerr = NULL;
+
+               error("%s", err->message);
+               reply = g_dbus_create_error(dc_data->msg,
+                                       ERROR_INTERFACE ".HealthError",
+                                       "Cannot reconnect: %s", err->message);
+               g_dbus_send_message(dc_data->conn, reply);
+
+               /* Send abort request because remote side */
+               /* is now in PENDING state */
+               if (!mcap_mdl_abort(chan->mdl, abort_mdl_cb, NULL, NULL,
+                                                               &gerr)) {
+                       error("%s", gerr->message);
+                       g_error_free(gerr);
+               }
+               return;
+       }
+
+       fd = mcap_mdl_get_fd(dc_data->hdp_chann->mdl);
+       if (fd < 0) {
+               reply = g_dbus_create_error(dc_data->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "Cannot get file descriptor");
+               g_dbus_send_message(dc_data->conn, reply);
+               return;
+       }
+
+       reply = g_dbus_create_reply(dc_data->msg, DBUS_TYPE_UNIX_FD,
+                                                       &fd, DBUS_TYPE_INVALID);
+       g_dbus_send_message(dc_data->conn, reply);
+
+       g_dbus_emit_signal(dc_data->conn,
+                       device_get_path(dc_data->hdp_chann->dev->dev),
+                       HEALTH_DEVICE, "ChannelConnected",
+                       DBUS_TYPE_OBJECT_PATH, &dc_data->hdp_chann->path,
+                       DBUS_TYPE_INVALID);
+}
+
+static void hdp_get_dcpsm_cb(uint16_t dcpsm, gpointer user_data, GError *err)
+{
+       struct hdp_tmp_dc_data *hdp_conn = user_data;
+       struct hdp_channel *hdp_chann = hdp_conn->hdp_chann;
+       GError *gerr = NULL;
+       uint8_t mode;
+
+       if (err) {
+               hdp_conn->cb(hdp_chann->mdl, err, hdp_conn);
+               return;
+       }
+
+       if (hdp_chann->config == HDP_RELIABLE_DC)
+               mode = L2CAP_MODE_ERTM;
+       else
+               mode = L2CAP_MODE_STREAMING;
+
+       if (mcap_connect_mdl(hdp_chann->mdl, mode, dcpsm, hdp_conn->cb,
+                                       hdp_tmp_dc_data_ref(hdp_conn),
+                                       hdp_tmp_dc_data_destroy, &gerr))
+               return;
+
+       hdp_tmp_dc_data_unref(hdp_conn);
+       hdp_conn->cb(hdp_chann->mdl, err, hdp_conn);
+       g_error_free(gerr);
+}
+
+static void device_reconnect_mdl_cb(struct mcap_mdl *mdl, GError *err,
+                                                               gpointer data)
+{
+       struct hdp_tmp_dc_data *dc_data = data;
+       GError *gerr = NULL;
+       DBusMessage *reply;
+
+       if (err) {
+               reply = g_dbus_create_error(dc_data->msg,
+                                       ERROR_INTERFACE ".HealthError",
+                                       "Cannot reconnect: %s", err->message);
+               g_dbus_send_message(dc_data->conn, reply);
+               return;
+       }
+
+       dc_data->cb = hdp_mdl_reconn_cb;
+
+       if (hdp_get_dcpsm(dc_data->hdp_chann->dev, hdp_get_dcpsm_cb,
+                                       hdp_tmp_dc_data_ref(dc_data),
+                                       hdp_tmp_dc_data_destroy, &gerr))
+               return;
+
+       error("%s", gerr->message);
+
+       reply = g_dbus_create_error(dc_data->msg,
+                                       ERROR_INTERFACE ".HealthError",
+                                       "Cannot reconnect: %s", gerr->message);
+       g_dbus_send_message(dc_data->conn, reply);
+       hdp_tmp_dc_data_unref(dc_data);
+       g_error_free(gerr);
+       gerr = NULL;
+
+       /* Send abort request because remote side is now in PENDING state */
+       if (!mcap_mdl_abort(mdl, abort_mdl_cb, NULL, NULL, &gerr)) {
+               error("%s", gerr->message);
+               g_error_free(gerr);
+       }
+}
+
+static DBusMessage *channel_acquire_continue(struct hdp_tmp_dc_data *data,
+                                                               GError *err)
+{
+       DBusMessage *reply;
+       GError *gerr = NULL;
+       int fd;
+
+       if (err) {
+               return g_dbus_create_error(data->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "%s", err->message);
+       }
+
+       fd = mcap_mdl_get_fd(data->hdp_chann->mdl);
+       if (fd >= 0)
+               return g_dbus_create_reply(data->msg, DBUS_TYPE_UNIX_FD, &fd,
+                                                       DBUS_TYPE_INVALID);
+
+       hdp_tmp_dc_data_ref(data);
+       if (mcap_reconnect_mdl(data->hdp_chann->mdl, device_reconnect_mdl_cb,
+                                       data, hdp_tmp_dc_data_destroy, &gerr))
+               return NULL;
+
+       hdp_tmp_dc_data_unref(data);
+       reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError",
+                                       "Cannot reconnect: %s", gerr->message);
+       g_error_free(gerr);
+
+       return reply;
+}
+
+static void channel_acquire_cb(gpointer data, GError *err)
+{
+       struct hdp_tmp_dc_data *dc_data = data;
+       DBusMessage *reply;
+
+       reply = channel_acquire_continue(data, err);
+
+       if (reply)
+               g_dbus_send_message(dc_data->conn, reply);
+}
+
+static DBusMessage *channel_acquire(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct hdp_channel *chan = user_data;
+       struct hdp_tmp_dc_data *dc_data;
+       GError *gerr = NULL;
+       DBusMessage *reply;
+
+       dc_data = g_new0(struct hdp_tmp_dc_data, 1);
+       dc_data->conn = dbus_connection_ref(conn);
+       dc_data->msg = dbus_message_ref(msg);
+       dc_data->hdp_chann = hdp_channel_ref(chan);
+
+       if (chan->dev->mcl_conn) {
+               reply = channel_acquire_continue(hdp_tmp_dc_data_ref(dc_data),
+                                                                       NULL);
+               hdp_tmp_dc_data_unref(dc_data);
+               return reply;
+       }
+
+       if (hdp_establish_mcl(chan->dev, channel_acquire_cb,
+                                               hdp_tmp_dc_data_ref(dc_data),
+                                               hdp_tmp_dc_data_destroy, &gerr))
+               return NULL;
+
+       reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
+                                       "%s", gerr->message);
+       hdp_tmp_dc_data_unref(dc_data);
+       g_error_free(gerr);
+
+       return reply;
+}
+
+static void close_mdl(struct hdp_channel *hdp_chann)
+{
+       int fd;
+
+       fd = mcap_mdl_get_fd(hdp_chann->mdl);
+       if (fd < 0)
+               return;
+
+       close(fd);
+}
+
+static DBusMessage *channel_release(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct hdp_channel *hdp_chann = user_data;
+
+       close_mdl(hdp_chann);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void free_echo_data(struct hdp_echo_data *edata)
+{
+       if (!edata)
+               return;
+
+       if (edata->tid)
+               g_source_remove(edata->tid);
+
+       if (edata->buf)
+               g_free(edata->buf);
+
+
+       g_free(edata);
+}
+
+static void health_channel_destroy(void *data)
+{
+       struct hdp_channel *hdp_chan = data;
+       struct hdp_device *dev = hdp_chan->dev;
+
+       DBG("Destroy Health Channel %s", hdp_chan->path);
+       if (!g_slist_find(dev->channels, hdp_chan))
+               goto end;
+
+       dev->channels = g_slist_remove(dev->channels, hdp_chan);
+
+       if (hdp_chan->mdep != HDP_MDEP_ECHO)
+               g_dbus_emit_signal(dev->conn, device_get_path(dev->dev),
+                                       HEALTH_DEVICE, "ChannelDeleted",
+                                       DBUS_TYPE_OBJECT_PATH, &hdp_chan->path,
+                                       DBUS_TYPE_INVALID);
+
+       if (hdp_chan == dev->fr) {
+               char *empty_path;
+
+               hdp_channel_unref(dev->fr);
+               dev->fr = NULL;
+               empty_path = "/";
+               emit_property_changed(dev->conn, device_get_path(dev->dev),
+                                       HEALTH_DEVICE, "MainChannel",
+                                       DBUS_TYPE_OBJECT_PATH, &empty_path);
+       }
+
+end:
+       hdp_channel_unref(hdp_chan);
+}
+
+static GDBusMethodTable health_channels_methods[] = {
+       {"GetProperties","",    "a{sv}",        channel_get_properties },
+       {"Acquire",     "",     "h",            channel_acquire,
+                                               G_DBUS_METHOD_FLAG_ASYNC },
+       {"Release",     "",     "",             channel_release },
+       { NULL }
+};
+
+static struct hdp_channel *create_channel(struct hdp_device *dev,
+                                               uint8_t config,
+                                               struct mcap_mdl *mdl,
+                                               uint16_t mdlid,
+                                               struct hdp_application *app,
+                                               GError **err)
+{
+       struct hdp_channel *hdp_chann;
+
+       if (!dev)
+               return NULL;
+
+       hdp_chann = g_new0(struct hdp_channel, 1);
+       hdp_chann->config = config;
+       hdp_chann->dev = health_device_ref(dev);
+       hdp_chann->mdlid = mdlid;
+
+       if (mdl)
+               hdp_chann->mdl = mcap_mdl_ref(mdl);
+
+       if (app) {
+               hdp_chann->mdep = app->id;
+               hdp_chann->app = hdp_application_ref(app);
+       } else
+               hdp_chann->edata = g_new0(struct hdp_echo_data, 1);
+
+       hdp_chann->path = g_strdup_printf("%s/chan%d",
+                                       device_get_path(hdp_chann->dev->dev),
+                                       hdp_chann->mdlid);
+
+       dev->channels = g_slist_append(dev->channels,
+                                               hdp_channel_ref(hdp_chann));
+
+       if (hdp_chann->mdep == HDP_MDEP_ECHO)
+               return hdp_channel_ref(hdp_chann);
+
+       if (!g_dbus_register_interface(dev->conn, hdp_chann->path,
+                                       HEALTH_CHANNEL,
+                                       health_channels_methods, NULL, NULL,
+                                       hdp_chann, health_channel_destroy)) {
+               g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR,
+                                       "Can't register the channel interface");
+               health_channel_destroy(hdp_chann);
+               return NULL;
+       }
+
+       return hdp_channel_ref(hdp_chann);
+}
+
+static void remove_channels(struct hdp_device *dev)
+{
+       struct hdp_channel *chan;
+       char *path;
+
+       while (dev->channels) {
+               chan = dev->channels->data;
+
+               path = g_strdup(chan->path);
+               if (!g_dbus_unregister_interface(dev->conn, path,
+                                                               HEALTH_CHANNEL))
+                       health_channel_destroy(chan);
+               g_free(path);
+       }
+}
+
+static void close_device_con(struct hdp_device *dev, gboolean cache)
+{
+       if (!dev->mcl)
+               return;
+
+       mcap_close_mcl(dev->mcl, cache);
+       dev->mcl_conn = FALSE;
+
+       if (cache)
+               return;
+
+       device_unref_mcl(dev);
+       remove_channels(dev);
+
+       if (!dev->sdp_present) {
+               const char *path;
+
+               path = device_get_path(dev->dev);
+               g_dbus_unregister_interface(dev->conn, path, HEALTH_DEVICE);
+       }
+}
+
+static int send_echo_data(int sock, const void *buf, uint32_t size)
+{
+       const uint8_t *buf_b = buf;
+       uint32_t sent = 0;
+
+       while (sent < size) {
+               int n = write(sock, buf_b + sent, size - sent);
+               if (n < 0)
+                       return -1;
+               sent += n;
+       }
+
+       return 0;
+}
+
+static gboolean serve_echo(GIOChannel *io_chan, GIOCondition cond,
+                                                               gpointer data)
+{
+       struct hdp_channel *chan = data;
+       uint8_t buf[MCAP_DC_MTU];
+       int fd, len;
+
+       if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+               hdp_channel_unref(chan);
+               return FALSE;
+       }
+
+       if (chan->edata->echo_done)
+               goto fail;
+
+       chan->edata->echo_done = TRUE;
+
+       fd = g_io_channel_unix_get_fd(io_chan);
+       len = read(fd, buf, sizeof(buf));
+
+       if (send_echo_data(fd, buf, len)  >= 0)
+               return TRUE;
+
+fail:
+       close_device_con(chan->dev, FALSE);
+       hdp_channel_unref(chan);
+       return FALSE;
+}
+
+static gboolean check_channel_conf(struct hdp_channel *chan)
+{
+       GError *err = NULL;
+       GIOChannel *io;
+       uint8_t mode;
+       uint16_t imtu, omtu;
+       int fd;
+
+       fd = mcap_mdl_get_fd(chan->mdl);
+       if (fd < 0)
+               return FALSE;
+       io = g_io_channel_unix_new(fd);
+
+       if (!bt_io_get(io, BT_IO_L2CAP, &err,
+                       BT_IO_OPT_MODE, &mode,
+                       BT_IO_OPT_IMTU, &imtu,
+                       BT_IO_OPT_OMTU, &omtu,
+                       BT_IO_OPT_INVALID)) {
+               error("Error: %s", err->message);
+               g_io_channel_unref(io);
+               g_error_free(err);
+               return FALSE;
+       }
+
+       g_io_channel_unref(io);
+
+       switch (chan->config) {
+       case HDP_RELIABLE_DC:
+               if (mode != L2CAP_MODE_ERTM)
+                       return FALSE;
+               break;
+       case HDP_STREAMING_DC:
+               if (mode != L2CAP_MODE_STREAMING)
+                       return FALSE;
+               break;
+       default:
+               error("Error: Connected with unknown configuration");
+               return FALSE;
+       }
+
+       DBG("MDL imtu %d omtu %d Channel imtu %d omtu %d", imtu, omtu,
+                                               chan->imtu, chan->omtu);
+
+       if (!chan->imtu)
+               chan->imtu = imtu;
+       if (!chan->omtu)
+               chan->omtu = omtu;
+
+       if (chan->imtu != imtu || chan->omtu != omtu)
+               return FALSE;
+
+       return TRUE;
+}
+
+static void hdp_mcap_mdl_connected_cb(struct mcap_mdl *mdl, void *data)
+{
+       struct hdp_device *dev = data;
+       struct hdp_channel *chan;
+
+       DBG("hdp_mcap_mdl_connected_cb");
+       if (!dev->ndc)
+               return;
+
+       chan = dev->ndc;
+       if (!chan->mdl)
+               chan->mdl = mcap_mdl_ref(mdl);
+
+       if (!g_slist_find(dev->channels, chan))
+               dev->channels = g_slist_prepend(dev->channels,
+                                                       hdp_channel_ref(chan));
+
+       if (!check_channel_conf(chan)) {
+               close_mdl(chan);
+               goto end;
+       }
+
+       if (chan->mdep == HDP_MDEP_ECHO) {
+               GIOChannel *io;
+               int fd;
+
+               fd = mcap_mdl_get_fd(chan->mdl);
+               if (fd < 0)
+                       goto end;
+
+               chan->edata->echo_done = FALSE;
+               io = g_io_channel_unix_new(fd);
+               g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN,
+                               serve_echo, hdp_channel_ref(chan));
+               g_io_channel_unref(io);
+               goto end;
+       }
+
+       g_dbus_emit_signal(dev->conn, device_get_path(dev->dev), HEALTH_DEVICE,
+                                       "ChannelConnected",
+                                       DBUS_TYPE_OBJECT_PATH, &chan->path,
+                                       DBUS_TYPE_INVALID);
+
+       if (dev->fr)
+               goto end;
+
+       dev->fr = hdp_channel_ref(chan);
+
+       emit_property_changed(dev->conn, device_get_path(dev->dev),
+                                       HEALTH_DEVICE, "MainChannel",
+                                       DBUS_TYPE_OBJECT_PATH, &dev->fr->path);
+
+end:
+       hdp_channel_unref(dev->ndc);
+       dev->ndc = NULL;
+}
+
+static void hdp_mcap_mdl_closed_cb(struct mcap_mdl *mdl, void *data)
+{
+       /* struct hdp_device *dev = data; */
+
+       DBG("hdp_mcap_mdl_closed_cb");
+
+       /* Nothing to do */
+}
+
+static void hdp_mcap_mdl_deleted_cb(struct mcap_mdl *mdl, void *data)
+{
+       struct hdp_device *dev = data;
+       struct hdp_channel *chan;
+       char *path;
+       GSList *l;
+
+       DBG("hdp_mcap_mdl_deleted_cb");
+       l = g_slist_find_custom(dev->channels, mdl, cmp_chan_mdl);
+       if (!l)
+               return;
+
+       chan = l->data;
+
+       path = g_strdup(chan->path);
+       if (!g_dbus_unregister_interface(dev->conn, path, HEALTH_CHANNEL))
+               health_channel_destroy(chan);
+       g_free(path);
+}
+
+static void hdp_mcap_mdl_aborted_cb(struct mcap_mdl *mdl, void *data)
+{
+       struct hdp_device *dev = data;
+
+       DBG("hdp_mcap_mdl_aborted_cb");
+       if (!dev->ndc)
+               return;
+
+       dev->ndc->mdl = mcap_mdl_ref(mdl);
+
+       if (!g_slist_find(dev->channels, dev->ndc))
+               dev->channels = g_slist_prepend(dev->channels,
+                                               hdp_channel_ref(dev->ndc));
+
+       if (dev->ndc->mdep != HDP_MDEP_ECHO)
+               g_dbus_emit_signal(dev->conn, device_get_path(dev->dev),
+                                       HEALTH_DEVICE, "ChannelConnected",
+                                       DBUS_TYPE_OBJECT_PATH, &dev->ndc->path,
+                                       DBUS_TYPE_INVALID);
+
+       hdp_channel_unref(dev->ndc);
+       dev->ndc = NULL;
+}
+
+static uint8_t hdp2l2cap_mode(uint8_t hdp_mode)
+{
+       return hdp_mode == HDP_STREAMING_DC ? L2CAP_MODE_STREAMING :
+                                                               L2CAP_MODE_ERTM;
+}
+
+static uint8_t hdp_mcap_mdl_conn_req_cb(struct mcap_mcl *mcl, uint8_t mdepid,
+                               uint16_t mdlid, uint8_t *conf, void *data)
+{
+       struct hdp_device *dev = data;
+       struct hdp_application *app;
+       GError *err = NULL;
+       GSList *l;
+
+       DBG("Data channel request");
+
+       if (mdepid == HDP_MDEP_ECHO) {
+               switch (*conf) {
+               case HDP_NO_PREFERENCE_DC:
+                       *conf = HDP_RELIABLE_DC;
+               case HDP_RELIABLE_DC:
+                       break;
+               case HDP_STREAMING_DC:
+                       return MCAP_CONFIGURATION_REJECTED;
+               default:
+                       /* Special case defined in HDP spec 3.4. When an invalid
+                       * configuration is received we shall close the MCL when
+                       * we are still processing the callback. */
+                       close_device_con(dev, FALSE);
+                       return MCAP_CONFIGURATION_REJECTED; /* not processed */
+               }
+
+               if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi,
+                                               L2CAP_MODE_ERTM, &err)) {
+                       error("Error: %s", err->message);
+                       g_error_free(err);
+                       return MCAP_MDL_BUSY;
+               }
+
+               dev->ndc = create_channel(dev, *conf, NULL, mdlid, NULL, NULL);
+               if (!dev->ndc)
+                       return MCAP_MDL_BUSY;
+
+               return MCAP_SUCCESS;
+       }
+
+       l = g_slist_find_custom(applications, &mdepid, cmp_app_id);
+       if (!l)
+               return MCAP_INVALID_MDEP;
+
+       app = l->data;
+
+       /* Check if is the first dc if so,
+       * only reliable configuration is allowed */
+       switch (*conf) {
+       case HDP_NO_PREFERENCE_DC:
+               if (app->role == HDP_SINK)
+                       return MCAP_CONFIGURATION_REJECTED;
+               else if (dev->fr && app->chan_type_set)
+                       *conf = app->chan_type;
+               else
+                       *conf = HDP_RELIABLE_DC;
+               break;
+       case HDP_STREAMING_DC:
+               if (!dev->fr || app->role == HDP_SOURCE)
+                       return MCAP_CONFIGURATION_REJECTED;
+       case HDP_RELIABLE_DC:
+               if (app->role == HDP_SOURCE)
+                       return MCAP_CONFIGURATION_REJECTED;
+               break;
+       default:
+               /* Special case defined in HDP spec 3.4. When an invalid
+               * configuration is received we shall close the MCL when
+               * we are still processing the callback. */
+               close_device_con(dev, FALSE);
+               return MCAP_CONFIGURATION_REJECTED; /* not processed */
+       }
+
+       l = g_slist_find_custom(dev->channels, &mdlid, cmp_chan_mdlid);
+       if (l) {
+               struct hdp_channel *chan = l->data;
+               char *path;
+
+               path = g_strdup(chan->path);
+               g_dbus_unregister_interface(dev->conn, path, HEALTH_CHANNEL);
+               g_free(path);
+       }
+
+       if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi,
+                                               hdp2l2cap_mode(*conf), &err)) {
+               error("Error: %s", err->message);
+               g_error_free(err);
+               return MCAP_MDL_BUSY;
+       }
+
+       dev->ndc = create_channel(dev, *conf, NULL, mdlid, app, NULL);
+       if (!dev->ndc)
+               return MCAP_MDL_BUSY;
+
+       return MCAP_SUCCESS;
+}
+
+static uint8_t hdp_mcap_mdl_reconn_req_cb(struct mcap_mdl *mdl, void *data)
+{
+       struct hdp_device *dev = data;
+       struct hdp_channel *chan;
+       GError *err = NULL;
+       GSList *l;
+
+       l = g_slist_find_custom(dev->channels, mdl, cmp_chan_mdl);
+       if (!l)
+               return MCAP_INVALID_MDL;
+
+       chan = l->data;
+
+       if (!dev->fr && (chan->config != HDP_RELIABLE_DC) &&
+                                               (chan->mdep != HDP_MDEP_ECHO))
+               return MCAP_UNSPECIFIED_ERROR;
+
+       if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi,
+                                       hdp2l2cap_mode(chan->config), &err)) {
+               error("Error: %s", err->message);
+               g_error_free(err);
+               return MCAP_MDL_BUSY;
+       }
+
+       dev->ndc = hdp_channel_ref(chan);
+
+       return MCAP_SUCCESS;
+}
+
+gboolean hdp_set_mcl_cb(struct hdp_device *device, GError **err)
+{
+       gboolean ret;
+
+       if (!device->mcl)
+               return FALSE;
+
+       ret = mcap_mcl_set_cb(device->mcl, device, err,
+               MCAP_MDL_CB_CONNECTED, hdp_mcap_mdl_connected_cb,
+               MCAP_MDL_CB_CLOSED, hdp_mcap_mdl_closed_cb,
+               MCAP_MDL_CB_DELETED, hdp_mcap_mdl_deleted_cb,
+               MCAP_MDL_CB_ABORTED, hdp_mcap_mdl_aborted_cb,
+               MCAP_MDL_CB_REMOTE_CONN_REQ, hdp_mcap_mdl_conn_req_cb,
+               MCAP_MDL_CB_REMOTE_RECONN_REQ, hdp_mcap_mdl_reconn_req_cb,
+               MCAP_MDL_CB_INVALID);
+
+       if (ret)
+               return TRUE;
+
+       error("Can't set mcl callbacks, closing mcl");
+       close_device_con(device, TRUE);
+
+       return FALSE;
+}
+
+static void mcl_connected(struct mcap_mcl *mcl, gpointer data)
+{
+       struct hdp_device *hdp_device;
+       bdaddr_t addr;
+       GSList *l;
+
+       mcap_mcl_get_addr(mcl, &addr);
+       l = g_slist_find_custom(devices, &addr, cmp_dev_addr);
+       if (!l) {
+               struct hdp_adapter *hdp_adapter = data;
+               struct btd_device *device;
+               char str[18];
+
+               ba2str(&addr, str);
+               device = adapter_get_device(connection,
+                                       hdp_adapter->btd_adapter, str);
+               if (!device)
+                       return;
+               hdp_device = create_health_device(connection, device);
+               if (!hdp_device)
+                       return;
+               devices = g_slist_append(devices, hdp_device);
+       } else
+               hdp_device = l->data;
+
+       hdp_device->mcl = mcap_mcl_ref(mcl);
+       hdp_device->mcl_conn = TRUE;
+
+       DBG("New mcl connected from  %s", device_get_path(hdp_device->dev));
+
+       hdp_set_mcl_cb(hdp_device, NULL);
+}
+
+static void mcl_reconnected(struct mcap_mcl *mcl, gpointer data)
+{
+       struct hdp_device *hdp_device;
+       GSList *l;
+
+       l = g_slist_find_custom(devices, mcl, cmp_dev_mcl);
+       if (!l)
+               return;
+
+       hdp_device = l->data;
+       hdp_device->mcl_conn = TRUE;
+
+       DBG("MCL reconnected %s", device_get_path(hdp_device->dev));
+
+       hdp_set_mcl_cb(hdp_device, NULL);
+}
+
+static void mcl_disconnected(struct mcap_mcl *mcl, gpointer data)
+{
+       struct hdp_device *hdp_device;
+       GSList *l;
+
+       l = g_slist_find_custom(devices, mcl, cmp_dev_mcl);
+       if (!l)
+               return;
+
+       hdp_device = l->data;
+       hdp_device->mcl_conn = FALSE;
+
+       DBG("Mcl disconnected %s", device_get_path(hdp_device->dev));
+}
+
+static void mcl_uncached(struct mcap_mcl *mcl, gpointer data)
+{
+       struct hdp_device *hdp_device;
+       const char *path;
+       GSList *l;
+
+       l = g_slist_find_custom(devices, mcl, cmp_dev_mcl);
+       if (!l)
+               return;
+
+       hdp_device = l->data;
+       device_unref_mcl(hdp_device);
+
+       if (hdp_device->sdp_present)
+               return;
+
+       /* Because remote device hasn't announced an HDP record */
+       /* the Bluetooth daemon won't notify when the device shall */
+       /* be removed. Then we have to remove the HealthDevice */
+       /* interface manually */
+       path = device_get_path(hdp_device->dev);
+       g_dbus_unregister_interface(hdp_device->conn, path, HEALTH_DEVICE);
+       DBG("Mcl uncached %s", path);
+}
+
+static void check_devices_mcl()
+{
+       struct hdp_device *dev;
+       GSList *l, *to_delete = NULL;
+
+       for (l = devices; l; l = l->next) {
+               dev = l->data;
+               device_unref_mcl(dev);
+
+               if (!dev->sdp_present)
+                       to_delete = g_slist_append(to_delete, dev);
+               else
+                       remove_channels(dev);
+       }
+
+       for (l = to_delete; l; l = l->next) {
+               const char *path;
+
+               path = device_get_path(dev->dev);
+               g_dbus_unregister_interface(dev->conn, path, HEALTH_DEVICE);
+       }
+
+       g_slist_free(to_delete);
+}
+
+static void release_adapter_instance(struct hdp_adapter *hdp_adapter)
+{
+       if (!hdp_adapter->mi)
+               return;
+
+       check_devices_mcl();
+       mcap_release_instance(hdp_adapter->mi);
+       mcap_instance_unref(hdp_adapter->mi);
+       hdp_adapter->mi = NULL;
+}
+
+static gboolean update_adapter(struct hdp_adapter *hdp_adapter)
+{
+       GError *err = NULL;
+       bdaddr_t addr;
+
+       if (!applications) {
+               release_adapter_instance(hdp_adapter);
+               goto update;
+       }
+
+       if (hdp_adapter->mi)
+               goto update;
+
+       adapter_get_address(hdp_adapter->btd_adapter, &addr);
+       hdp_adapter->mi = mcap_create_instance(&addr, BT_IO_SEC_MEDIUM, 0, 0,
+                                       mcl_connected, mcl_reconnected,
+                                       mcl_disconnected, mcl_uncached,
+                                       NULL, /* CSP is not used by now */
+                                       hdp_adapter, &err);
+
+       if (!hdp_adapter->mi) {
+               error("Error creating the MCAP instance: %s", err->message);
+               g_error_free(err);
+               return FALSE;
+       }
+
+       hdp_adapter->ccpsm = mcap_get_ctrl_psm(hdp_adapter->mi, &err);
+       if (err) {
+               error("Error getting MCAP control PSM: %s", err->message);
+               goto fail;
+       }
+
+       hdp_adapter->dcpsm = mcap_get_data_psm(hdp_adapter->mi, &err);
+       if (err) {
+               error("Error getting MCAP data PSM: %s", err->message);
+               goto fail;
+       }
+
+update:
+       if (hdp_update_sdp_record(hdp_adapter, applications))
+               return TRUE;
+       error("Error updating the SDP record");
+
+fail:
+       release_adapter_instance(hdp_adapter);
+       if (err)
+               g_error_free(err);
+       return FALSE;
+}
+
+int hdp_adapter_register(DBusConnection *conn, struct btd_adapter *adapter)
+{
+       struct hdp_adapter *hdp_adapter;
+
+       hdp_adapter = g_new0(struct hdp_adapter, 1);
+       hdp_adapter->btd_adapter = btd_adapter_ref(adapter);
+
+       if(!update_adapter(hdp_adapter))
+               goto fail;
+
+       adapters = g_slist_append(adapters, hdp_adapter);
+
+       return 0;
+
+fail:
+       btd_adapter_unref(hdp_adapter->btd_adapter);
+       g_free(hdp_adapter);
+       return -1;
+}
+
+void hdp_adapter_unregister(struct btd_adapter *adapter)
+{
+       struct hdp_adapter *hdp_adapter;
+       GSList *l;
+
+       l = g_slist_find_custom(adapters, adapter, cmp_adapter);
+
+       if (!l)
+               return;
+
+       hdp_adapter = l->data;
+       adapters = g_slist_remove(adapters, hdp_adapter);
+       if (hdp_adapter->sdp_handler)
+               remove_record_from_server(hdp_adapter->sdp_handler);
+       release_adapter_instance(hdp_adapter);
+       btd_adapter_unref(hdp_adapter->btd_adapter);
+       g_free(hdp_adapter);
+}
+
+static void delete_echo_channel_cb(GError *err, gpointer chan)
+{
+       if (err && err->code != MCAP_INVALID_MDL) {
+               /* TODO: Decide if more action is required here */
+               error("Error deleting echo channel: %s", err->message);
+               return;
+       }
+
+       health_channel_destroy(chan);
+}
+
+static void delete_echo_channel(struct hdp_channel *chan)
+{
+       GError *err = NULL;
+
+       if (!chan->dev->mcl_conn) {
+               error("Echo channel cannot be deleted: mcl closed");
+               return;
+       }
+
+       if (mcap_delete_mdl(chan->mdl, delete_echo_channel_cb,
+                               hdp_channel_ref(chan),
+                               (GDestroyNotify) hdp_channel_unref, &err))
+               return;
+
+       hdp_channel_unref(chan);
+       error("Error deleting the echo channel: %s", err->message);
+       g_error_free(err);
+
+       /* TODO: Decide if more action is required here */
+}
+
+static void abort_echo_channel_cb(GError *err, gpointer data)
+{
+       struct hdp_channel *chan = data;
+
+       if (err && err->code != MCAP_ERROR_INVALID_OPERATION) {
+               error("Aborting error: %s", err->message);
+               if (err->code == MCAP_INVALID_MDL) {
+                       /* MDL is removed from MCAP so we can */
+                       /* free the data channel without sending */
+                       /* a MD_DELETE_MDL_REQ */
+                       /* TODO review the above comment */
+                       /* hdp_channel_unref(chan); */
+               }
+               return;
+       }
+
+       delete_echo_channel(chan);
+}
+
+static void destroy_create_dc_data(gpointer data)
+{
+       struct hdp_create_dc *dc_data = data;
+
+       hdp_create_data_unref(dc_data);
+}
+
+static void *generate_echo_packet()
+{
+       uint8_t *buf;
+       int i;
+
+       buf = g_malloc(HDP_ECHO_LEN);
+       srand(time(NULL));
+
+       for(i = 0; i < HDP_ECHO_LEN; i++)
+               buf[i] = rand() % UINT8_MAX;
+
+       return buf;
+}
+
+static gboolean check_echo(GIOChannel *io_chan, GIOCondition cond,
+                                                               gpointer data)
+{
+       struct hdp_tmp_dc_data *hdp_conn =  data;
+       struct hdp_echo_data *edata = hdp_conn->hdp_chann->edata;
+       struct hdp_channel *chan = hdp_conn->hdp_chann;
+       uint8_t buf[MCAP_DC_MTU];
+       DBusMessage *reply;
+       gboolean value;
+       int fd, len;
+
+       if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+               value = FALSE;
+               goto end;
+       }
+
+       fd = g_io_channel_unix_get_fd(io_chan);
+       len = read(fd, buf, sizeof(buf));
+
+       if (len != HDP_ECHO_LEN) {
+               value = FALSE;
+               goto end;
+       }
+
+       value = (memcmp(buf, edata->buf, len) == 0);
+
+end:
+       reply = g_dbus_create_reply(hdp_conn->msg, DBUS_TYPE_BOOLEAN, &value,
+                                                       DBUS_TYPE_INVALID);
+       g_dbus_send_message(hdp_conn->conn, reply);
+       g_source_remove(edata->tid);
+       edata->tid = 0;
+       g_free(edata->buf);
+       edata->buf = NULL;
+
+       if (!value)
+               close_device_con(chan->dev, FALSE);
+       else
+               delete_echo_channel(chan);
+       hdp_tmp_dc_data_unref(hdp_conn);
+
+       return FALSE;
+}
+
+static gboolean echo_timeout(gpointer data)
+{
+       struct hdp_channel *chan = data;
+       GIOChannel *io;
+       int fd;
+
+       error("Error: Echo request timeout");
+       chan->edata->tid = 0;
+
+       fd = mcap_mdl_get_fd(chan->mdl);
+       if (fd < 0)
+               return FALSE;
+
+       io = g_io_channel_unix_new(fd);
+       g_io_channel_shutdown(io, TRUE, NULL);
+
+       return FALSE;
+}
+
+static void hdp_echo_connect_cb(struct mcap_mdl *mdl, GError *err,
+                                                               gpointer data)
+{
+       struct hdp_tmp_dc_data *hdp_conn =  data;
+       struct hdp_echo_data *edata;
+       GError *gerr = NULL;
+       DBusMessage *reply;
+       GIOChannel *io;
+       int fd;
+
+       if (err) {
+               reply = g_dbus_create_error(hdp_conn->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "%s", err->message);
+               g_dbus_send_message(hdp_conn->conn, reply);
+
+               /* Send abort request because remote */
+               /* side is now in PENDING state. */
+               if (!mcap_mdl_abort(hdp_conn->hdp_chann->mdl,
+                                       abort_echo_channel_cb,
+                                       hdp_channel_ref(hdp_conn->hdp_chann),
+                                       (GDestroyNotify) hdp_channel_unref,
+                                       &gerr)) {
+                       error("%s", gerr->message);
+                       g_error_free(gerr);
+                       hdp_channel_unref(hdp_conn->hdp_chann);
+               }
+               return;
+       }
+
+       fd = mcap_mdl_get_fd(hdp_conn->hdp_chann->mdl);
+       if (fd < 0) {
+               reply = g_dbus_create_error(hdp_conn->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "Can't write in echo channel");
+               g_dbus_send_message(hdp_conn->conn, reply);
+               delete_echo_channel(hdp_conn->hdp_chann);
+               return;
+       }
+
+       edata = hdp_conn->hdp_chann->edata;
+       edata->buf = generate_echo_packet();
+       send_echo_data(fd, edata->buf, HDP_ECHO_LEN);
+
+       io = g_io_channel_unix_new(fd);
+       g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN,
+                       check_echo, hdp_tmp_dc_data_ref(hdp_conn));
+
+       edata->tid  = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT,
+                                       ECHO_TIMEOUT, echo_timeout,
+                                       hdp_channel_ref(hdp_conn->hdp_chann),
+                                       (GDestroyNotify) hdp_channel_unref);
+
+       g_io_channel_unref(io);
+}
+
+static void delete_mdl_cb(GError *err, gpointer data)
+{
+       if (err)
+               error("Deleting error: %s", err->message);
+}
+
+static void abort_and_del_mdl_cb(GError *err, gpointer data)
+{
+       struct mcap_mdl *mdl = data;
+       GError *gerr = NULL;
+
+       if (err) {
+               error("%s", err->message);
+               if (err->code == MCAP_INVALID_MDL) {
+                       /* MDL is removed from MCAP so we don't */
+                       /* need to delete it. */
+                       return;
+               }
+       }
+
+       if (!mcap_delete_mdl(mdl, delete_mdl_cb, NULL, NULL, &gerr)) {
+               error("%s", gerr->message);
+               g_error_free(gerr);
+       }
+}
+
+static void hdp_mdl_conn_cb(struct mcap_mdl *mdl, GError *err, gpointer data)
+{
+       struct hdp_tmp_dc_data *hdp_conn =  data;
+       struct hdp_channel *hdp_chann = hdp_conn->hdp_chann;
+       struct hdp_device *dev = hdp_chann->dev;
+       DBusMessage *reply;
+       GError *gerr = NULL;
+
+       if (err) {
+               error("%s", err->message);
+               reply = g_dbus_create_reply(hdp_conn->msg,
+                                       DBUS_TYPE_OBJECT_PATH, &hdp_chann->path,
+                                       DBUS_TYPE_INVALID);
+               g_dbus_send_message(hdp_conn->conn, reply);
+
+               /* Send abort request because remote side */
+               /* is now in PENDING state */
+               if (!mcap_mdl_abort(hdp_chann->mdl, abort_mdl_cb, NULL,
+                                                               NULL, &gerr)) {
+                       error("%s", gerr->message);
+                       g_error_free(gerr);
+               }
+               return;
+       }
+
+       reply = g_dbus_create_reply(hdp_conn->msg,
+                                       DBUS_TYPE_OBJECT_PATH, &hdp_chann->path,
+                                       DBUS_TYPE_INVALID);
+       g_dbus_send_message(hdp_conn->conn, reply);
+
+       if (!check_channel_conf(hdp_chann)) {
+               close_mdl(hdp_chann);
+               return;
+       }
+
+       if (dev->fr)
+               return;
+
+       dev->fr = hdp_channel_ref(hdp_chann);
+
+       emit_property_changed(dev->conn, device_get_path(dev->dev),
+                                       HEALTH_DEVICE, "MainChannel",
+                                       DBUS_TYPE_OBJECT_PATH, &dev->fr->path);
+}
+
+static void device_create_mdl_cb(struct mcap_mdl *mdl, uint8_t conf,
+                                               GError *err, gpointer data)
+{
+       struct hdp_create_dc *user_data = data;
+       struct hdp_tmp_dc_data *hdp_conn;
+       struct hdp_channel *hdp_chan;
+       GError *gerr = NULL;
+       DBusMessage *reply;
+
+       if (err) {
+               reply = g_dbus_create_error(user_data->msg,
+                                       ERROR_INTERFACE ".HealthError",
+                                       "%s", err->message);
+               g_dbus_send_message(user_data->conn, reply);
+               return;
+       }
+
+       if (user_data->mdep != HDP_MDEP_ECHO &&
+                               user_data->config == HDP_NO_PREFERENCE_DC) {
+               if (!user_data->dev->fr && (conf != HDP_RELIABLE_DC)) {
+                       g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                       "Data channel aborted, first data "
+                                       "channel should be reliable");
+                       goto fail;
+               } else if (conf == HDP_NO_PREFERENCE_DC ||
+                                               conf > HDP_STREAMING_DC) {
+                       g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                                       "Data channel aborted, "
+                                                       "configuration error");
+                       goto fail;
+               }
+       }
+
+       hdp_chan = create_channel(user_data->dev, conf, mdl,
+                                                       mcap_mdl_get_mdlid(mdl),
+                                                       user_data->app, &gerr);
+       if (!hdp_chan)
+               goto fail;
+
+       if (user_data->mdep != HDP_MDEP_ECHO)
+               g_dbus_emit_signal(user_data->conn,
+                                       device_get_path(hdp_chan->dev->dev),
+                                       HEALTH_DEVICE,
+                                       "ChannelConnected",
+                                       DBUS_TYPE_OBJECT_PATH, &hdp_chan->path,
+                                       DBUS_TYPE_INVALID);
+
+       hdp_conn = g_new0(struct hdp_tmp_dc_data, 1);
+       hdp_conn->msg = dbus_message_ref(user_data->msg);
+       hdp_conn->conn = dbus_connection_ref(user_data->conn);
+       hdp_conn->hdp_chann = hdp_chan;
+       hdp_conn->cb = user_data->cb;
+       hdp_chan->mdep = user_data->mdep;
+
+       if (hdp_get_dcpsm(hdp_chan->dev, hdp_get_dcpsm_cb,
+                                               hdp_tmp_dc_data_ref(hdp_conn),
+                                               hdp_tmp_dc_data_destroy, &gerr))
+               return;
+
+       error("%s", gerr->message);
+       g_error_free(gerr);
+       gerr = NULL;
+
+       reply = g_dbus_create_reply(hdp_conn->msg,
+                                       DBUS_TYPE_OBJECT_PATH, &hdp_chan->path,
+                                       DBUS_TYPE_INVALID);
+       g_dbus_send_message(hdp_conn->conn, reply);
+       hdp_tmp_dc_data_unref(hdp_conn);
+
+       /* Send abort request because remote side is now in PENDING state */
+       if (!mcap_mdl_abort(mdl, abort_mdl_cb, NULL, NULL, &gerr)) {
+               error("%s", gerr->message);
+               g_error_free(gerr);
+       }
+
+       return;
+
+fail:
+       reply = g_dbus_create_error(user_data->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "%s", gerr->message);
+       g_dbus_send_message(user_data->conn, reply);
+       g_error_free(gerr);
+       gerr = NULL;
+
+       /* Send abort request because remote side is now in PENDING */
+       /* state. Then we have to delete it because we couldn't */
+       /* register the HealthChannel interface */
+       if (!mcap_mdl_abort(mdl, abort_and_del_mdl_cb, mcap_mdl_ref(mdl), NULL,
+                                                               &gerr)) {
+               error("%s", gerr->message);
+               g_error_free(gerr);
+               mcap_mdl_unref(mdl);
+       }
+}
+
+static void device_create_dc_cb(gpointer user_data, GError *err)
+{
+       struct hdp_create_dc *data = user_data;
+       DBusMessage *reply;
+       GError *gerr = NULL;
+
+       if (err) {
+               reply = g_dbus_create_error(data->msg,
+                                       ERROR_INTERFACE ".HealthError",
+                                       "%s", err->message);
+               g_dbus_send_message(data->conn, reply);
+               return;
+       }
+
+       if (!data->dev->mcl) {
+               g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                               "Mcl was closed");
+               goto fail;
+       }
+
+       hdp_create_data_ref(data);
+
+       if (mcap_create_mdl(data->dev->mcl, data->mdep, data->config,
+                                               device_create_mdl_cb, data,
+                                               destroy_create_dc_data, &gerr))
+               return;
+       hdp_create_data_unref(data);
+
+fail:
+       reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError",
+                                                       "%s", gerr->message);
+       g_error_free(gerr);
+       g_dbus_send_message(data->conn, reply);
+}
+
+static DBusMessage *device_echo(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct hdp_device *device = user_data;
+       struct hdp_create_dc *data;
+       DBusMessage *reply;
+       GError *err = NULL;
+
+       data = g_new0(struct hdp_create_dc, 1);
+       data->dev = health_device_ref(device);
+       data->mdep = HDP_MDEP_ECHO;
+       data->config = HDP_RELIABLE_DC;
+       data->msg = dbus_message_ref(msg);
+       data->conn = dbus_connection_ref(conn);
+       data->cb = hdp_echo_connect_cb;
+       hdp_create_data_ref(data);
+
+       if (device->mcl_conn && device->mcl) {
+               if (mcap_create_mdl(device->mcl, data->mdep, data->config,
+                                               device_create_mdl_cb, data,
+                                               destroy_create_dc_data, &err))
+                       return NULL;
+               goto fail;
+       }
+
+       if (hdp_establish_mcl(data->dev, device_create_dc_cb,
+                                       data, destroy_create_dc_data, &err))
+               return NULL;
+
+fail:
+       reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
+                                                       "%s", err->message);
+       g_error_free(err);
+       hdp_create_data_unref(data);
+       return reply;
+}
+
+static void device_get_mdep_cb(uint8_t mdep, gpointer data, GError *err)
+{
+       struct hdp_create_dc *dc_data, *user_data = data;
+       DBusMessage *reply;
+       GError *gerr = NULL;
+
+       if (err) {
+               reply = g_dbus_create_error(user_data->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "%s", err->message);
+               g_dbus_send_message(user_data->conn, reply);
+               return;
+       }
+
+       dc_data = hdp_create_data_ref(user_data);
+       dc_data->mdep = mdep;
+
+       if (user_data->dev->mcl_conn) {
+               device_create_dc_cb(dc_data, NULL);
+               hdp_create_data_unref(dc_data);
+               return;
+       }
+
+       if (hdp_establish_mcl(dc_data->dev, device_create_dc_cb,
+                                       dc_data, destroy_create_dc_data, &gerr))
+               return;
+
+       reply = g_dbus_create_error(user_data->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "%s", gerr->message);
+       hdp_create_data_unref(dc_data);
+       g_error_free(gerr);
+       g_dbus_send_message(user_data->conn, reply);
+}
+
+static DBusMessage *device_create_channel(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct hdp_device *device = user_data;
+       struct hdp_application *app;
+       struct hdp_create_dc *data;
+       char *app_path, *conf;
+       DBusMessage *reply;
+       GError *err = NULL;
+       uint8_t config;
+       GSList *l;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &app_path,
+                                                       DBUS_TYPE_STRING, &conf,
+                                                       DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       l = g_slist_find_custom(applications, app_path, cmp_app);
+       if (!l)
+               return btd_error_invalid_args(msg);
+
+       app = l->data;
+
+       if (g_ascii_strcasecmp("Reliable", conf) == 0)
+               config = HDP_RELIABLE_DC;
+       else if (g_ascii_strcasecmp("Streaming", conf) == 0)
+               config = HDP_STREAMING_DC;
+       else if (g_ascii_strcasecmp("Any", conf) == 0)
+               config = HDP_NO_PREFERENCE_DC;
+       else
+               return btd_error_invalid_args(msg);
+
+       if (app->role == HDP_SINK && config != HDP_NO_PREFERENCE_DC)
+               return btd_error_invalid_args(msg);
+
+       if (app->role == HDP_SOURCE && config == HDP_NO_PREFERENCE_DC)
+               return btd_error_invalid_args(msg);
+
+       if (!device->fr && config == HDP_STREAMING_DC)
+               return btd_error_invalid_args(msg);
+
+       data = g_new0(struct hdp_create_dc, 1);
+       data->dev = health_device_ref(device);
+       data->config = config;
+       data->app = hdp_application_ref(app);
+       data->msg = dbus_message_ref(msg);
+       data->conn = dbus_connection_ref(conn);
+       data->cb = hdp_mdl_conn_cb;
+
+       if (hdp_get_mdep(device, l->data, device_get_mdep_cb,
+                                               hdp_create_data_ref(data),
+                                               destroy_create_dc_data, &err))
+               return NULL;
+
+       reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
+                                                       "%s", err->message);
+       g_error_free(err);
+       hdp_create_data_unref(data);
+       return reply;
+}
+
+static void hdp_mdl_delete_cb(GError *err, gpointer data)
+{
+       struct hdp_tmp_dc_data *del_data = data;
+       DBusMessage *reply;
+       char *path;
+
+       if (err && err->code != MCAP_INVALID_MDL) {
+               reply = g_dbus_create_error(del_data->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "%s", err->message);
+               g_dbus_send_message(del_data->conn, reply);
+               return;
+       }
+
+       path = g_strdup(del_data->hdp_chann->path);
+       g_dbus_unregister_interface(del_data->conn, path, HEALTH_CHANNEL);
+       g_free(path);
+
+       reply = g_dbus_create_reply(del_data->msg, DBUS_TYPE_INVALID);
+       g_dbus_send_message(del_data->conn, reply);
+}
+
+static void hdp_continue_del_cb(gpointer user_data, GError *err)
+{
+       struct hdp_tmp_dc_data *del_data = user_data;
+       GError *gerr = NULL;
+       DBusMessage *reply;
+
+       if (err) {
+               reply = g_dbus_create_error(del_data->msg,
+                                       ERROR_INTERFACE ".HealthError",
+                                       "%s", err->message);
+               g_dbus_send_message(del_data->conn, reply);
+               return;
+       }
+
+       if (mcap_delete_mdl(del_data->hdp_chann->mdl, hdp_mdl_delete_cb,
+                                               hdp_tmp_dc_data_ref(del_data),
+                                               hdp_tmp_dc_data_destroy, &gerr))
+                       return;
+
+       reply = g_dbus_create_error(del_data->msg,
+                                               ERROR_INTERFACE ".HealthError",
+                                               "%s", gerr->message);
+       hdp_tmp_dc_data_unref(del_data);
+       g_error_free(gerr);
+       g_dbus_send_message(del_data->conn, reply);
+}
+
+static DBusMessage *device_destroy_channel(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct hdp_device *device = user_data;
+       struct hdp_tmp_dc_data *del_data;
+       struct hdp_channel *hdp_chan;
+       DBusMessage *reply;
+       GError *err = NULL;
+       char *path;
+       GSList *l;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                                       DBUS_TYPE_INVALID)){
+               return btd_error_invalid_args(msg);
+       }
+
+       l = g_slist_find_custom(device->channels, path, cmp_chan_path);
+       if (!l)
+               return btd_error_invalid_args(msg);
+
+       hdp_chan = l->data;
+       del_data = g_new0(struct hdp_tmp_dc_data, 1);
+       del_data->msg = dbus_message_ref(msg);
+       del_data->conn = dbus_connection_ref(conn);
+       del_data->hdp_chann = hdp_channel_ref(hdp_chan);
+
+       if (device->mcl_conn) {
+               if (mcap_delete_mdl(hdp_chan->mdl, hdp_mdl_delete_cb,
+                                               hdp_tmp_dc_data_ref(del_data),
+                                               hdp_tmp_dc_data_destroy, &err))
+                       return NULL;
+               goto fail;
+       }
+
+       if (hdp_establish_mcl(device, hdp_continue_del_cb,
+                                               hdp_tmp_dc_data_ref(del_data),
+                                               hdp_tmp_dc_data_destroy, &err))
+               return NULL;
+
+fail:
+       reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
+                                                       "%s", err->message);
+       hdp_tmp_dc_data_unref(del_data);
+       g_error_free(err);
+       return reply;
+}
+
+static DBusMessage *device_get_properties(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct hdp_device *device = user_data;
+       DBusMessageIter iter, dict;
+       DBusMessage *reply;
+       char *path;
+
+       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);
+
+       if (device->fr)
+               path = g_strdup(device->fr->path);
+       else
+               path = g_strdup("");
+       dict_append_entry(&dict, "MainChannel", DBUS_TYPE_STRING, &path);
+       g_free(path);
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static void health_device_destroy(void *data)
+{
+       struct hdp_device *device = data;
+
+       DBG("Unregistered interface %s on path %s", HEALTH_DEVICE,
+                                               device_get_path(device->dev));
+
+       remove_channels(device);
+       if (device->ndc) {
+               hdp_channel_unref(device->ndc);
+               device->ndc = NULL;
+       }
+
+       devices = g_slist_remove(devices, device);
+       health_device_unref(device);
+}
+
+static GDBusMethodTable health_device_methods[] = {
+       {"Echo",                "",     "b",    device_echo,
+                                               G_DBUS_METHOD_FLAG_ASYNC },
+       {"CreateChannel",       "os",   "o",    device_create_channel,
+                                               G_DBUS_METHOD_FLAG_ASYNC },
+       {"DestroyChannel",      "o",    "",     device_destroy_channel,
+                                               G_DBUS_METHOD_FLAG_ASYNC },
+       {"GetProperties",       "",     "a{sv}", device_get_properties},
+       { NULL }
+};
+
+static GDBusSignalTable health_device_signals[] = {
+       {"ChannelConnected",            "o"             },
+       {"ChannelDeleted",              "o"             },
+       {"PropertyChanged",             "sv"            },
+       { NULL }
+};
+
+static struct hdp_device *create_health_device(DBusConnection *conn,
+                                               struct btd_device *device)
+{
+       struct btd_adapter *adapter = device_get_adapter(device);
+       const gchar *path = device_get_path(device);
+       struct hdp_device *dev;
+       GSList *l;
+
+       if (!device)
+               return NULL;
+
+       dev = g_new0(struct hdp_device, 1);
+       dev->conn = dbus_connection_ref(conn);
+       dev->dev = btd_device_ref(device);
+       health_device_ref(dev);
+
+       l = g_slist_find_custom(adapters, adapter, cmp_adapter);
+       if (!l)
+               goto fail;
+
+       dev->hdp_adapter = l->data;
+
+       if (!g_dbus_register_interface(conn, path,
+                                       HEALTH_DEVICE,
+                                       health_device_methods,
+                                       health_device_signals, NULL,
+                                       dev, health_device_destroy)) {
+               error("D-Bus failed to register %s interface", HEALTH_DEVICE);
+               goto fail;
+       }
+
+       DBG("Registered interface %s on path %s", HEALTH_DEVICE, path);
+       return dev;
+
+fail:
+       health_device_unref(dev);
+       return NULL;
+}
+
+int hdp_device_register(DBusConnection *conn, struct btd_device *device)
+{
+       struct hdp_device *hdev;
+       GSList *l;
+
+       l = g_slist_find_custom(devices, device, cmp_device);
+       if (l) {
+               hdev = l->data;
+               hdev->sdp_present = TRUE;
+               return 0;
+       }
+
+       hdev = create_health_device(conn, device);
+       if (!hdev)
+               return -1;
+
+       hdev->sdp_present = TRUE;
+
+       devices = g_slist_prepend(devices, hdev);
+       return 0;
+}
+
+void hdp_device_unregister(struct btd_device *device)
+{
+       struct hdp_device *hdp_dev;
+       const char *path;
+       GSList *l;
+
+       l = g_slist_find_custom(devices, device, cmp_device);
+       if (!l)
+               return;
+
+       hdp_dev = l->data;
+       path = device_get_path(hdp_dev->dev);
+       g_dbus_unregister_interface(hdp_dev->conn, path, HEALTH_DEVICE);
+}
+
+int hdp_manager_start(DBusConnection *conn)
+{
+       DBG("Starting Health manager");
+
+       if (!g_dbus_register_interface(conn, MANAGER_PATH,
+                                       HEALTH_MANAGER,
+                                       health_manager_methods, NULL, NULL,
+                                       NULL, manager_path_unregister)) {
+               error("D-Bus failed to register %s interface", HEALTH_MANAGER);
+               return -1;
+       }
+
+       connection = dbus_connection_ref(conn);
+
+       return 0;
+}
+
+void hdp_manager_stop()
+{
+       g_dbus_unregister_interface(connection, MANAGER_PATH, HEALTH_MANAGER);
+
+       dbus_connection_unref(connection);
+       DBG("Stopped Health manager");
+}
+
+struct hdp_device *health_device_ref(struct hdp_device *hdp_dev)
+{
+       hdp_dev->ref++;
+
+       DBG("health_device_ref(%p): ref=%d", hdp_dev, hdp_dev->ref);
+
+       return hdp_dev;
+}
+
+void health_device_unref(struct hdp_device *hdp_dev)
+{
+       hdp_dev->ref--;
+
+       DBG("health_device_unref(%p): ref=%d", hdp_dev, hdp_dev->ref);
+
+       if (hdp_dev->ref > 0)
+               return;
+
+       free_health_device(hdp_dev);
+}
diff --git a/health/hdp.h b/health/hdp.h
new file mode 100644 (file)
index 0000000..3cf87ca
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Authors:
+ *  Santiago Carot Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 hdp_adapter_register(DBusConnection *conn, struct btd_adapter *btd_adapter);
+void hdp_adapter_unregister(struct btd_adapter *btd_adapter);
+
+int hdp_device_register(DBusConnection *conn, struct btd_device *device);
+void hdp_device_unregister(struct btd_device *device);
+
+int hdp_manager_start(DBusConnection *conn);
+void hdp_manager_stop();
+
+gboolean hdp_set_mcl_cb(struct hdp_device *device, GError **err);
diff --git a/health/hdp_main.c b/health/hdp_main.c
new file mode 100644 (file)
index 0000000..b40a37c
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Authors:
+ *  Santiago Carot Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "hdp_manager.h"
+
+static DBusConnection *connection = NULL;
+
+static int hdp_init(void)
+{
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (connection == NULL)
+               return -EIO;
+
+       if (hdp_manager_init(connection) < 0) {
+               dbus_connection_unref(connection);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void hdp_exit(void)
+{
+       hdp_manager_exit();
+
+       dbus_connection_unref(connection);
+       connection = NULL;
+}
+
+BLUETOOTH_PLUGIN_DEFINE(health, VERSION,
+                       BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, hdp_init, hdp_exit)
diff --git a/health/hdp_manager.c b/health/hdp_manager.c
new file mode 100644 (file)
index 0000000..88b49fc
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Authors:
+ *  Santiago Carot Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 <config.h>
+#endif
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <btio.h>
+#include <adapter.h>
+#include <device.h>
+
+#include "hdp_types.h"
+
+#include "log.h"
+#include "hdp_manager.h"
+#include "hdp.h"
+
+#include "glib-helper.h"
+
+static DBusConnection *connection = NULL;
+
+static int hdp_adapter_probe(struct btd_adapter *adapter)
+{
+       return hdp_adapter_register(connection, adapter);
+}
+
+static void hdp_adapter_remove(struct btd_adapter *adapter)
+{
+       hdp_adapter_unregister(adapter);
+}
+
+static struct btd_adapter_driver hdp_adapter_driver = {
+       .name   = "hdp-adapter-driver",
+       .probe  = hdp_adapter_probe,
+       .remove = hdp_adapter_remove,
+};
+
+static int hdp_driver_probe(struct btd_device *device, GSList *uuids)
+{
+       return hdp_device_register(connection, device);
+}
+
+static void hdp_driver_remove(struct btd_device *device)
+{
+       hdp_device_unregister(device);
+}
+
+static struct btd_device_driver hdp_device_driver = {
+       .name   = "hdp-device-driver",
+       .uuids  = BTD_UUIDS(HDP_UUID, HDP_SOURCE_UUID, HDP_SINK_UUID),
+       .probe  = hdp_driver_probe,
+       .remove = hdp_driver_remove
+};
+
+int hdp_manager_init(DBusConnection *conn)
+{
+       if (hdp_manager_start(conn))
+               return -1;
+
+       connection = dbus_connection_ref(conn);
+       btd_register_adapter_driver(&hdp_adapter_driver);
+       btd_register_device_driver(&hdp_device_driver);
+
+       return 0;
+}
+
+void hdp_manager_exit(void)
+{
+       btd_unregister_device_driver(&hdp_device_driver);
+       btd_unregister_adapter_driver(&hdp_adapter_driver);
+       hdp_manager_stop();
+
+       dbus_connection_unref(connection);
+       connection = NULL;
+}
diff --git a/health/hdp_manager.h b/health/hdp_manager.h
new file mode 100644 (file)
index 0000000..b91ef75
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Authors:
+ *  Santiago Carot Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 hdp_manager_init(DBusConnection *conn);
+void hdp_manager_exit(void);
diff --git a/health/hdp_types.h b/health/hdp_types.h
new file mode 100644 (file)
index 0000000..7d23293
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Authors:
+ *  Santiago Carot Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 __HDP_TYPES_H__
+#define __HDP_TYPES_H__
+
+#define HDP_UUID               "00001400-0000-1000-8000-00805F9B34FB"
+#define HDP_SOURCE_UUID                "00001401-0000-1000-8000-00805F9B34FB"
+#define HDP_SINK_UUID          "00001402-0000-1000-8000-00805F9B34FB"
+
+#define MANAGER_PATH           "/org/bluez"
+
+#define HEALTH_MANAGER         "org.bluez.HealthManager"
+#define HEALTH_DEVICE          "org.bluez.HealthDevice"
+#define HEALTH_CHANNEL         "org.bluez.HealthChannel"
+
+#define HDP_VERSION            0x0100
+
+#define HDP_SERVICE_NAME       "Bluez HDP"
+#define HDP_SERVICE_DSC                "A Bluez health device profile implementation"
+#define HDP_SERVICE_PROVIDER   "Bluez"
+
+#define HDP_MDEP_ECHO          0x00
+#define HDP_MDEP_INITIAL       0x01
+#define HDP_MDEP_FINAL         0x7F
+
+#define HDP_ERROR              g_quark_from_static_string("hdp-error-quark")
+
+#define HDP_NO_PREFERENCE_DC   0x00
+#define HDP_RELIABLE_DC                0x01
+#define HDP_STREAMING_DC       0x02
+
+#define HDP_SINK_ROLE_AS_STRING                "sink"
+#define HDP_SOURCE_ROLE_AS_STRING      "source"
+
+typedef enum {
+       HDP_SOURCE = 0x00,
+       HDP_SINK = 0x01
+} HdpRole;
+
+typedef enum {
+       HDP_DIC_PARSE_ERROR,
+       HDP_DIC_ENTRY_PARSE_ERROR,
+       HDP_CONNECTION_ERROR,
+       HDP_UNSPECIFIED_ERROR,
+       HDP_UNKNOWN_ERROR
+} HdpError;
+
+enum data_specs {
+       DATA_EXCHANGE_SPEC_11073 = 0x01
+};
+
+struct hdp_application {
+       DBusConnection          *conn;          /* For dbus watcher */
+       char                    *path;          /* The path of the application */
+       uint16_t                data_type;      /* Data type handled for this application */
+       gboolean                data_type_set;  /* Flag for dictionary parsing */
+       uint8_t                 role;           /* Role of this application */
+       gboolean                role_set;       /* Flag for dictionary parsing */
+       uint8_t                 chan_type;      /* QoS preferred by source applications */
+       gboolean                chan_type_set;  /* Flag for dictionary parsing */
+       char                    *description;   /* Options description for SDP record */
+       uint8_t                 id;             /* The identification is also the mdepid */
+       char                    *oname;         /* Name of the owner application */
+       int                     dbus_watcher;   /* Watch for clients disconnection */
+       gint                    ref;            /* Reference counter */
+};
+
+struct hdp_adapter {
+       struct btd_adapter      *btd_adapter;   /* Bluetooth adapter */
+       struct mcap_instance    *mi;            /* Mcap instance in */
+       uint16_t                ccpsm;          /* Control channel psm */
+       uint16_t                dcpsm;          /* Data channel psm */
+       uint32_t                sdp_handler;    /* SDP record handler */
+       uint32_t                record_state;   /* Service record state */
+};
+
+struct hdp_device {
+       DBusConnection          *conn;          /* For name listener handling */
+       struct btd_device       *dev;           /* Device reference */
+       struct hdp_adapter      *hdp_adapter;   /* hdp_adapater */
+       struct mcap_mcl         *mcl;           /* The mcap control channel */
+       gboolean                mcl_conn;       /* Mcl status */
+       gboolean                sdp_present;    /* Has an sdp record */
+       GSList                  *channels;      /* Data Channel list */
+       struct hdp_channel      *ndc;           /* Data channel being negotiated */
+       struct hdp_channel      *fr;            /* First reliable data channel */
+       gint                    ref;            /* Reference counting */
+};
+
+struct hdp_echo_data;
+
+struct hdp_channel {
+       struct hdp_device       *dev;           /* Device where this channel belongs */
+       struct hdp_application  *app;           /* Application */
+       struct mcap_mdl         *mdl;           /* The data channel reference */
+       char                    *path;          /* The path of the channel */
+       uint8_t                 config;         /* Channel configuration */
+       uint8_t                 mdep;           /* Remote MDEP */
+       uint16_t                mdlid;          /* Data channel Id */
+       uint16_t                imtu;           /* Channel incoming MTU */
+       uint16_t                omtu;           /* Channel outgoing MTU */
+       struct hdp_echo_data    *edata;         /* private data used by echo channels */
+       gint                    ref;            /* Reference counter */
+};
+
+#endif /* __HDP_TYPES_H__ */
diff --git a/health/hdp_util.c b/health/hdp_util.c
new file mode 100644 (file)
index 0000000..aefe5f9
--- /dev/null
@@ -0,0 +1,1211 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Authors:
+ *  Santiago Carot Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 <gdbus.h>
+
+#include <adapter.h>
+#include <device.h>
+#include <stdint.h>
+#include <hdp_types.h>
+#include <hdp_util.h>
+#include <mcap.h>
+#include <hdp.h>
+
+#include <sdpd.h>
+#include <sdp_lib.h>
+#include <glib-helper.h>
+
+#include <btio.h>
+#include <mcap_lib.h>
+
+#include <log.h>
+
+typedef gboolean (*parse_item_f)(DBusMessageIter *iter, gpointer user_data,
+                                                               GError **err);
+
+struct dict_entry_func {
+       char            *key;
+       parse_item_f    func;
+};
+
+struct get_mdep_data {
+       struct hdp_application  *app;
+       gpointer                data;
+       hdp_continue_mdep_f     func;
+       GDestroyNotify          destroy;
+};
+
+struct conn_mcl_data {
+       int                     refs;
+       gpointer                data;
+       hdp_continue_proc_f     func;
+       GDestroyNotify          destroy;
+       struct hdp_device       *dev;
+};
+
+struct get_dcpsm_data {
+       gpointer                data;
+       hdp_continue_dcpsm_f    func;
+       GDestroyNotify          destroy;
+};
+
+static gboolean parse_dict_entry(struct dict_entry_func dict_context[],
+                                                       DBusMessageIter *iter,
+                                                       GError **err,
+                                                       gpointer user_data)
+{
+       DBusMessageIter entry;
+       char *key;
+       int ctype, i;
+       struct dict_entry_func df;
+
+       dbus_message_iter_recurse(iter, &entry);
+       ctype = dbus_message_iter_get_arg_type(&entry);
+       if (ctype != DBUS_TYPE_STRING) {
+               g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+                       "Dictionary entries should have a string as key");
+               return FALSE;
+       }
+
+       dbus_message_iter_get_basic(&entry, &key);
+       dbus_message_iter_next(&entry);
+       /* Find function and call it */
+       for (i = 0, df = dict_context[0]; df.key; i++, df = dict_context[i]) {
+               if (g_ascii_strcasecmp(df.key, key) == 0)
+                       return df.func(&entry, user_data, err);
+       }
+
+       g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+                       "No function found for parsing value for key %s", key);
+       return FALSE;
+}
+
+static gboolean parse_dict(struct dict_entry_func dict_context[],
+                                                       DBusMessageIter *iter,
+                                                       GError **err,
+                                                       gpointer user_data)
+{
+       int ctype;
+       DBusMessageIter dict;
+
+       ctype = dbus_message_iter_get_arg_type(iter);
+       if (ctype != DBUS_TYPE_ARRAY) {
+               g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR,
+                                       "Dictionary should be an array");
+               return FALSE;
+       }
+
+       dbus_message_iter_recurse(iter, &dict);
+       while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
+                                                       DBUS_TYPE_INVALID) {
+               if (ctype != DBUS_TYPE_DICT_ENTRY) {
+                       g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR,
+                                               "Dictionary array should "
+                                               "contain dict entries");
+                       return FALSE;
+               }
+
+               /* Start parsing entry */
+               if (!parse_dict_entry(dict_context, &dict, err,
+                                                       user_data))
+                       return FALSE;
+               /* Finish entry parsing */
+
+               dbus_message_iter_next(&dict);
+       }
+
+       return TRUE;
+}
+
+static gboolean parse_data_type(DBusMessageIter *iter, gpointer data,
+                                                               GError **err)
+{
+       struct hdp_application *app = data;
+       DBusMessageIter *value;
+       int ctype;
+
+       ctype = dbus_message_iter_get_arg_type(iter);
+       value = iter;
+       if (ctype == DBUS_TYPE_VARIANT) {
+               DBusMessageIter variant;
+
+               /* Get value inside the variable */
+               dbus_message_iter_recurse(iter, &variant);
+               ctype = dbus_message_iter_get_arg_type(&variant);
+               value = &variant;
+       }
+
+       if (ctype != DBUS_TYPE_UINT16) {
+               g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+                       "Final value for data type should be uint16");
+               return FALSE;
+       }
+
+       dbus_message_iter_get_basic(value, &app->data_type);
+       app->data_type_set = TRUE;
+       return TRUE;
+}
+
+static gboolean parse_role(DBusMessageIter *iter, gpointer data, GError **err)
+{
+       struct hdp_application *app = data;
+       DBusMessageIter *string;
+       int ctype;
+       const char *role;
+
+       ctype = dbus_message_iter_get_arg_type(iter);
+       if (ctype == DBUS_TYPE_VARIANT) {
+               DBusMessageIter value;
+
+               /* Get value inside the variable */
+               dbus_message_iter_recurse(iter, &value);
+               ctype = dbus_message_iter_get_arg_type(&value);
+               string = &value;
+       } else {
+               string = iter;
+       }
+
+       if (ctype != DBUS_TYPE_STRING) {
+               g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR,
+                               "Value data spec should be variable or string");
+               return FALSE;
+       }
+
+       dbus_message_iter_get_basic(string, &role);
+       if (g_ascii_strcasecmp(role, HDP_SINK_ROLE_AS_STRING) == 0) {
+               app->role = HDP_SINK;
+       } else if (g_ascii_strcasecmp(role, HDP_SOURCE_ROLE_AS_STRING) == 0) {
+               app->role = HDP_SOURCE;
+       } else {
+               g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR,
+                       "Role value should be \"source\" or \"sink\"");
+               return FALSE;
+       }
+
+       app->role_set = TRUE;
+
+       return TRUE;
+}
+
+static gboolean parse_desc(DBusMessageIter *iter, gpointer data, GError **err)
+{
+       struct hdp_application *app = data;
+       DBusMessageIter *string;
+       int ctype;
+       const char *desc;
+
+       ctype = dbus_message_iter_get_arg_type(iter);
+       if (ctype == DBUS_TYPE_VARIANT) {
+               DBusMessageIter variant;
+
+               /* Get value inside the variable */
+               dbus_message_iter_recurse(iter, &variant);
+               ctype = dbus_message_iter_get_arg_type(&variant);
+               string = &variant;
+       } else {
+               string = iter;
+       }
+
+       if (ctype != DBUS_TYPE_STRING) {
+               g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+                               "Value data spec should be variable or string");
+               return FALSE;
+       }
+
+       dbus_message_iter_get_basic(string, &desc);
+       app->description = g_strdup(desc);
+       return TRUE;
+}
+
+static gboolean parse_chan_type(DBusMessageIter *iter, gpointer data,
+                                                               GError **err)
+{
+       struct hdp_application *app = data;
+       DBusMessageIter *value;
+       char *chan_type;
+       int ctype;
+
+       ctype = dbus_message_iter_get_arg_type(iter);
+       value = iter;
+       if (ctype == DBUS_TYPE_VARIANT) {
+               DBusMessageIter variant;
+
+               /* Get value inside the variable */
+               dbus_message_iter_recurse(iter, &variant);
+               ctype = dbus_message_iter_get_arg_type(&variant);
+               value = &variant;
+       }
+
+       if (ctype != DBUS_TYPE_STRING) {
+               g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+                       "Final value for channel type should be an string");
+               return FALSE;
+       }
+
+       dbus_message_iter_get_basic(value, &chan_type);
+
+       if (g_ascii_strcasecmp("Reliable", chan_type) == 0)
+               app->chan_type = HDP_RELIABLE_DC;
+       else if (g_ascii_strcasecmp("Streaming", chan_type) == 0)
+               app->chan_type = HDP_STREAMING_DC;
+       else {
+               g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+                                               "Invalid value for data type");
+               return FALSE;
+       }
+
+       app->chan_type_set = TRUE;
+
+       return TRUE;
+}
+
+static struct dict_entry_func dict_parser[] = {
+       {"DataType",            parse_data_type},
+       {"Role",                parse_role},
+       {"Description",         parse_desc},
+       {"ChannelType",         parse_chan_type},
+       {NULL, NULL}
+};
+
+struct hdp_application *hdp_get_app_config(DBusMessageIter *iter, GError **err)
+{
+       struct hdp_application *app;
+
+       app = g_new0(struct hdp_application, 1);
+       app->ref = 1;
+       if (!parse_dict(dict_parser, iter, err, app))
+               goto fail;
+       if (!app->data_type_set || !app->role_set) {
+               g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR,
+                                               "Mandatory fields aren't set");
+               goto fail;
+       }
+       return app;
+
+fail:
+       hdp_application_unref(app);
+       return NULL;
+}
+
+static gboolean is_app_role(GSList *app_list, HdpRole role)
+{
+       GSList *l;
+
+       for (l = app_list; l; l = l->next) {
+               struct hdp_application *app = l->data;
+
+               if (app->role == role)
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static gboolean set_sdp_services_uuid(sdp_record_t *record, HdpRole role)
+{
+       uuid_t svc_uuid_source, svc_uuid_sink;
+       sdp_list_t *svc_list = NULL;
+
+       sdp_uuid16_create(&svc_uuid_sink, HDP_SINK_SVCLASS_ID);
+       sdp_uuid16_create(&svc_uuid_source, HDP_SOURCE_SVCLASS_ID);
+
+       sdp_get_service_classes(record, &svc_list);
+
+       if (role == HDP_SOURCE) {
+               if (!sdp_list_find(svc_list, &svc_uuid_source, sdp_uuid_cmp))
+                       svc_list = sdp_list_append(svc_list, &svc_uuid_source);
+       } else if (role == HDP_SINK) {
+               if (!sdp_list_find(svc_list, &svc_uuid_sink, sdp_uuid_cmp))
+                       svc_list = sdp_list_append(svc_list, &svc_uuid_sink);
+       }
+
+       if (sdp_set_service_classes(record, svc_list) < 0) {
+               sdp_list_free(svc_list, NULL);
+               return FALSE;
+       }
+
+       sdp_list_free(svc_list, NULL);
+
+       return TRUE;
+}
+
+static gboolean register_service_protocols(struct hdp_adapter *adapter,
+                                               sdp_record_t *sdp_record)
+{
+       gboolean ret;
+       uuid_t l2cap_uuid, mcap_c_uuid;
+       sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL;
+       sdp_list_t *access_proto_list = NULL;
+       sdp_data_t *psm = NULL, *mcap_ver = NULL;
+       uint16_t version = MCAP_VERSION;
+
+       /* set l2cap information */
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       l2cap_list = sdp_list_append(NULL, &l2cap_uuid);
+       if (!l2cap_list) {
+               ret = FALSE;
+               goto end;
+       }
+
+       psm = sdp_data_alloc(SDP_UINT16, &adapter->ccpsm);
+       if (!psm) {
+               ret = FALSE;
+               goto end;
+       }
+
+       if (!sdp_list_append(l2cap_list, psm)) {
+               ret = FALSE;
+               goto end;
+       }
+
+       proto_list = sdp_list_append(NULL, l2cap_list);
+       if (!proto_list) {
+               ret = FALSE;
+               goto end;
+       }
+
+       /* set mcap information */
+       sdp_uuid16_create(&mcap_c_uuid, MCAP_CTRL_UUID);
+       mcap_list = sdp_list_append(NULL, &mcap_c_uuid);
+       if (!mcap_list) {
+               ret = FALSE;
+               goto end;
+       }
+
+       mcap_ver = sdp_data_alloc(SDP_UINT16, &version);
+       if (!mcap_ver) {
+               ret = FALSE;
+               goto end;
+       }
+
+       if (!sdp_list_append(mcap_list, mcap_ver)) {
+               ret = FALSE;
+               goto end;
+       }
+
+       if (!sdp_list_append(proto_list, mcap_list)) {
+               ret = FALSE;
+               goto end;
+       }
+
+       /* attach protocol information to service record */
+       access_proto_list = sdp_list_append(NULL, proto_list);
+       if (!access_proto_list) {
+               ret = FALSE;
+               goto end;
+       }
+
+       if (sdp_set_access_protos(sdp_record, access_proto_list) < 0) {
+               ret = FALSE;
+               goto end;
+       }
+       ret = TRUE;
+
+end:
+       if (l2cap_list)
+               sdp_list_free(l2cap_list, NULL);
+       if (mcap_list)
+               sdp_list_free(mcap_list, NULL);
+       if (proto_list)
+               sdp_list_free(proto_list, NULL);
+       if (access_proto_list)
+               sdp_list_free(access_proto_list, NULL);
+       if (psm)
+               sdp_data_free(psm);
+       if (mcap_ver)
+               sdp_data_free(mcap_ver);
+
+       return ret;
+}
+
+static gboolean register_service_profiles(sdp_record_t *sdp_record)
+{
+       gboolean ret;
+       sdp_list_t *profile_list;
+       sdp_profile_desc_t hdp_profile;
+
+       /* set hdp information */
+       sdp_uuid16_create(&hdp_profile.uuid, HDP_SVCLASS_ID);
+       hdp_profile.version = HDP_VERSION;
+       profile_list = sdp_list_append(NULL, &hdp_profile);
+       if (!profile_list)
+               return FALSE;
+
+       /* set profile descriptor list */
+       if (sdp_set_profile_descs(sdp_record, profile_list) < 0)
+               ret = FALSE;
+       else
+               ret = TRUE;
+
+       sdp_list_free(profile_list, NULL);
+
+       return ret;
+}
+
+static gboolean register_service_additional_protocols(
+                                               struct hdp_adapter *adapter,
+                                               sdp_record_t *sdp_record)
+{
+       gboolean ret;
+       uuid_t l2cap_uuid, mcap_d_uuid;
+       sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL;
+       sdp_list_t *access_proto_list = NULL;
+       sdp_data_t *psm = NULL;
+
+       /* set l2cap information */
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       l2cap_list = sdp_list_append(NULL, &l2cap_uuid);
+       if (!l2cap_list) {
+               ret = FALSE;
+               goto end;
+       }
+
+       psm = sdp_data_alloc(SDP_UINT16, &adapter->dcpsm);
+       if (!psm) {
+               ret = FALSE;
+               goto end;
+       }
+
+       if (!sdp_list_append(l2cap_list, psm)) {
+               ret = FALSE;
+               goto end;
+       }
+
+       proto_list = sdp_list_append(NULL, l2cap_list);
+       if (!proto_list) {
+               ret = FALSE;
+               goto end;
+       }
+
+       /* set mcap information */
+       sdp_uuid16_create(&mcap_d_uuid, MCAP_DATA_UUID);
+       mcap_list = sdp_list_append(NULL, &mcap_d_uuid);
+       if (!mcap_list) {
+               ret = FALSE;
+               goto end;
+       }
+
+       if (!sdp_list_append(proto_list, mcap_list)) {
+               ret = FALSE;
+               goto end;
+       }
+
+       /* attach protocol information to service record */
+       access_proto_list = sdp_list_append(NULL, proto_list);
+       if (!access_proto_list) {
+               ret = FALSE;
+               goto end;
+       }
+
+       if (sdp_set_add_access_protos(sdp_record, access_proto_list) < 0)
+               ret = FALSE;
+       else
+               ret = TRUE;
+
+end:
+       if (l2cap_list)
+               sdp_list_free(l2cap_list, NULL);
+       if (mcap_list)
+               sdp_list_free(mcap_list, NULL);
+       if (proto_list)
+               sdp_list_free(proto_list, NULL);
+       if (access_proto_list)
+               sdp_list_free(access_proto_list, NULL);
+       if (psm)
+               sdp_data_free(psm);
+
+       return ret;
+}
+
+static sdp_list_t *app_to_sdplist(struct hdp_application *app)
+{
+       sdp_data_t *mdepid,
+               *dtype = NULL,
+               *role = NULL,
+               *desc = NULL;
+       sdp_list_t *f_list = NULL;
+
+       mdepid = sdp_data_alloc(SDP_UINT8, &app->id);
+       if (!mdepid)
+               return NULL;
+
+       dtype = sdp_data_alloc(SDP_UINT16, &app->data_type);
+       if (!dtype)
+               goto fail;
+
+       role = sdp_data_alloc(SDP_UINT8, &app->role);
+       if (!role)
+               goto fail;
+
+       if (app->description) {
+               desc = sdp_data_alloc(SDP_TEXT_STR8, app->description);
+               if (!desc)
+                       goto fail;
+       }
+
+       f_list = sdp_list_append(NULL, mdepid);
+       if (!f_list)
+               goto fail;
+
+       if (!sdp_list_append(f_list, dtype))
+               goto fail;
+
+       if (!sdp_list_append(f_list, role))
+               goto fail;
+
+       if (desc)
+               if (!sdp_list_append(f_list, desc))
+                       goto fail;
+
+       return f_list;
+
+fail:
+       if (f_list)
+               sdp_list_free(f_list, NULL);
+       if (mdepid)
+               sdp_data_free(mdepid);
+       if (dtype)
+               sdp_data_free(dtype);
+       if (role)
+               sdp_data_free(role);
+       if (desc)
+               sdp_data_free(desc);
+
+       return NULL;
+}
+
+static gboolean register_features(struct hdp_application *app,
+                                               sdp_list_t **sup_features)
+{
+       sdp_list_t *hdp_feature;
+
+       hdp_feature = app_to_sdplist(app);
+       if (!hdp_feature)
+               goto fail;
+
+       if (!*sup_features) {
+               *sup_features = sdp_list_append(NULL, hdp_feature);
+               if (!*sup_features)
+                       goto fail;
+       } else if (!sdp_list_append(*sup_features, hdp_feature)) {
+               goto fail;
+       }
+
+       return TRUE;
+
+fail:
+       if (hdp_feature)
+               sdp_list_free(hdp_feature, (sdp_free_func_t)sdp_data_free);
+       return FALSE;
+}
+
+static void free_hdp_list(void *list)
+{
+       sdp_list_t *hdp_list = list;
+
+       sdp_list_free(hdp_list, (sdp_free_func_t)sdp_data_free);
+}
+
+static gboolean register_service_sup_features(GSList *app_list,
+                                               sdp_record_t *sdp_record)
+{
+       GSList *l;
+       sdp_list_t *sup_features = NULL;
+
+       for (l = app_list; l; l = l->next) {
+               if (!register_features(l->data, &sup_features))
+                       return FALSE;
+       }
+
+       if (sdp_set_supp_feat(sdp_record, sup_features) < 0) {
+               sdp_list_free(sup_features, free_hdp_list);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean register_data_exchange_spec(sdp_record_t *record)
+{
+       sdp_data_t *spec;
+       uint8_t data_spec = DATA_EXCHANGE_SPEC_11073;
+       /* As by now 11073 is the only supported we set it by default */
+
+       spec = sdp_data_alloc(SDP_UINT8, &data_spec);
+       if (!spec)
+               return FALSE;
+
+       if (sdp_attr_add(record, SDP_ATTR_DATA_EXCHANGE_SPEC, spec) < 0) {
+               sdp_data_free(spec);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean register_mcap_features(sdp_record_t *sdp_record)
+{
+       sdp_data_t *mcap_proc;
+       uint8_t mcap_sup_proc = MCAP_SUP_PROC;
+
+       mcap_proc = sdp_data_alloc(SDP_UINT8, &mcap_sup_proc);
+       if (!mcap_proc)
+               return FALSE;
+
+       if (sdp_attr_add(sdp_record, SDP_ATTR_MCAP_SUPPORTED_PROCEDURES,
+                                                       mcap_proc) < 0) {
+               sdp_data_free(mcap_proc);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+gboolean hdp_update_sdp_record(struct hdp_adapter *adapter, GSList *app_list)
+{
+       sdp_record_t *sdp_record;
+       bdaddr_t addr;
+
+       if (adapter->sdp_handler)
+               remove_record_from_server(adapter->sdp_handler);
+
+       if (!app_list) {
+               adapter->sdp_handler = 0;
+               return TRUE;
+       }
+
+       sdp_record = sdp_record_alloc();
+       if (!sdp_record)
+               return FALSE;
+
+       if (adapter->sdp_handler)
+               sdp_record->handle = adapter->sdp_handler;
+       else
+               sdp_record->handle = 0xffffffff; /* Set automatically */
+
+       if (is_app_role(app_list, HDP_SINK))
+               set_sdp_services_uuid(sdp_record, HDP_SINK);
+       if (is_app_role(app_list, HDP_SOURCE))
+               set_sdp_services_uuid(sdp_record, HDP_SOURCE);
+
+       if (!register_service_protocols(adapter, sdp_record))
+               goto fail;
+       if (!register_service_profiles(sdp_record))
+               goto fail;
+       if (!register_service_additional_protocols(adapter, sdp_record))
+               goto fail;
+
+       sdp_set_info_attr(sdp_record, HDP_SERVICE_NAME, HDP_SERVICE_PROVIDER,
+                                                       HDP_SERVICE_DSC);
+       if (!register_service_sup_features(app_list, sdp_record))
+               goto fail;
+       if (!register_data_exchange_spec(sdp_record))
+               goto fail;
+
+       register_mcap_features(sdp_record);
+
+       if (sdp_set_record_state(sdp_record, adapter->record_state++))
+               goto fail;
+
+       adapter_get_address(adapter->btd_adapter, &addr);
+
+       if (add_record_to_server(&addr, sdp_record) < 0)
+               goto fail;
+       adapter->sdp_handler = sdp_record->handle;
+       return TRUE;
+
+fail:
+       if (sdp_record)
+               sdp_record_free(sdp_record);
+       return FALSE;
+}
+
+static gboolean check_role(uint8_t rec_role, uint8_t app_role)
+{
+       if ((rec_role == HDP_SINK && app_role == HDP_SOURCE) ||
+                       (rec_role == HDP_SOURCE && app_role == HDP_SINK))
+               return TRUE;
+
+       return FALSE;
+}
+
+static gboolean get_mdep_from_rec(const sdp_record_t *rec, uint8_t role,
+                               uint16_t d_type, uint8_t *mdep, char **desc)
+{
+       sdp_data_t *list, *feat;
+
+       if (!desc && !mdep)
+               return TRUE;
+
+       list = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST);
+
+       if (list->dtd != SDP_SEQ8 && list->dtd != SDP_SEQ16 &&
+                                                       list->dtd != SDP_SEQ32)
+               return FALSE;
+
+       for (feat = list->val.dataseq; feat; feat = feat->next) {
+               sdp_data_t *data_type, *mdepid, *role_t, *desc_t;
+
+               if (feat->dtd != SDP_SEQ8 && feat->dtd != SDP_SEQ16 &&
+                                               feat->dtd != SDP_SEQ32)
+                       continue;
+
+               mdepid = feat->val.dataseq;
+               if (!mdepid)
+                       continue;
+
+               data_type = mdepid->next;
+               if (!data_type)
+                       continue;
+
+               role_t = data_type->next;
+               if (!role_t)
+                       continue;
+
+               desc_t = role_t->next;
+
+               if (data_type->dtd != SDP_UINT16 || mdepid->dtd != SDP_UINT8 ||
+                                               role_t->dtd != SDP_UINT8)
+                       continue;
+
+               if (data_type->val.uint16 != d_type ||
+                                       !check_role(role_t->val.uint8, role))
+                       continue;
+
+               if (mdep)
+                       *mdep = mdepid->val.uint8;
+
+               if (desc  && desc_t && (desc_t->dtd == SDP_TEXT_STR8 ||
+                                       desc_t->dtd == SDP_TEXT_STR16  ||
+                                       desc_t->dtd == SDP_TEXT_STR32))
+                       *desc = g_strdup(desc_t->val.str);
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void get_mdep_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+       struct get_mdep_data *mdep_data = user_data;
+       GError *gerr = NULL;
+       uint8_t mdep;
+
+       if (err || !recs) {
+               g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                       "Error getting remote SDP records");
+               mdep_data->func(0, mdep_data->data, gerr);
+               g_error_free(gerr);
+               return;
+       }
+
+       if (!get_mdep_from_rec(recs->data, mdep_data->app->role,
+                               mdep_data->app->data_type, &mdep, NULL)) {
+               g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                       "No matching MDEP found");
+               mdep_data->func(0, mdep_data->data, gerr);
+               g_error_free(gerr);
+               return;
+       }
+
+       mdep_data->func(mdep, mdep_data->data, NULL);
+}
+
+static void free_mdep_data(gpointer data)
+{
+       struct get_mdep_data *mdep_data = data;
+
+       if (mdep_data->destroy)
+               mdep_data->destroy(mdep_data->data);
+       hdp_application_unref(mdep_data->app);
+
+       g_free(mdep_data);
+}
+
+gboolean hdp_get_mdep(struct hdp_device *device, struct hdp_application *app,
+                               hdp_continue_mdep_f func, gpointer data,
+                               GDestroyNotify destroy, GError **err)
+{
+       struct get_mdep_data *mdep_data;
+       bdaddr_t dst, src;
+       uuid_t uuid;
+
+       device_get_address(device->dev, &dst);
+       adapter_get_address(device_get_adapter(device->dev), &src);
+
+       mdep_data = g_new0(struct get_mdep_data, 1);
+       mdep_data->app = hdp_application_ref(app);
+       mdep_data->func = func;
+       mdep_data->data = data;
+       mdep_data->destroy = destroy;
+
+       bt_string2uuid(&uuid, HDP_UUID);
+       if (bt_search_service(&src, &dst, &uuid, get_mdep_cb, mdep_data,
+                                                       free_mdep_data)) {
+               g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                               "Can't get remote SDP record");
+               g_free(mdep_data);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean get_prot_desc_entry(sdp_data_t *entry, int type, guint16 *val)
+{
+       sdp_data_t *iter;
+       int proto;
+
+       if (!entry || (entry->dtd != SDP_SEQ8 && entry->dtd != SDP_SEQ16 &&
+                                               entry->dtd != SDP_SEQ32))
+               return FALSE;
+
+       iter = entry->val.dataseq;
+       if (!(iter->dtd & SDP_UUID_UNSPEC))
+               return FALSE;
+
+       proto = sdp_uuid_to_proto(&iter->val.uuid);
+       if (proto != type)
+               return FALSE;
+
+       if (!val)
+               return TRUE;
+
+       iter = iter->next;
+       if (iter->dtd != SDP_UINT16)
+               return FALSE;
+
+       *val = iter->val.uint16;
+
+       return TRUE;
+}
+
+static gboolean hdp_get_prot_desc_list(const sdp_record_t *rec, guint16 *psm,
+                                                       guint16 *version)
+{
+       sdp_data_t *pdl, *p0, *p1;
+
+       if (!psm && !version)
+               return TRUE;
+
+       pdl = sdp_data_get(rec, SDP_ATTR_PROTO_DESC_LIST);
+       if (pdl->dtd != SDP_SEQ8 && pdl->dtd != SDP_SEQ16 &&
+                                                       pdl->dtd != SDP_SEQ32)
+               return FALSE;
+
+       p0 = pdl->val.dataseq;
+       if (!get_prot_desc_entry(p0, L2CAP_UUID, psm))
+               return FALSE;
+
+       p1 = p0->next;
+       if (!get_prot_desc_entry(p1, MCAP_CTRL_UUID, version))
+               return FALSE;
+
+       return TRUE;
+}
+
+static gboolean hdp_get_add_prot_desc_list(const sdp_record_t *rec,
+                                                               guint16 *psm)
+{
+       sdp_data_t *pdl, *p0, *p1;
+
+       if (!psm)
+               return TRUE;
+
+       pdl = sdp_data_get(rec, SDP_ATTR_ADD_PROTO_DESC_LIST);
+       if (pdl->dtd != SDP_SEQ8)
+               return FALSE;
+       pdl = pdl->val.dataseq;
+       if (pdl->dtd != SDP_SEQ8)
+               return FALSE;
+
+       p0 = pdl->val.dataseq;
+
+       if (!get_prot_desc_entry(p0, L2CAP_UUID, psm))
+               return FALSE;
+       p1 = p0->next;
+       if (!get_prot_desc_entry(p1, MCAP_DATA_UUID, NULL))
+               return FALSE;
+
+       return TRUE;
+}
+
+static gboolean get_ccpsm(sdp_list_t *recs, uint16_t *ccpsm)
+{
+       sdp_list_t *l;
+
+       for (l = recs; l; l = l->next) {
+               sdp_record_t *rec = l->data;
+
+               if (hdp_get_prot_desc_list(rec, ccpsm, NULL))
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static gboolean get_dcpsm(sdp_list_t *recs, uint16_t *dcpsm)
+{
+       sdp_list_t *l;
+
+       for (l = recs; l; l = l->next) {
+               sdp_record_t *rec = l->data;
+
+               if (hdp_get_add_prot_desc_list(rec, dcpsm))
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void con_mcl_data_unref(struct conn_mcl_data *conn_data)
+{
+       if (!conn_data)
+               return;
+
+       if (--conn_data->refs > 0)
+               return;
+
+       if (conn_data->destroy)
+               conn_data->destroy(conn_data->data);
+
+       health_device_unref(conn_data->dev);
+       g_free(conn_data);
+}
+
+static void destroy_con_mcl_data(gpointer data)
+{
+       con_mcl_data_unref(data);
+}
+
+static struct conn_mcl_data *con_mcl_data_ref(struct conn_mcl_data *conn_data)
+{
+       if (!conn_data)
+               return NULL;
+
+       conn_data->refs++;
+       return conn_data;
+}
+
+static void create_mcl_cb(struct mcap_mcl *mcl, GError *err, gpointer data)
+{
+       struct conn_mcl_data *conn_data = data;
+       struct hdp_device *device = conn_data->dev;
+       GError *gerr = NULL;
+
+       if (err) {
+               conn_data->func(conn_data->data, err);
+               return;
+       }
+
+       if (!device->mcl)
+               device->mcl = mcap_mcl_ref(mcl);
+       device->mcl_conn = TRUE;
+
+       hdp_set_mcl_cb(device, &gerr);
+
+       conn_data->func(conn_data->data, gerr);
+       if (gerr)
+               g_error_free(gerr);
+}
+
+static void search_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+       struct conn_mcl_data *conn_data = user_data;
+       GError *gerr = NULL;
+       bdaddr_t dst;
+       uint16_t ccpsm;
+
+       if (!conn_data->dev->hdp_adapter->mi) {
+               g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                               "Mcap instance released");
+               goto fail;
+       }
+
+       if (err || !recs) {
+               g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                       "Error getting remote SDP records");
+               goto fail;
+       }
+
+       if (!get_ccpsm(recs, &ccpsm)) {
+               g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                               "Can't get remote PSM for control channel");
+               goto fail;
+       }
+
+       conn_data = con_mcl_data_ref(conn_data);
+
+       device_get_address(conn_data->dev->dev, &dst);
+       if (!mcap_create_mcl(conn_data->dev->hdp_adapter->mi, &dst, ccpsm,
+                                               create_mcl_cb, conn_data,
+                                               destroy_con_mcl_data, &gerr)) {
+               con_mcl_data_unref(conn_data);
+               goto fail;
+       }
+       return;
+fail:
+       conn_data->func(conn_data->data, gerr);
+       g_error_free(gerr);
+}
+
+gboolean hdp_establish_mcl(struct hdp_device *device,
+                                               hdp_continue_proc_f func,
+                                               gpointer data,
+                                               GDestroyNotify destroy,
+                                               GError **err)
+{
+       struct conn_mcl_data *conn_data;
+       bdaddr_t dst, src;
+       uuid_t uuid;
+
+       device_get_address(device->dev, &dst);
+       adapter_get_address(device_get_adapter(device->dev), &src);
+
+       conn_data = g_new0(struct conn_mcl_data, 1);
+       conn_data->refs = 1;
+       conn_data->func = func;
+       conn_data->data = data;
+       conn_data->destroy = destroy;
+       conn_data->dev = health_device_ref(device);
+
+       bt_string2uuid(&uuid, HDP_UUID);
+       if (bt_search_service(&src, &dst, &uuid, search_cb, conn_data,
+                                               destroy_con_mcl_data)) {
+               g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                               "Can't get remote SDP record");
+               g_free(conn_data);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void get_dcpsm_cb(sdp_list_t *recs, int err, gpointer data)
+{
+       struct get_dcpsm_data *dcpsm_data = data;
+       GError *gerr = NULL;
+       uint16_t dcpsm;
+
+       if (err || !recs) {
+               g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                       "Error getting remote SDP records");
+               goto fail;
+       }
+
+       if (!get_dcpsm(recs, &dcpsm)) {
+               g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+                               "Can't get remote PSM for data channel");
+               goto fail;
+       }
+
+       dcpsm_data->func(dcpsm, dcpsm_data->data, NULL);
+       return;
+
+fail:
+       dcpsm_data->func(0, dcpsm_data->data, gerr);
+       g_error_free(gerr);
+}
+
+static void free_dcpsm_data(gpointer data)
+{
+       struct get_dcpsm_data *dcpsm_data = data;
+
+       if (!dcpsm_data)
+               return;
+
+       if (dcpsm_data->destroy)
+               dcpsm_data->destroy(dcpsm_data->data);
+
+       g_free(dcpsm_data);
+}
+
+gboolean hdp_get_dcpsm(struct hdp_device *device, hdp_continue_dcpsm_f func,
+                                                       gpointer data,
+                                                       GDestroyNotify destroy,
+                                                       GError **err)
+{
+       struct get_dcpsm_data *dcpsm_data;
+       bdaddr_t dst, src;
+       uuid_t uuid;
+
+       device_get_address(device->dev, &dst);
+       adapter_get_address(device_get_adapter(device->dev), &src);
+
+       dcpsm_data = g_new0(struct get_dcpsm_data, 1);
+       dcpsm_data->func = func;
+       dcpsm_data->data = data;
+       dcpsm_data->destroy = destroy;
+
+       bt_string2uuid(&uuid, HDP_UUID);
+       if (bt_search_service(&src, &dst, &uuid, get_dcpsm_cb, dcpsm_data,
+                                                       free_dcpsm_data)) {
+               g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR,
+                                               "Can't get remote SDP record");
+               g_free(dcpsm_data);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void hdp_free_application(struct hdp_application *app)
+{
+       if (app->dbus_watcher)
+               g_dbus_remove_watch(app->conn, app->dbus_watcher);
+
+       if (app->conn)
+               dbus_connection_unref(app->conn);
+       g_free(app->oname);
+       g_free(app->description);
+       g_free(app->path);
+       g_free(app);
+}
+
+struct hdp_application *hdp_application_ref(struct hdp_application *app)
+{
+       if (!app)
+               return NULL;
+
+       app->ref++;
+
+       DBG("health_application_ref(%p): ref=%d", app, app->ref);
+       return app;
+}
+
+void hdp_application_unref(struct hdp_application *app)
+{
+       if (!app)
+               return;
+
+       app->ref --;
+
+       DBG("health_application_unref(%p): ref=%d", app, app->ref);
+       if (app->ref > 0)
+               return;
+
+       hdp_free_application(app);
+}
diff --git a/health/hdp_util.h b/health/hdp_util.h
new file mode 100644 (file)
index 0000000..fc59f50
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Authors:
+ *  Santiago Carot Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 __HDP_UTIL_H__
+#define __HDP_UTIL_H__
+
+typedef void (*hdp_continue_mdep_f)(uint8_t mdep, gpointer user_data,
+                                                               GError *err);
+typedef void (*hdp_continue_dcpsm_f)(uint16_t dcpsm, gpointer user_data,
+                                                               GError *err);
+typedef void (*hdp_continue_proc_f)(gpointer user_data, GError *err);
+
+struct hdp_application *hdp_get_app_config(DBusMessageIter *iter, GError **err);
+gboolean hdp_update_sdp_record(struct hdp_adapter *adapter, GSList *app_list);
+gboolean hdp_get_mdep(struct hdp_device *device, struct hdp_application *app,
+                               hdp_continue_mdep_f func,
+                               gpointer data, GDestroyNotify destroy,
+                               GError **err);
+
+gboolean hdp_establish_mcl(struct hdp_device *device,
+                                               hdp_continue_proc_f func,
+                                               gpointer data,
+                                               GDestroyNotify destroy,
+                                               GError **err);
+
+gboolean hdp_get_dcpsm(struct hdp_device *device, hdp_continue_dcpsm_f func,
+                                                       gpointer data,
+                                                       GDestroyNotify destroy,
+                                                       GError **err);
+
+
+struct hdp_application *hdp_application_ref(struct hdp_application *app);
+void hdp_application_unref(struct hdp_application *app);
+
+struct hdp_device *health_device_ref(struct hdp_device *hdp_dev);
+void health_device_unref(struct hdp_device *hdp_dev);
+
+
+#endif /* __HDP_UTIL_H__ */
diff --git a/health/mcap.c b/health/mcap.c
new file mode 100644 (file)
index 0000000..81fd8df
--- /dev/null
@@ -0,0 +1,2194 @@
+/*
+ *
+ *  MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ *  Authors:
+ *  Santiago Carot-Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 "log.h"
+#include "error.h"
+
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "btio.h"
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include "mcap.h"
+#include "mcap_lib.h"
+#include "mcap_internal.h"
+
+#define RESPONSE_TIMER 6       /* seconds */
+#define MAX_CACHED     10      /* 10 devices */
+
+#define MCAP_ERROR g_quark_from_static_string("mcap-error-quark")
+
+#define RELEASE_TIMER(__mcl) do {              \
+       if (__mcl->tid) {                       \
+               g_source_remove(__mcl->tid);    \
+               __mcl->tid = 0;                 \
+       }                                       \
+} while(0)
+
+struct connect_mcl {
+       struct mcap_mcl         *mcl;           /* MCL for this operation */
+       mcap_mcl_connect_cb     connect_cb;     /* Connect callback */
+       GDestroyNotify          destroy;        /* Destroy callback */
+       gpointer                user_data;      /* Callback user data */
+};
+
+typedef union {
+       mcap_mdl_operation_cb           op;
+       mcap_mdl_operation_conf_cb      op_conf;
+       mcap_mdl_notify_cb              notify;
+} mcap_cb_type;
+
+struct mcap_mdl_op_cb {
+       struct mcap_mdl         *mdl;           /* MDL for this operation */
+       mcap_cb_type            cb;             /* Operation callback */
+       GDestroyNotify          destroy;        /* Destroy callback */
+       gpointer                user_data;      /* Callback user data */
+};
+
+/* MCAP finite state machine functions */
+static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
+static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
+static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
+
+static void (*proc_req[])(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) = {
+       proc_req_connected,
+       proc_req_pending,
+       proc_req_active
+};
+
+static void mcap_cache_mcl(struct mcap_mcl *mcl);
+
+static void default_mdl_connected_cb(struct mcap_mdl *mdl, gpointer data)
+{
+       DBG("MCAP Unmanaged mdl connection");
+}
+
+static void default_mdl_closed_cb(struct mcap_mdl *mdl, gpointer data)
+{
+       DBG("MCAP Unmanaged mdl closed");
+}
+
+static void default_mdl_deleted_cb(struct mcap_mdl *mdl, gpointer data)
+{
+       DBG("MCAP Unmanaged mdl deleted");
+}
+
+static void default_mdl_aborted_cb(struct mcap_mdl *mdl, gpointer data)
+{
+       DBG("MCAP Unmanaged mdl aborted");
+}
+
+static uint8_t default_mdl_conn_req_cb(struct mcap_mcl *mcl,
+                                               uint8_t mdepid, uint16_t mdlid,
+                                               uint8_t *conf, gpointer data)
+{
+       DBG("MCAP mdl remote connection aborted");
+       /* Due to this callback isn't managed this request won't be supported */
+       return MCAP_REQUEST_NOT_SUPPORTED;
+}
+
+static uint8_t default_mdl_reconn_req_cb(struct mcap_mdl *mdl,
+                                               gpointer data)
+{
+       DBG("MCAP mdl remote reconnection aborted");
+       /* Due to this callback isn't managed this request won't be supported */
+       return MCAP_REQUEST_NOT_SUPPORTED;
+}
+
+static void set_default_cb(struct mcap_mcl *mcl)
+{
+       if (!mcl->cb)
+               mcl->cb = g_new0(struct mcap_mdl_cb, 1);
+
+       mcl->cb->mdl_connected = default_mdl_connected_cb;
+       mcl->cb->mdl_closed = default_mdl_closed_cb;
+       mcl->cb->mdl_deleted = default_mdl_deleted_cb;
+       mcl->cb->mdl_aborted = default_mdl_aborted_cb;
+       mcl->cb->mdl_conn_req = default_mdl_conn_req_cb;
+       mcl->cb->mdl_reconn_req = default_mdl_reconn_req_cb;
+}
+
+static char *error2str(uint8_t rc)
+{
+       switch (rc) {
+       case MCAP_SUCCESS:
+               return "Success";
+       case MCAP_INVALID_OP_CODE:
+               return "Invalid Op Code";
+       case MCAP_INVALID_PARAM_VALUE:
+               return "Invalid Parameter Value";
+       case MCAP_INVALID_MDEP:
+               return "Invalid MDEP";
+       case MCAP_MDEP_BUSY:
+               return "MDEP Busy";
+       case MCAP_INVALID_MDL:
+               return "Invalid MDL";
+       case MCAP_MDL_BUSY:
+               return "MDL Busy";
+       case MCAP_INVALID_OPERATION:
+               return "Invalid Operation";
+       case MCAP_RESOURCE_UNAVAILABLE:
+               return "Resource Unavailable";
+       case MCAP_UNSPECIFIED_ERROR:
+               return "Unspecified Error";
+       case MCAP_REQUEST_NOT_SUPPORTED:
+               return "Request Not Supported";
+       case MCAP_CONFIGURATION_REJECTED:
+               return "Configuration Rejected";
+       default:
+               return "Unknown Response Code";
+       }
+}
+
+static gboolean mcap_send_std_opcode(struct mcap_mcl *mcl, void *cmd,
+                                               uint32_t size, GError **err)
+{
+       if (mcl->state == MCL_IDLE) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+                                                       "MCL is not connected");
+               return FALSE;
+       }
+
+       if (mcl->req != MCL_AVAILABLE) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_RESOURCE_UNAVAILABLE,
+                                                       "Pending request");
+               return FALSE;
+       }
+
+       if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_REQUEST_NOT_SUPPORTED,
+                               "Remote does not support standard opcodes");
+               return FALSE;
+       }
+
+       if (mcl->state == MCL_PENDING) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_OPERATION,
+                       "Not Std Op. Codes can be sent in PENDING State");
+               return FALSE;
+       }
+
+       if (mcap_send_data(g_io_channel_unix_get_fd(mcl->cc), cmd, size) < 0) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+                                       "Command can't be sent, write error");
+               return FALSE;
+       }
+
+       mcl->lcmd = cmd;
+       mcl->req = MCL_WAITING_RSP;
+
+       return TRUE;
+}
+
+static void update_mcl_state(struct mcap_mcl *mcl)
+{
+       GSList *l;
+       struct mcap_mdl *mdl;
+
+       if (mcl->state == MCL_PENDING)
+               return;
+
+       for (l = mcl->mdls; l; l = l->next) {
+               mdl = l->data;
+
+               if (mdl->state == MDL_CONNECTED) {
+                       mcl->state = MCL_ACTIVE;
+                       return;
+               }
+       }
+
+       mcl->state = MCL_CONNECTED;
+}
+
+static void shutdown_mdl(struct mcap_mdl *mdl)
+{
+       mdl->state = MDL_CLOSED;
+
+       if (mdl->wid) {
+               g_source_remove(mdl->wid);
+               mdl->wid = 0;
+       }
+
+       if (mdl->dc) {
+               g_io_channel_shutdown(mdl->dc, TRUE, NULL);
+               g_io_channel_unref(mdl->dc);
+               mdl->dc = NULL;
+       }
+}
+
+static void free_mdl(struct mcap_mdl *mdl)
+{
+       if (!mdl)
+               return;
+
+       mcap_mcl_unref(mdl->mcl);
+       g_free(mdl);
+}
+
+static gint cmp_mdl_state(gconstpointer a, gconstpointer b)
+{
+       const struct mcap_mdl *mdl = a;
+       const MDLState *st = b;
+
+       if (mdl->state == *st)
+               return 0;
+       else if (mdl->state < *st)
+               return -1;
+       else
+               return 1;
+}
+
+static void free_mcap_mdl_op(struct mcap_mdl_op_cb *op)
+{
+       if (op->destroy)
+               op->destroy(op->user_data);
+
+       if (op->mdl)
+               mcap_mdl_unref(op->mdl);
+
+       g_free(op);
+}
+
+static void free_mcl_priv_data(struct mcap_mcl *mcl)
+{
+       free_mcap_mdl_op(mcl->priv_data);
+       mcl->priv_data = NULL;
+}
+
+static void mcap_notify_error(struct mcap_mcl *mcl, GError *err)
+{
+       struct mcap_mdl_op_cb *con = mcl->priv_data;
+       struct mcap_mdl *mdl;
+       MDLState st;
+       GSList *l;
+
+       if (!con || !mcl->lcmd)
+               return;
+
+       switch (mcl->lcmd[0]) {
+       case MCAP_MD_CREATE_MDL_REQ:
+               st = MDL_WAITING;
+               l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
+               mdl = l->data;
+               mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+               mcap_mdl_unref(mdl);
+               update_mcl_state(mcl);
+               con->cb.op_conf(NULL, 0, err, con->user_data);
+               break;
+       case MCAP_MD_ABORT_MDL_REQ:
+               st = MDL_WAITING;
+               l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
+               shutdown_mdl(l->data);
+               update_mcl_state(mcl);
+               con->cb.notify(err, con->user_data);
+               break;
+       case MCAP_MD_DELETE_MDL_REQ:
+               for (l = mcl->mdls; l; l = l->next) {
+                       mdl = l->data;
+                       if (mdl->state == MDL_DELETING)
+                               mdl->state = (mdl->dc) ? MDL_CONNECTED :
+                                                               MDL_CLOSED;
+               }
+               update_mcl_state(mcl);
+               con->cb.notify(err, con->user_data);
+               break;
+       case MCAP_MD_RECONNECT_MDL_REQ:
+               st = MDL_WAITING;
+               l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
+               shutdown_mdl(l->data);
+               update_mcl_state(mcl);
+               con->cb.op(NULL, err, con->user_data);
+               break;
+       }
+
+       free_mcl_priv_data(mcl);
+       g_free(mcl->lcmd);
+       mcl->lcmd = NULL;
+}
+
+int mcap_send_data(int sock, const void *buf, uint32_t size)
+{
+       const uint8_t *buf_b = buf;
+       uint32_t sent = 0;
+
+       while (sent < size) {
+               int n = write(sock, buf_b + sent, size - sent);
+               if (n < 0)
+                       return -1;
+               sent += n;
+       }
+
+       return 0;
+}
+
+static int mcap_send_cmd(struct mcap_mcl *mcl, uint8_t oc, uint8_t rc,
+                                       uint16_t mdl, uint8_t *data, size_t len)
+{
+       mcap_rsp *cmd;
+       int sock, sent;
+
+       if (mcl->cc == NULL)
+               return -1;
+
+       sock = g_io_channel_unix_get_fd(mcl->cc);
+
+       cmd = g_malloc(sizeof(mcap_rsp) + len);
+       cmd->op = oc;
+       cmd->rc = rc;
+       cmd->mdl = htons(mdl);
+
+       if (data && len > 0)
+               memcpy(cmd->data, data, len);
+
+       sent = mcap_send_data(sock, cmd, sizeof(mcap_rsp) + len);
+       g_free(cmd);
+
+       return sent;
+}
+
+static struct mcap_mdl *get_mdl(struct mcap_mcl *mcl, uint16_t mdlid)
+{
+       GSList *l;
+       struct mcap_mdl *mdl;
+
+       for (l = mcl->mdls; l; l = l->next) {
+               mdl = l->data;
+               if (mdlid == mdl->mdlid)
+                       return mdl;
+       }
+
+       return NULL;
+}
+
+static uint16_t generate_mdlid(struct mcap_mcl *mcl)
+{
+       uint16_t mdlid = mcl->next_mdl;
+       struct mcap_mdl *mdl;
+
+       do {
+               mdl = get_mdl(mcl, mdlid);
+               if (!mdl) {
+                       mcl->next_mdl = (mdlid % MCAP_MDLID_FINAL) + 1;
+                       return mdlid;
+               } else
+                       mdlid = (mdlid % MCAP_MDLID_FINAL) + 1;
+       } while (mdlid != mcl->next_mdl);
+
+       /* No more mdlids availables */
+       return 0;
+}
+
+static mcap_md_req *create_req(uint8_t op, uint16_t mdl_id)
+{
+       mcap_md_req *req_cmd;
+
+       req_cmd = g_new0(mcap_md_req, 1);
+
+       req_cmd->op = op;
+       req_cmd->mdl = htons(mdl_id);
+
+       return req_cmd;
+}
+
+static mcap_md_create_mdl_req *create_mdl_req(uint16_t mdl_id, uint8_t mdep,
+                                                               uint8_t conf)
+{
+       mcap_md_create_mdl_req *req_mdl;
+
+       req_mdl = g_new0(mcap_md_create_mdl_req, 1);
+
+       req_mdl->op = MCAP_MD_CREATE_MDL_REQ;
+       req_mdl->mdl = htons(mdl_id);
+       req_mdl->mdep = mdep;
+       req_mdl->conf = conf;
+
+       return req_mdl;
+}
+
+static gint compare_mdl(gconstpointer a, gconstpointer b)
+{
+       const struct mcap_mdl *mdla = a;
+       const struct mcap_mdl *mdlb = b;
+
+       if (mdla->mdlid == mdlb->mdlid)
+               return 0;
+       else if (mdla->mdlid < mdlb->mdlid)
+               return -1;
+       else
+               return 1;
+}
+
+static gboolean wait_response_timer(gpointer data)
+{
+       struct mcap_mcl *mcl = data;
+
+       GError *gerr = NULL;
+
+       RELEASE_TIMER(mcl);
+
+       g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED,
+                                       "Timeout waiting response");
+
+       mcap_notify_error(mcl, gerr);
+
+       g_error_free(gerr);
+       mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
+       mcap_cache_mcl(mcl);
+
+       return FALSE;
+}
+
+gboolean mcap_create_mdl(struct mcap_mcl *mcl,
+                               uint8_t mdepid,
+                               uint8_t conf,
+                               mcap_mdl_operation_conf_cb connect_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err)
+{
+       struct mcap_mdl *mdl;
+       struct mcap_mdl_op_cb *con;
+       mcap_md_create_mdl_req *cmd;
+       uint16_t id;
+
+       id = generate_mdlid(mcl);
+       if (!id) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+                                       "Not more mdlids available");
+               return FALSE;
+       }
+
+       mdl = g_new0(struct mcap_mdl, 1);
+       mdl->mcl = mcap_mcl_ref(mcl);
+       mdl->mdlid = id;
+       mdl->mdep_id = mdepid;
+       mdl->state = MDL_WAITING;
+
+       con = g_new0(struct mcap_mdl_op_cb, 1);
+       con->mdl = mcap_mdl_ref(mdl);
+       con->cb.op_conf = connect_cb;
+       con->destroy = destroy;
+       con->user_data = user_data;
+
+       cmd = create_mdl_req(id, mdepid, conf);
+       if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_create_mdl_req),
+                                                                       err)) {
+               mcap_mdl_unref(con->mdl);
+               g_free(con);
+               g_free(cmd);
+               return FALSE;
+       }
+
+       mcl->state = MCL_ACTIVE;
+       mcl->priv_data = con;
+
+       mcl->mdls = g_slist_insert_sorted(mcl->mdls, mcap_mdl_ref(mdl),
+                                                               compare_mdl);
+       mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
+                                                                       mcl);
+       return TRUE;
+}
+
+gboolean mcap_reconnect_mdl(struct mcap_mdl *mdl,
+                               mcap_mdl_operation_cb reconnect_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err)
+{
+       struct mcap_mdl_op_cb *con;
+       struct mcap_mcl *mcl = mdl->mcl;
+       mcap_md_req *cmd;
+
+       if (mdl->state != MDL_CLOSED) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+                                       "MDL is not closed");
+               return FALSE;
+       }
+
+       cmd = create_req(MCAP_MD_RECONNECT_MDL_REQ, mdl->mdlid);
+       if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
+               g_free(cmd);
+               return FALSE;
+       }
+
+       mdl->state = MDL_WAITING;
+
+       con = g_new0(struct mcap_mdl_op_cb, 1);
+       con->mdl = mcap_mdl_ref(mdl);
+       con->cb.op = reconnect_cb;
+       con->destroy = destroy;
+       con->user_data = user_data;
+
+       mcl->state = MCL_ACTIVE;
+       mcl->priv_data = con;
+
+       mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
+                                                                       mcl);
+       return TRUE;
+}
+
+static gboolean send_delete_req(struct mcap_mcl *mcl,
+                                               struct mcap_mdl_op_cb *con,
+                                               uint16_t mdlid,
+                                               GError **err)
+{
+       mcap_md_req *cmd;
+
+       cmd = create_req(MCAP_MD_DELETE_MDL_REQ, mdlid);
+       if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
+               g_free(cmd);
+               return FALSE;
+       }
+
+       mcl->priv_data = con;
+
+       mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
+                                                                       mcl);
+       return TRUE;
+}
+
+gboolean mcap_delete_all_mdls(struct mcap_mcl *mcl,
+                                       mcap_mdl_notify_cb delete_cb,
+                                       gpointer user_data,
+                                       GDestroyNotify destroy,
+                                       GError **err)
+{
+       GSList *l;
+       struct mcap_mdl *mdl;
+       struct mcap_mdl_op_cb *con;
+
+       DBG("MCL in state: %d", mcl->state);
+       if (!mcl->mdls) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+                               "There are not MDLs created");
+               return FALSE;
+       }
+
+       for (l = mcl->mdls; l; l = l->next) {
+               mdl = l->data;
+               if (mdl->state != MDL_WAITING)
+                       mdl->state = MDL_DELETING;
+       }
+
+       con = g_new0(struct mcap_mdl_op_cb, 1);
+       con->mdl = NULL;
+       con->cb.notify = delete_cb;
+       con->destroy = destroy;
+       con->user_data = user_data;
+
+
+       if (!send_delete_req(mcl, con, MCAP_ALL_MDLIDS, err)) {
+               g_free(con);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+gboolean mcap_delete_mdl(struct mcap_mdl *mdl, mcap_mdl_notify_cb delete_cb,
+                                                       gpointer user_data,
+                                                       GDestroyNotify destroy,
+                                                       GError **err)
+{
+       struct mcap_mcl *mcl= mdl->mcl;
+       struct mcap_mdl_op_cb *con;
+       GSList *l;
+
+       l = g_slist_find(mcl->mdls, mdl);
+
+       if (!l) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL,
+                                       "%s" , error2str(MCAP_INVALID_MDEP));
+               return FALSE;
+       }
+
+       if (mdl->state == MDL_WAITING) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+                                                       "Mdl is not created");
+               return FALSE;
+       }
+
+       mdl->state = MDL_DELETING;
+
+       con = g_new0(struct mcap_mdl_op_cb, 1);
+       con->mdl = mcap_mdl_ref(mdl);
+       con->cb.notify = delete_cb;
+       con->destroy = destroy;
+       con->user_data = user_data;
+
+       if (!send_delete_req(mcl, con, mdl->mdlid, err)) {
+               mcap_mdl_unref(con->mdl);
+               g_free(con);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+gboolean mcap_mdl_abort(struct mcap_mdl *mdl, mcap_mdl_notify_cb abort_cb,
+                                                       gpointer user_data,
+                                                       GDestroyNotify destroy,
+                                                       GError **err)
+{
+       struct mcap_mdl_op_cb *con;
+       struct mcap_mcl *mcl = mdl->mcl;
+       mcap_md_req *cmd;
+
+       if (mdl->state != MDL_WAITING) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+                                                       "Mdl in invalid state");
+               return FALSE;
+       }
+
+       cmd = create_req(MCAP_MD_ABORT_MDL_REQ, mdl->mdlid);
+       if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
+               g_free(cmd);
+               return FALSE;
+       }
+
+       con = g_new0(struct mcap_mdl_op_cb, 1);
+       con->mdl = mcap_mdl_ref(mdl);
+       con->cb.notify = abort_cb;
+       con->destroy = destroy;
+       con->user_data = user_data;
+
+       mcl->priv_data = con;
+       mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
+                                                                       mcl);
+       return TRUE;
+}
+
+static struct mcap_mcl *find_mcl(GSList *list, const bdaddr_t *addr)
+{
+       GSList *l;
+       struct mcap_mcl *mcl;
+
+       for (l = list; l; l = l->next) {
+               mcl = l->data;
+
+               if (!bacmp(&mcl->addr, addr))
+                       return mcl;
+       }
+
+       return NULL;
+}
+
+int mcap_mdl_get_fd(struct mcap_mdl *mdl)
+{
+       if (!mdl || mdl->state != MDL_CONNECTED)
+               return -ENOTCONN;
+
+       return g_io_channel_unix_get_fd(mdl->dc);
+}
+
+uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl)
+{
+       if (!mdl)
+               return MCAP_MDLID_RESERVED;
+
+       return mdl->mdlid;
+}
+
+static void close_mcl(struct mcap_mcl *mcl, gboolean cache_requested)
+{
+       gboolean save = ((!(mcl->ctrl & MCAP_CTRL_FREE)) && cache_requested);
+
+       RELEASE_TIMER(mcl);
+
+       if (mcl->cc) {
+               g_io_channel_shutdown(mcl->cc, TRUE, NULL);
+               g_io_channel_unref(mcl->cc);
+               mcl->cc = NULL;
+       }
+
+       if (mcl->wid) {
+               g_source_remove(mcl->wid);
+               mcl->wid = 0;
+       }
+
+       if (mcl->lcmd) {
+               g_free(mcl->lcmd);
+               mcl->lcmd = NULL;
+       }
+
+       if (mcl->priv_data)
+               free_mcl_priv_data(mcl);
+
+       g_slist_foreach(mcl->mdls, (GFunc) shutdown_mdl, NULL);
+
+       mcap_sync_stop(mcl);
+
+       mcl->state = MCL_IDLE;
+
+       if (save)
+               return;
+
+       g_slist_foreach(mcl->mdls, (GFunc) mcap_mdl_unref, NULL);
+       g_slist_free(mcl->mdls);
+       mcl->mdls = NULL;
+}
+
+static void mcap_mcl_shutdown(struct mcap_mcl *mcl)
+{
+       close_mcl(mcl, TRUE);
+}
+
+static void mcap_mcl_release(struct mcap_mcl *mcl)
+{
+       close_mcl(mcl, FALSE);
+}
+
+static void mcap_cache_mcl(struct mcap_mcl *mcl)
+{
+       GSList *l;
+       struct mcap_mcl *last;
+       int len;
+
+       if (mcl->ctrl & MCAP_CTRL_CACHED)
+               return;
+
+       mcl->mi->mcls = g_slist_remove(mcl->mi->mcls, mcl);
+
+       if (mcl->ctrl & MCAP_CTRL_NOCACHE) {
+               mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
+               mcap_mcl_release(mcl);
+               mcap_mcl_unref(mcl);
+               return;
+       }
+
+       DBG("Caching MCL");
+
+       len = g_slist_length(mcl->mi->cached);
+       if (len == MAX_CACHED) {
+               /* Remove the latest cached mcl */
+               l = g_slist_last(mcl->mi->cached);
+               last = l->data;
+               mcl->mi->cached = g_slist_remove(mcl->mi->cached, last);
+               last->ctrl &= ~MCAP_CTRL_CACHED;
+               if (last->ctrl & MCAP_CTRL_CONN) {
+                       /* We have to release this MCL if */
+                       /* connection is not succesful    */
+                       last->ctrl |= MCAP_CTRL_FREE;
+               } else {
+                       mcap_mcl_release(last);
+                       last->mi->mcl_uncached_cb(last, last->mi->user_data);
+               }
+               mcap_mcl_unref(last);
+       }
+
+       mcl->mi->cached = g_slist_prepend(mcl->mi->cached, mcl);
+       mcl->ctrl |= MCAP_CTRL_CACHED;
+       mcap_mcl_shutdown(mcl);
+}
+
+static void mcap_uncache_mcl(struct mcap_mcl *mcl)
+{
+       if (!(mcl->ctrl & MCAP_CTRL_CACHED))
+               return;
+
+       DBG("Got MCL from cache");
+
+       mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
+       mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls, mcl);
+       mcl->ctrl &= ~MCAP_CTRL_CACHED;
+       mcl->ctrl &= ~MCAP_CTRL_FREE;
+}
+
+void mcap_close_mcl(struct mcap_mcl *mcl, gboolean cache)
+{
+       if (!mcl)
+               return;
+
+       if (mcl->ctrl & MCAP_CTRL_FREE) {
+               mcap_mcl_release(mcl);
+               return;
+       }
+
+       if (!cache)
+               mcl->ctrl |= MCAP_CTRL_NOCACHE;
+
+       if (mcl->cc) {
+               g_io_channel_shutdown(mcl->cc, TRUE, NULL);
+               g_io_channel_unref(mcl->cc);
+               mcl->cc = NULL;
+               mcl->state = MCL_IDLE;
+       } else if ((mcl->ctrl & MCAP_CTRL_CACHED) &&
+                                       (mcl->ctrl & MCAP_CTRL_NOCACHE)) {
+               mcl->ctrl &= ~MCAP_CTRL_CACHED;
+               mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
+               mcap_mcl_release(mcl);
+               mcap_mcl_unref(mcl);
+       }
+}
+
+struct mcap_mcl *mcap_mcl_ref(struct mcap_mcl *mcl)
+{
+       mcl->ref++;
+
+       DBG("mcap_mcl_ref(%p): ref=%d", mcl, mcl->ref);
+
+       return mcl;
+}
+
+void mcap_mcl_unref(struct mcap_mcl *mcl)
+{
+       mcl->ref--;
+
+       DBG("mcap_mcl_unref(%p): ref=%d", mcl, mcl->ref);
+
+       if (mcl->ref > 0)
+               return;
+
+       mcap_mcl_release(mcl);
+       mcap_instance_unref(mcl->mi);
+       g_free(mcl->cb);
+       g_free(mcl);
+}
+
+static gboolean parse_set_opts(struct mcap_mdl_cb *mdl_cb, GError **err,
+                                               McapMclCb cb1, va_list args)
+{
+       McapMclCb cb = cb1;
+       struct mcap_mdl_cb *c;
+
+       c = g_new0(struct mcap_mdl_cb, 1);
+
+       while (cb != MCAP_MDL_CB_INVALID) {
+               switch (cb) {
+               case MCAP_MDL_CB_CONNECTED:
+                       c->mdl_connected = va_arg(args, mcap_mdl_event_cb);
+                       break;
+               case MCAP_MDL_CB_CLOSED:
+                       c->mdl_closed = va_arg(args, mcap_mdl_event_cb);
+                       break;
+               case MCAP_MDL_CB_DELETED:
+                       c->mdl_deleted = va_arg(args, mcap_mdl_event_cb);
+                       break;
+               case MCAP_MDL_CB_ABORTED:
+                       c->mdl_aborted = va_arg(args, mcap_mdl_event_cb);
+                       break;
+               case MCAP_MDL_CB_REMOTE_CONN_REQ:
+                       c->mdl_conn_req = va_arg(args,
+                                               mcap_remote_mdl_conn_req_cb);
+                       break;
+               case MCAP_MDL_CB_REMOTE_RECONN_REQ:
+                       c->mdl_reconn_req = va_arg(args,
+                                               mcap_remote_mdl_reconn_req_cb);
+                       break;
+               default:
+                       g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+                                               "Unknown option %d", cb);
+                       return FALSE;
+               }
+               cb = va_arg(args, int);
+       }
+
+       /* Set new callbacks */
+       if (c->mdl_connected)
+               mdl_cb->mdl_connected = c->mdl_connected;
+       if (c->mdl_closed)
+               mdl_cb->mdl_closed = c->mdl_closed;
+       if (c->mdl_deleted)
+               mdl_cb->mdl_deleted = c->mdl_deleted;
+       if (c->mdl_aborted)
+               mdl_cb->mdl_aborted = c->mdl_aborted;
+       if (c->mdl_conn_req)
+               mdl_cb->mdl_conn_req = c->mdl_conn_req;
+       if (c->mdl_reconn_req)
+               mdl_cb->mdl_reconn_req = c->mdl_reconn_req;
+
+       g_free(c);
+
+       return TRUE;
+}
+
+gboolean mcap_mcl_set_cb(struct mcap_mcl *mcl, gpointer user_data,
+                                       GError **gerr, McapMclCb cb1, ...)
+{
+       va_list args;
+       gboolean ret;
+
+       va_start(args, cb1);
+       ret = parse_set_opts(mcl->cb, gerr, cb1, args);
+       va_end(args);
+
+       if (!ret)
+               return FALSE;
+
+       mcl->cb->user_data = user_data;
+       return TRUE;
+}
+
+void mcap_mcl_get_addr(struct mcap_mcl *mcl, bdaddr_t *addr)
+{
+       bacpy(addr, &mcl->addr);
+}
+
+static void mcap_del_mdl(gpointer elem, gpointer user_data)
+{
+       struct mcap_mdl *mdl = elem;
+       gboolean notify = *(gboolean *) user_data;
+
+       shutdown_mdl(mdl);
+       if (notify)
+               mdl->mcl->cb->mdl_deleted(mdl, mdl->mcl->cb->user_data);
+
+       mcap_mdl_unref(mdl);
+}
+
+static gboolean check_cmd_req_length(struct mcap_mcl *mcl, void *cmd,
+                               uint32_t rlen, uint32_t explen, uint8_t rspcod)
+{
+       mcap_md_req *req;
+       uint16_t mdl_id;
+
+       if (rlen != explen) {
+               if (rlen >= sizeof(mcap_md_req)) {
+                       req = cmd;
+                       mdl_id = ntohs(req->mdl);
+               } else {
+                       /* We can't get mdlid */
+                       mdl_id = MCAP_MDLID_RESERVED;
+               }
+               mcap_send_cmd(mcl, rspcod, MCAP_INVALID_PARAM_VALUE, mdl_id,
+                                                               NULL, 0);
+               return FALSE;
+       }
+       return TRUE;
+}
+
+static void process_md_create_mdl_req(struct mcap_mcl *mcl, void *cmd,
+                                                               uint32_t len)
+{
+       mcap_md_create_mdl_req *req;
+       struct mcap_mdl *mdl;
+       uint16_t mdl_id;
+       uint8_t mdep_id;
+       uint8_t cfga, conf;
+       uint8_t rsp;
+
+       if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_create_mdl_req),
+                                                       MCAP_MD_CREATE_MDL_RSP))
+               return;
+
+       req = cmd;
+       mdl_id = ntohs(req->mdl);
+       if (mdl_id < MCAP_MDLID_INITIAL || mdl_id > MCAP_MDLID_FINAL) {
+               mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDL,
+                                                       mdl_id, NULL, 0);
+               return;
+       }
+
+       mdep_id = req->mdep;
+       if (mdep_id > MCAP_MDEPID_FINAL) {
+               mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDEP,
+                                                       mdl_id, NULL, 0);
+               return;
+       }
+
+       mdl = get_mdl(mcl, mdl_id);
+       if (mdl && (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING )) {
+               /* Creation request arrives for a MDL that is being managed
+               * at current moment */
+               mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_MDL_BUSY,
+                                                       mdl_id, NULL, 0);
+               return;
+       }
+
+       cfga = conf = req->conf;
+       /* Callback to upper layer */
+       rsp = mcl->cb->mdl_conn_req(mcl, mdep_id, mdl_id, &conf,
+                                                       mcl->cb->user_data);
+       if (mcl->state == MCL_IDLE) {
+               /* MCL has been closed int the callback */
+               return;
+       }
+
+       if (cfga != 0 && cfga != conf) {
+               /* Remote device set default configuration but upper profile */
+               /* has changed it. Protocol Error: force closing the MCL by */
+               /* remote device using UNSPECIFIED_ERROR response */
+               mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP,
+                               MCAP_UNSPECIFIED_ERROR, mdl_id, NULL, 0);
+               return;
+       }
+       if (rsp != MCAP_SUCCESS) {
+               mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, rsp, mdl_id,
+                                                               NULL, 0);
+               return;
+       }
+
+       if (!mdl) {
+               mdl = g_new0(struct mcap_mdl, 1);
+               mdl->mcl = mcap_mcl_ref(mcl);
+               mdl->mdlid = mdl_id;
+               mcl->mdls = g_slist_insert_sorted(mcl->mdls, mcap_mdl_ref(mdl),
+                                                               compare_mdl);
+       } else if (mdl->state == MDL_CONNECTED) {
+               /* MCAP specification says that we should close the MCL if
+                * it is open when we receive a MD_CREATE_MDL_REQ */
+               shutdown_mdl(mdl);
+       }
+
+       mdl->mdep_id = mdep_id;
+       mdl->state = MDL_WAITING;
+
+       mcl->state = MCL_PENDING;
+       mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_SUCCESS, mdl_id,
+                                                               &conf, 1);
+}
+
+static void process_md_reconnect_mdl_req(struct mcap_mcl *mcl, void *cmd,
+                                                               uint32_t len)
+{
+       mcap_md_req *req;
+       struct mcap_mdl *mdl;
+       uint16_t mdl_id;
+       uint8_t rsp;
+
+       if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
+                                               MCAP_MD_RECONNECT_MDL_RSP))
+               return;
+
+       req = cmd;
+       mdl_id = ntohs(req->mdl);
+
+       mdl = get_mdl(mcl, mdl_id);
+       if (!mdl) {
+               mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_INVALID_MDL,
+                                                       mdl_id, NULL, 0);
+               return;
+       } else if (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING ) {
+               /* Creation request arrives for a MDL that is being managed
+               * at current moment */
+               mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_MDL_BUSY,
+                                                       mdl_id, NULL, 0);
+               return;
+       }
+
+       /* Callback to upper layer */
+       rsp = mcl->cb->mdl_reconn_req(mdl, mcl->cb->user_data);
+       if (mcl->state == MCL_IDLE)
+               return;
+
+       if (rsp != MCAP_SUCCESS) {
+               mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, rsp, mdl_id,
+                                                               NULL, 0);
+               return;
+       }
+
+       if (mdl->state == MDL_CONNECTED)
+               shutdown_mdl(mdl);
+
+       mdl->state = MDL_WAITING;
+       mcl->state = MCL_PENDING;
+       mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_SUCCESS, mdl_id,
+                                                               NULL, 0);
+}
+
+static void process_md_abort_mdl_req(struct mcap_mcl *mcl, void *cmd,
+                                                               uint32_t len)
+{
+       mcap_md_req *req;
+       GSList *l;
+       struct mcap_mdl *mdl, *abrt;
+       uint16_t mdl_id;
+
+       if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
+                                                       MCAP_MD_ABORT_MDL_RSP))
+               return;
+
+       req = cmd;
+       mdl_id = ntohs(req->mdl);
+       mcl->state = MCL_CONNECTED;
+       for (l = mcl->mdls; l; l = l->next) {
+               mdl = l->data;
+               if (mdl_id == mdl->mdlid && mdl->state == MDL_WAITING) {
+                       abrt = mdl;
+                       if (mcl->state != MCL_CONNECTED)
+                               break;
+                       continue;
+               }
+               if (mdl->state == MDL_CONNECTED && mcl->state != MCL_ACTIVE)
+                       mcl->state = MCL_ACTIVE;
+
+               if (abrt && mcl->state == MCL_ACTIVE)
+                       break;
+       }
+
+       if (!abrt) {
+               mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_INVALID_MDL,
+                                                       mdl_id, NULL, 0);
+               return;
+       }
+
+       mcl->cb->mdl_aborted(abrt, mcl->cb->user_data);
+       abrt->state = MDL_CLOSED;
+       mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_SUCCESS, mdl_id,
+                                                               NULL, 0);
+}
+
+static void process_md_delete_mdl_req(struct mcap_mcl *mcl, void *cmd,
+                                                               uint32_t len)
+{
+       mcap_md_req *req;
+       struct mcap_mdl *mdl, *aux;
+       uint16_t mdlid;
+       gboolean notify;
+       GSList *l;
+
+       if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
+                                                       MCAP_MD_DELETE_MDL_RSP))
+               return;
+
+       req = cmd;
+       mdlid = ntohs(req->mdl);
+       if (mdlid == MCAP_ALL_MDLIDS) {
+               notify = FALSE;
+               g_slist_foreach(mcl->mdls, mcap_del_mdl, &notify);
+               g_slist_free(mcl->mdls);
+               mcl->mdls = NULL;
+               mcl->state = MCL_CONNECTED;
+               /* NULL mdl means ALL_MDLS */
+               mcl->cb->mdl_deleted(NULL, mcl->cb->user_data);
+               goto resp;
+       }
+
+       if (mdlid < MCAP_MDLID_INITIAL || mdlid > MCAP_MDLID_FINAL) {
+               mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL,
+                                                               mdlid, NULL, 0);
+               return;
+       }
+
+       for (l = mcl->mdls, mdl = NULL; l; l = l->next) {
+               aux = l->data;
+               if (aux->mdlid == mdlid) {
+                       mdl = aux;
+                       break;
+               }
+       }
+
+       if (!mdl || mdl->state == MDL_WAITING) {
+               mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL,
+                                                               mdlid, NULL, 0);
+               return;
+       }
+
+       mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+       update_mcl_state(mcl);
+       notify = TRUE;
+       mcap_del_mdl(mdl, &notify);
+
+resp:
+       mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_SUCCESS, mdlid,
+                                                               NULL, 0);
+}
+
+static void invalid_req_state(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       uint16_t mdlr;
+
+       error("Invalid cmd received (op code = %d) in state %d", cmd[0],
+                                                               mcl->state);
+       /* Get previously mdlid sent to generate an appropriate
+        * response if it is possible */
+       mdlr = len < sizeof(mcap_md_req) ? MCAP_MDLID_RESERVED :
+                                       ntohs(((mcap_md_req *) cmd)->mdl);
+       mcap_send_cmd(mcl, cmd[0]+1, MCAP_INVALID_OPERATION, mdlr, NULL, 0);
+}
+
+/* Function used to process commands depending of MCL state */
+static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       switch (cmd[0]) {
+       case MCAP_MD_CREATE_MDL_REQ:
+               process_md_create_mdl_req(mcl, cmd, len);
+               break;
+       case MCAP_MD_RECONNECT_MDL_REQ:
+               process_md_reconnect_mdl_req(mcl, cmd, len);
+               break;
+       case MCAP_MD_DELETE_MDL_REQ:
+               process_md_delete_mdl_req(mcl, cmd, len);
+               break;
+       default:
+               invalid_req_state(mcl, cmd, len);
+       }
+}
+
+static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       if (cmd[0] == MCAP_MD_ABORT_MDL_REQ)
+               process_md_abort_mdl_req(mcl, cmd, len);
+       else
+               invalid_req_state(mcl, cmd, len);
+}
+
+static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       switch (cmd[0]) {
+       case MCAP_MD_CREATE_MDL_REQ:
+               process_md_create_mdl_req(mcl, cmd, len);
+               break;
+       case MCAP_MD_RECONNECT_MDL_REQ:
+               process_md_reconnect_mdl_req(mcl, cmd, len);
+               break;
+       case MCAP_MD_DELETE_MDL_REQ:
+               process_md_delete_mdl_req(mcl, cmd, len);
+               break;
+       default:
+               invalid_req_state(mcl, cmd, len);
+       }
+}
+
+/* Function used to process replies */
+static gboolean check_err_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp,
+                               uint32_t rlen, uint32_t len, GError **gerr)
+{
+       mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd;
+       gint err = MCAP_ERROR_FAILED;
+       gboolean close = FALSE;
+       char *msg;
+
+       if (rsp->op == MCAP_ERROR_RSP) {
+               msg = "MCAP_ERROR_RSP received";
+               close = FALSE;
+               goto fail;
+       }
+
+       /* Check if the response matches with the last request */
+       if (rlen < sizeof(mcap_rsp) || (mcl->lcmd[0] + 1) != rsp->op) {
+               msg = "Protocol error";
+               close = FALSE;
+               goto fail;
+       }
+
+       if (rlen < len) {
+               msg = "Protocol error";
+               close = FALSE;
+               goto fail;
+       }
+
+       if (rsp->mdl != cmdlast->mdl) {
+               msg = "MDLID received doesn't match with MDLID sent";
+               close = TRUE;
+               goto fail;
+       }
+
+       if (rsp->rc == MCAP_REQUEST_NOT_SUPPORTED) {
+               msg = "Remote does not support opcodes";
+               mcl->ctrl &= ~MCAP_CTRL_STD_OP;
+               goto fail;
+       }
+
+       if (rsp->rc == MCAP_UNSPECIFIED_ERROR) {
+               msg = "Unspecified error";
+               close = TRUE;
+               goto fail;
+       }
+
+       if (rsp->rc != MCAP_SUCCESS) {
+               msg = error2str(rsp->rc);
+               err = rsp->rc;
+               goto fail;
+       }
+
+       return FALSE;
+
+fail:
+       g_set_error(gerr, MCAP_ERROR, err, "%s", msg);
+       return close;
+}
+
+static gboolean process_md_create_mdl_rsp(struct mcap_mcl *mcl,
+                                               mcap_rsp *rsp, uint32_t len)
+{
+       mcap_md_create_mdl_req *cmdlast = (mcap_md_create_mdl_req *) mcl->lcmd;
+       struct mcap_mdl_op_cb *conn = mcl->priv_data;
+       mcap_mdl_operation_conf_cb connect_cb = conn->cb.op_conf;
+       gpointer user_data = conn->user_data;
+       struct mcap_mdl *mdl = conn->mdl;
+       uint8_t conf = cmdlast->conf;
+       gboolean close;
+       GError *gerr = NULL;
+
+       close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp) + 1, &gerr);
+       g_free(mcl->lcmd);
+       mcl->lcmd = NULL;
+       mcl->req = MCL_AVAILABLE;
+
+       if (gerr)
+               goto fail;
+
+       /* Check if preferences changed */
+       if (conf != 0x00 && rsp->data[0] != conf) {
+               g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED,
+                                               "Configuration changed");
+               close = TRUE;
+               goto fail;
+       }
+
+       connect_cb(mdl, rsp->data[0], gerr, user_data);
+       return close;
+
+fail:
+       connect_cb(NULL, 0, gerr, user_data);
+       mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+       mcap_mdl_unref(mdl);
+       g_error_free(gerr);
+       update_mcl_state(mcl);
+       return close;
+}
+
+static gboolean process_md_reconnect_mdl_rsp(struct mcap_mcl *mcl,
+                                               mcap_rsp *rsp, uint32_t len)
+{
+       struct mcap_mdl_op_cb *reconn = mcl->priv_data;
+       mcap_mdl_operation_cb reconn_cb = reconn->cb.op;
+       gpointer user_data = reconn->user_data;
+       struct mcap_mdl *mdl = reconn->mdl;
+       GError *gerr = NULL;
+       gboolean close;
+
+       close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
+
+       g_free(mcl->lcmd);
+       mcl->lcmd = NULL;
+       mcl->req = MCL_AVAILABLE;
+
+       reconn_cb(mdl, gerr, user_data);
+       if (!gerr)
+               return close;
+
+       g_error_free(gerr);
+       shutdown_mdl(mdl);
+       update_mcl_state(mcl);
+
+       if (rsp->rc != MCAP_INVALID_MDL)
+               return close;
+
+       /* Remove cached mdlid */
+       mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+       mcl->cb->mdl_deleted(mdl, mcl->cb->user_data);
+       mcap_mdl_unref(mdl);
+
+       return close;
+}
+
+static gboolean process_md_abort_mdl_rsp(struct mcap_mcl *mcl,
+                                               mcap_rsp *rsp, uint32_t len)
+{
+       struct mcap_mdl_op_cb *abrt = mcl->priv_data;
+       mcap_mdl_notify_cb abrt_cb = abrt->cb.notify;
+       gpointer user_data = abrt->user_data;
+       struct mcap_mdl *mdl = abrt->mdl;
+       GError *gerr = NULL;
+       gboolean close;
+
+       close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
+
+       g_free(mcl->lcmd);
+       mcl->lcmd = NULL;
+       mcl->req = MCL_AVAILABLE;
+
+       abrt_cb(gerr, user_data);
+       shutdown_mdl(mdl);
+
+       if (len >= sizeof(mcap_rsp) && rsp->rc == MCAP_INVALID_MDL) {
+               mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+               mcl->cb->mdl_deleted(mdl, mcl->cb->user_data);
+               mcap_mdl_unref(mdl);
+       }
+
+       if (gerr)
+               g_error_free(gerr);
+
+       update_mcl_state(mcl);
+
+       return close;
+}
+
+static void restore_mdl(gpointer elem, gpointer data)
+{
+       struct mcap_mdl *mdl = elem;
+
+       if (mdl->state == MDL_DELETING) {
+               if (mdl->dc)
+                       mdl->state = MDL_CONNECTED;
+               else
+                       mdl->state = MDL_CLOSED;
+       } else if (mdl->state == MDL_CLOSED)
+               mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data);
+}
+
+static void check_mdl_del_err(struct mcap_mdl *mdl, mcap_rsp *rsp)
+{
+       if (rsp->rc != MCAP_ERROR_INVALID_MDL) {
+               restore_mdl(mdl, NULL);
+               return;
+       }
+
+       /* MDL does not exist in remote side, we can delete it */
+       mdl->mcl->mdls = g_slist_remove(mdl->mcl->mdls, mdl);
+       mcap_mdl_unref(mdl);
+}
+
+static gboolean process_md_delete_mdl_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp,
+                                                               uint32_t len)
+{
+       struct mcap_mdl_op_cb *del = mcl->priv_data;
+       struct mcap_mdl *mdl = del->mdl;
+       mcap_mdl_notify_cb deleted_cb = del->cb.notify;
+       gpointer user_data = del->user_data;
+       mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd;
+       uint16_t mdlid = ntohs(cmdlast->mdl);
+       GError *gerr = NULL;
+       gboolean close;
+       gboolean notify = FALSE;
+
+       close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
+
+       g_free(mcl->lcmd);
+       mcl->lcmd = NULL;
+       mcl->req = MCL_AVAILABLE;
+
+       if (gerr) {
+               if (mdl)
+                       check_mdl_del_err(mdl, rsp);
+               else
+                       g_slist_foreach(mcl->mdls, restore_mdl, NULL);
+               deleted_cb(gerr, user_data);
+               g_error_free(gerr);
+               return close;
+       }
+
+       if (mdlid == MCAP_ALL_MDLIDS) {
+               g_slist_foreach(mcl->mdls, mcap_del_mdl, &notify);
+               g_slist_free(mcl->mdls);
+               mcl->mdls = NULL;
+               mcl->state = MCL_CONNECTED;
+       } else {
+               mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+               update_mcl_state(mcl);
+               mcap_del_mdl(mdl, &notify);
+       }
+
+       deleted_cb(gerr, user_data);
+
+       return close;
+}
+
+static void post_process_rsp(struct mcap_mcl *mcl, struct mcap_mdl_op_cb *op)
+{
+       if (mcl->priv_data != op) {
+               /* Queued MCAP request in some callback. */
+               /* We should not delete the mcl private data */
+               free_mcap_mdl_op(op);
+       } else {
+               /* This is not a queued request. It's safe */
+               /* delete the mcl private data here. */
+               free_mcl_priv_data(mcl);
+       }
+}
+
+static void proc_response(struct mcap_mcl *mcl, void *buf, uint32_t len)
+{
+       struct mcap_mdl_op_cb *op = mcl->priv_data;
+       mcap_rsp *rsp = buf;
+       gboolean close;
+
+       RELEASE_TIMER(mcl);
+
+       switch (mcl->lcmd[0] + 1) {
+       case MCAP_MD_CREATE_MDL_RSP:
+               close = process_md_create_mdl_rsp(mcl, rsp, len);
+               post_process_rsp(mcl, op);
+               break;
+       case MCAP_MD_RECONNECT_MDL_RSP:
+               close = process_md_reconnect_mdl_rsp(mcl, rsp, len);
+               post_process_rsp(mcl, op);
+               break;
+       case MCAP_MD_ABORT_MDL_RSP:
+               close = process_md_abort_mdl_rsp(mcl, rsp, len);
+               post_process_rsp(mcl, op);
+               break;
+       case MCAP_MD_DELETE_MDL_RSP:
+               close = process_md_delete_mdl_rsp(mcl, rsp, len);
+               post_process_rsp(mcl, op);
+               break;
+       default:
+               DBG("Unknown cmd response received (op code = %d)", rsp->op);
+               close = TRUE;
+               break;
+       }
+
+       if (close) {
+               mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
+               mcap_cache_mcl(mcl);
+       }
+}
+
+static void proc_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       GError *gerr = NULL;
+
+       if (cmd[0] > MCAP_MD_SYNC_INFO_IND ||
+                                       (cmd[0] > MCAP_MD_DELETE_MDL_RSP &&
+                                       cmd[0] < MCAP_MD_SYNC_CAP_REQ)) {
+               error("Unknown cmd received (op code = %d)", cmd[0]);
+               mcap_send_cmd(mcl, MCAP_ERROR_RSP, MCAP_INVALID_OP_CODE,
+                                               MCAP_MDLID_RESERVED, NULL, 0);
+               return;
+       }
+
+       if (cmd[0] >= MCAP_MD_SYNC_CAP_REQ &&
+                                       cmd[0] <= MCAP_MD_SYNC_INFO_IND) {
+               proc_sync_cmd(mcl, cmd, len);
+               return;
+       }
+
+       if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) {
+               /* In case the remote device doesn't work correctly */
+               error("Remote device does not support opcodes, cmd ignored");
+               return;
+       }
+
+       if (mcl->req == MCL_WAITING_RSP) {
+               if (cmd[0] & 0x01) {
+                       /* Request arrived when a response is expected */
+                       if (mcl->role == MCL_INITIATOR)
+                               /* ignore */
+                               return;
+                       /* Initiator will ignore our last request */
+                       RELEASE_TIMER(mcl);
+                       mcl->req = MCL_AVAILABLE;
+                       g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_REQ_IGNORED,
+                               "Initiator sent a request with more priority");
+                       mcap_notify_error(mcl, gerr);
+                       proc_req[mcl->state](mcl, cmd, len);
+                       return;
+               }
+               proc_response(mcl, cmd, len);
+       } else if (cmd[0] & 0x01)
+               proc_req[mcl->state](mcl, cmd, len);
+}
+
+static gboolean mdl_event_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+
+       struct mcap_mdl *mdl = data;
+       gboolean notify;
+
+       DBG("Close MDL %d", mdl->mdlid);
+
+       notify = (mdl->state == MDL_CONNECTED);
+       shutdown_mdl(mdl);
+
+       update_mcl_state(mdl->mcl);
+
+       if (notify) {
+               /*Callback to upper layer */
+               mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data);
+       }
+
+       return FALSE;
+}
+
+static void mcap_connect_mdl_cb(GIOChannel *chan, GError *conn_err,
+                                                               gpointer data)
+{
+       struct mcap_mdl_op_cb *con = data;
+       struct mcap_mdl *mdl = con->mdl;
+       mcap_mdl_operation_cb cb = con->cb.op;
+       gpointer user_data = con->user_data;
+
+       DBG("mdl connect callback");
+
+       if (conn_err) {
+               DBG("ERROR: mdl connect callback");
+               mdl->state = MDL_CLOSED;
+               g_io_channel_unref(mdl->dc);
+               mdl->dc = NULL;
+               cb(mdl, conn_err, user_data);
+               return;
+       }
+
+       mdl->state = MDL_CONNECTED;
+       mdl->wid = g_io_add_watch_full(mdl->dc, G_PRIORITY_DEFAULT,
+                                       G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                       (GIOFunc) mdl_event_cb,
+                                       mcap_mdl_ref(mdl),
+                                       (GDestroyNotify) mcap_mdl_unref);
+
+       cb(mdl, conn_err, user_data);
+}
+
+gboolean mcap_connect_mdl(struct mcap_mdl *mdl, uint8_t mode,
+                                       uint16_t dcpsm,
+                                       mcap_mdl_operation_cb connect_cb,
+                                       gpointer user_data,
+                                       GDestroyNotify destroy,
+                                       GError **err)
+{
+       struct mcap_mdl_op_cb *con;
+
+       if (mdl->state != MDL_WAITING) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL,
+                                       "%s", error2str(MCAP_INVALID_MDL));
+               return FALSE;
+       }
+
+       if ((mode != L2CAP_MODE_ERTM) && (mode != L2CAP_MODE_STREAMING)) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+                                               "Invalid MDL configuration");
+               return FALSE;
+       }
+
+       con = g_new0(struct mcap_mdl_op_cb, 1);
+       con->mdl = mcap_mdl_ref(mdl);
+       con->cb.op = connect_cb;
+       con->destroy = destroy;
+       con->user_data = user_data;
+
+       mdl->dc = bt_io_connect(BT_IO_L2CAP, mcap_connect_mdl_cb, con,
+                               (GDestroyNotify) free_mcap_mdl_op, err,
+                               BT_IO_OPT_SOURCE_BDADDR, &mdl->mcl->mi->src,
+                               BT_IO_OPT_DEST_BDADDR, &mdl->mcl->addr,
+                               BT_IO_OPT_PSM, dcpsm,
+                               BT_IO_OPT_MTU, MCAP_DC_MTU,
+                               BT_IO_OPT_SEC_LEVEL, mdl->mcl->mi->sec,
+                               BT_IO_OPT_MODE, mode,
+                               BT_IO_OPT_INVALID);
+       if (!mdl->dc) {
+               DBG("MDL Connection error");
+               mdl->state = MDL_CLOSED;
+               mcap_mdl_unref(con->mdl);
+               g_free(con);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean mcl_control_cb(GIOChannel *chan, GIOCondition cond,
+                                                               gpointer data)
+{
+       GError *gerr = NULL;
+       struct mcap_mcl *mcl = data;
+       int sk, len;
+       uint8_t buf[MCAP_CC_MTU];
+
+       if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+               goto fail;
+
+       sk = g_io_channel_unix_get_fd(chan);
+       len = read(sk, buf, sizeof(buf));
+       if (len < 0)
+               goto fail;
+
+       proc_cmd(mcl, buf, (uint32_t) len);
+       return TRUE;
+
+fail:
+       if (mcl->state != MCL_IDLE) {
+               if (mcl->req == MCL_WAITING_RSP) {
+                       /* notify error in pending callback */
+                       g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_MCL_CLOSED,
+                                                               "MCL closed");
+                       mcap_notify_error(mcl, gerr);
+                       g_error_free(gerr);
+               }
+               mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
+       }
+       mcap_cache_mcl(mcl);
+       return FALSE;
+}
+
+static void mcap_connect_mcl_cb(GIOChannel *chan, GError *conn_err,
+                                                       gpointer user_data)
+{
+       char dstaddr[18];
+       struct connect_mcl *con = user_data;
+       struct mcap_mcl *aux, *mcl = con->mcl;
+       mcap_mcl_connect_cb connect_cb = con->connect_cb;
+       gpointer data = con->user_data;
+       GError *gerr = NULL;
+
+       mcl->ctrl &= ~MCAP_CTRL_CONN;
+
+       if (conn_err) {
+               if (mcl->ctrl & MCAP_CTRL_FREE) {
+                       mcap_mcl_release(mcl);
+                       mcl->mi->mcl_uncached_cb(mcl, mcl->mi->user_data);
+               }
+               connect_cb(NULL, conn_err, data);
+               return;
+       }
+
+       ba2str(&mcl->addr, dstaddr);
+
+       aux = find_mcl(mcl->mi->mcls, &mcl->addr);
+       if (aux) {
+               /* Double MCL connection case */
+               error("MCL error: Device %s is already connected", dstaddr);
+               g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
+                                       "MCL %s is already connected", dstaddr);
+               connect_cb(NULL, gerr, data);
+               g_error_free(gerr);
+               return;
+       }
+
+       mcl->state = MCL_CONNECTED;
+       mcl->role = MCL_INITIATOR;
+       mcl->req = MCL_AVAILABLE;
+       mcl->ctrl |= MCAP_CTRL_STD_OP;
+
+       mcap_sync_init(mcl);
+
+       if (mcl->ctrl & MCAP_CTRL_CACHED)
+               mcap_uncache_mcl(mcl);
+       else {
+               mcl->ctrl &= ~MCAP_CTRL_FREE;
+               mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls,
+                                                       mcap_mcl_ref(mcl));
+       }
+
+       mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT,
+                               G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                               (GIOFunc) mcl_control_cb,
+                               mcap_mcl_ref(mcl),
+                               (GDestroyNotify) mcap_mcl_unref);
+       connect_cb(mcl, gerr, data);
+}
+
+static void set_mdl_properties(GIOChannel *chan, struct mcap_mdl *mdl)
+{
+       struct mcap_mcl *mcl = mdl->mcl;
+
+       mdl->state = MDL_CONNECTED;
+       mdl->dc = g_io_channel_ref(chan);
+       mdl->wid = g_io_add_watch_full(mdl->dc, G_PRIORITY_DEFAULT,
+                                       G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                       (GIOFunc) mdl_event_cb,
+                                       mcap_mdl_ref(mdl),
+                                       (GDestroyNotify) mcap_mdl_unref);
+
+       mcl->state = MCL_ACTIVE;
+       mcl->cb->mdl_connected(mdl, mcl->cb->user_data);
+}
+
+static void mcl_io_destroy(gpointer data)
+{
+       struct connect_mcl *con = data;
+
+       mcap_mcl_unref(con->mcl);
+       if (con->destroy)
+               con->destroy(con->user_data);
+       g_free(con);
+}
+
+gboolean mcap_create_mcl(struct mcap_instance *mi,
+                               const bdaddr_t *addr,
+                               uint16_t ccpsm,
+                               mcap_mcl_connect_cb connect_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err)
+{
+       struct mcap_mcl *mcl;
+       struct connect_mcl *con;
+
+       mcl = find_mcl(mi->mcls, addr);
+       if (mcl) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
+                                       "MCL is already connected.");
+               return FALSE;
+       }
+
+       mcl = find_mcl(mi->cached, addr);
+       if (!mcl) {
+               mcl = g_new0(struct mcap_mcl, 1);
+               mcl->mi = mcap_instance_ref(mi);
+               mcl->state = MCL_IDLE;
+               bacpy(&mcl->addr, addr);
+               set_default_cb(mcl);
+               mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1;
+       }
+
+       mcl->ctrl |= MCAP_CTRL_CONN;
+
+       con = g_new0(struct connect_mcl, 1);
+       con->mcl = mcap_mcl_ref(mcl);
+       con->connect_cb = connect_cb;
+       con->destroy = destroy;
+       con->user_data = user_data;
+
+       mcl->cc = bt_io_connect(BT_IO_L2CAP, mcap_connect_mcl_cb, con,
+                               mcl_io_destroy, err,
+                               BT_IO_OPT_SOURCE_BDADDR, &mi->src,
+                               BT_IO_OPT_DEST_BDADDR, addr,
+                               BT_IO_OPT_PSM, ccpsm,
+                               BT_IO_OPT_MTU, MCAP_CC_MTU,
+                               BT_IO_OPT_SEC_LEVEL, mi->sec,
+                               BT_IO_OPT_MODE, L2CAP_MODE_ERTM,
+                               BT_IO_OPT_INVALID);
+       if (!mcl->cc) {
+               mcl->ctrl &= ~MCAP_CTRL_CONN;
+               if (mcl->ctrl & MCAP_CTRL_FREE) {
+                       mcap_mcl_release(mcl);
+                       mcl->mi->mcl_uncached_cb(mcl, mcl->mi->user_data);
+               }
+               mcap_mcl_unref(con->mcl);
+               g_free(con);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void connect_dc_event_cb(GIOChannel *chan, GError *gerr,
+                                                       gpointer user_data)
+{
+       struct mcap_instance *mi = user_data;
+       struct mcap_mcl *mcl;
+       struct mcap_mdl *mdl;
+       GError *err = NULL;
+       bdaddr_t dst;
+       GSList *l;
+
+       if (gerr)
+               return;
+
+       bt_io_get(chan, BT_IO_L2CAP, &err,
+                       BT_IO_OPT_DEST_BDADDR, &dst,
+                       BT_IO_OPT_INVALID);
+       if (err) {
+               error("%s", err->message);
+               g_error_free(err);
+               goto drop;
+       }
+
+       mcl = find_mcl(mi->mcls, &dst);
+       if (!mcl || mcl->state != MCL_PENDING)
+               goto drop;
+
+       for (l = mcl->mdls; l; l = l->next) {
+               mdl = l->data;
+               if (mdl->state == MDL_WAITING) {
+                       set_mdl_properties(chan, mdl);
+                       return;
+               }
+       }
+
+drop:
+       g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static void set_mcl_conf(GIOChannel *chan, struct mcap_mcl *mcl)
+{
+       gboolean reconn;
+
+       mcl->state = MCL_CONNECTED;
+       mcl->role = MCL_ACCEPTOR;
+       mcl->req = MCL_AVAILABLE;
+       mcl->cc = g_io_channel_ref(chan);
+       mcl->ctrl |= MCAP_CTRL_STD_OP;
+
+       mcap_sync_init(mcl);
+
+       reconn = (mcl->ctrl & MCAP_CTRL_CACHED);
+       if (reconn)
+               mcap_uncache_mcl(mcl);
+       else
+               mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls,
+                                                       mcap_mcl_ref(mcl));
+
+       mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT,
+                               G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                               (GIOFunc) mcl_control_cb,
+                               mcap_mcl_ref(mcl),
+                               (GDestroyNotify) mcap_mcl_unref);
+
+       /* Callback to report new MCL */
+       if (reconn)
+               mcl->mi->mcl_reconnected_cb(mcl, mcl->mi->user_data);
+       else
+               mcl->mi->mcl_connected_cb(mcl, mcl->mi->user_data);
+}
+
+static void connect_mcl_event_cb(GIOChannel *chan, GError *gerr,
+                                                       gpointer user_data)
+{
+       struct mcap_instance *mi = user_data;
+       struct mcap_mcl *mcl;
+       bdaddr_t dst;
+       char address[18], srcstr[18];
+       GError *err = NULL;
+
+       if (gerr)
+               return;
+
+       bt_io_get(chan, BT_IO_L2CAP, &err,
+                       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;
+       }
+
+       ba2str(&mi->src, srcstr);
+       mcl = find_mcl(mi->mcls, &dst);
+       if (mcl) {
+               error("Control channel already created with %s on adapter %s",
+                               address, srcstr);
+               goto drop;
+       }
+
+       mcl = find_mcl(mi->cached, &dst);
+       if (!mcl) {
+               mcl = g_new0(struct mcap_mcl, 1);
+               mcl->mi = mcap_instance_ref(mi);
+               bacpy(&mcl->addr, &dst);
+               set_default_cb(mcl);
+               mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1;
+       }
+
+       set_mcl_conf(chan, mcl);
+
+       return;
+drop:
+       g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+struct mcap_instance *mcap_create_instance(bdaddr_t *src,
+                                       BtIOSecLevel sec,
+                                       uint16_t ccpsm,
+                                       uint16_t dcpsm,
+                                       mcap_mcl_event_cb mcl_connected,
+                                       mcap_mcl_event_cb mcl_reconnected,
+                                       mcap_mcl_event_cb mcl_disconnected,
+                                       mcap_mcl_event_cb mcl_uncached,
+                                       mcap_info_ind_event_cb mcl_sync_info_ind,
+                                       gpointer user_data,
+                                       GError **gerr)
+{
+       struct mcap_instance *mi;
+
+       if (sec < BT_IO_SEC_MEDIUM) {
+               g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+                               "Security level can't be minor of %d",
+                               BT_IO_SEC_MEDIUM);
+               return NULL;
+       }
+
+       if (!(mcl_connected && mcl_reconnected &&
+                       mcl_disconnected && mcl_uncached)) {
+               g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+                               "The callbacks can't be null");
+               return NULL;
+       }
+
+       mi = g_new0(struct mcap_instance, 1);
+
+       bacpy(&mi->src, src);
+
+       mi->sec = sec;
+       mi->mcl_connected_cb = mcl_connected;
+       mi->mcl_reconnected_cb = mcl_reconnected;
+       mi->mcl_disconnected_cb = mcl_disconnected;
+       mi->mcl_uncached_cb = mcl_uncached;
+       mi->mcl_sync_infoind_cb = mcl_sync_info_ind;
+       mi->user_data = user_data;
+       mi->csp_enabled = FALSE;
+
+       /* Listen incoming connections in control channel */
+       mi->ccio = bt_io_listen(BT_IO_L2CAP, connect_mcl_event_cb, NULL, mi,
+                               NULL, gerr,
+                               BT_IO_OPT_SOURCE_BDADDR, &mi->src,
+                               BT_IO_OPT_PSM, ccpsm,
+                               BT_IO_OPT_MTU, MCAP_CC_MTU,
+                               BT_IO_OPT_SEC_LEVEL, sec,
+                               BT_IO_OPT_MODE, L2CAP_MODE_ERTM,
+                               BT_IO_OPT_INVALID);
+       if (!mi->ccio) {
+               error("%s", (*gerr)->message);
+               g_free(mi);
+               return NULL;
+       }
+
+       /* Listen incoming connections in data channels */
+       mi->dcio = bt_io_listen(BT_IO_L2CAP, connect_dc_event_cb, NULL, mi,
+                               NULL, gerr,
+                               BT_IO_OPT_SOURCE_BDADDR, &mi->src,
+                               BT_IO_OPT_PSM, dcpsm,
+                               BT_IO_OPT_MTU, MCAP_DC_MTU,
+                               BT_IO_OPT_SEC_LEVEL, sec,
+                               BT_IO_OPT_INVALID);
+       if (!mi->dcio) {
+               g_io_channel_shutdown(mi->ccio, TRUE, NULL);
+               g_io_channel_unref(mi->ccio);
+               mi->ccio = NULL;
+               error("%s", (*gerr)->message);
+               g_free(mi);
+               return NULL;
+       }
+
+       /* Initialize random seed to generate mdlids for this instance */
+       srand(time(NULL));
+
+       return mcap_instance_ref(mi);;
+}
+
+void mcap_release_instance(struct mcap_instance *mi)
+{
+       GSList *l;
+
+       if (!mi)
+               return;
+
+       if (mi->ccio) {
+               g_io_channel_shutdown(mi->ccio, TRUE, NULL);
+               g_io_channel_unref(mi->ccio);
+               mi->ccio = NULL;
+       }
+
+       if (mi->dcio) {
+               g_io_channel_shutdown(mi->dcio, TRUE, NULL);
+               g_io_channel_unref(mi->dcio);
+               mi->dcio = NULL;
+       }
+
+       for (l = mi->mcls; l; l = l->next) {
+               mcap_mcl_release(l->data);
+               mcap_mcl_unref(l->data);
+       }
+
+       g_slist_free(mi->mcls);
+       mi->mcls = NULL;
+
+       for (l = mi->cached; l; l = l->next) {
+               mcap_mcl_release(l->data);
+               mcap_mcl_unref(l->data);
+       }
+
+       g_slist_free(mi->cached);
+       mi->cached = NULL;
+}
+
+struct mcap_instance *mcap_instance_ref(struct mcap_instance *mi)
+{
+       mi->ref++;
+
+       DBG("mcap_instance_ref(%p): ref=%d", mi, mi->ref);
+
+       return mi;
+}
+
+void mcap_instance_unref(struct mcap_instance *mi)
+{
+       mi->ref--;
+
+       DBG("mcap_instance_unref(%p): ref=%d", mi, mi->ref);
+
+       if (mi->ref > 0)
+               return;
+
+       mcap_release_instance(mi);
+       g_free(mi);
+}
+
+uint16_t mcap_get_ctrl_psm(struct mcap_instance *mi, GError **err)
+{
+       uint16_t lpsm;
+
+       if (!(mi && mi->ccio)) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+                       "Invalid MCAP instance");
+               return 0;
+       }
+
+       if (!bt_io_get(mi->ccio, BT_IO_L2CAP, err,
+                       BT_IO_OPT_PSM, &lpsm,
+                       BT_IO_OPT_INVALID))
+               return 0;
+
+       return lpsm;
+}
+
+uint16_t mcap_get_data_psm(struct mcap_instance *mi, GError **err)
+{
+       uint16_t lpsm;
+
+       if (!(mi && mi->dcio)) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+                       "Invalid MCAP instance");
+               return 0;
+       }
+
+       if (!bt_io_get(mi->dcio, BT_IO_L2CAP, err,
+                       BT_IO_OPT_PSM, &lpsm,
+                       BT_IO_OPT_INVALID))
+               return 0;
+
+       return lpsm;
+}
+
+gboolean mcap_set_data_chan_mode(struct mcap_instance *mi, uint8_t mode,
+                                                               GError **err)
+{
+       if (!(mi && mi->dcio)) {
+               g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+                                               "Invalid MCAP instance");
+               return FALSE;
+       }
+
+       return bt_io_set(mi->dcio, BT_IO_L2CAP, err, BT_IO_OPT_MODE, mode,
+                                                       BT_IO_OPT_INVALID);
+}
+
+struct mcap_mdl *mcap_mdl_ref(struct mcap_mdl *mdl)
+{
+       mdl->ref++;
+
+       DBG("mcap_mdl_ref(%p): ref=%d", mdl, mdl->ref);
+
+       return mdl;
+}
+
+void mcap_mdl_unref(struct mcap_mdl *mdl)
+{
+       mdl->ref--;
+
+       DBG("mcap_mdl_unref(%p): ref=%d", mdl, mdl->ref);
+
+       if (mdl->ref > 0)
+               return;
+
+       free_mdl(mdl);
+}
diff --git a/health/mcap.h b/health/mcap.h
new file mode 100644 (file)
index 0000000..81f53f1
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ *
+ *  MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Copyright (C) 2010 Signove
+ *
+ *  Authors:
+ *  Santiago Carot-Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *  Elvis Pfützenreuter <epx at signove.com>
+ *
+ *  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 __MCAP_H
+#define __MCAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MCAP_VERSION   0x0100  /* current version 01.00 */
+
+/* bytes to get MCAP Supported Procedures */
+#define MCAP_SUP_PROC  0x06
+
+/* maximum transmission unit for channels */
+#define MCAP_CC_MTU    48
+#define MCAP_DC_MTU    L2CAP_DEFAULT_MTU
+
+/* MCAP Standard Op Codes */
+#define MCAP_ERROR_RSP                 0x00
+#define MCAP_MD_CREATE_MDL_REQ         0x01
+#define MCAP_MD_CREATE_MDL_RSP         0x02
+#define MCAP_MD_RECONNECT_MDL_REQ      0x03
+#define MCAP_MD_RECONNECT_MDL_RSP      0x04
+#define MCAP_MD_ABORT_MDL_REQ          0x05
+#define MCAP_MD_ABORT_MDL_RSP          0x06
+#define MCAP_MD_DELETE_MDL_REQ         0x07
+#define MCAP_MD_DELETE_MDL_RSP         0x08
+
+/* MCAP Clock Sync Op Codes */
+#define MCAP_MD_SYNC_CAP_REQ           0x11
+#define MCAP_MD_SYNC_CAP_RSP           0x12
+#define MCAP_MD_SYNC_SET_REQ           0x13
+#define MCAP_MD_SYNC_SET_RSP           0x14
+#define MCAP_MD_SYNC_INFO_IND          0x15
+
+/* MCAP Response codes */
+#define MCAP_SUCCESS                   0x00
+#define MCAP_INVALID_OP_CODE           0x01
+#define MCAP_INVALID_PARAM_VALUE       0x02
+#define MCAP_INVALID_MDEP              0x03
+#define MCAP_MDEP_BUSY                 0x04
+#define MCAP_INVALID_MDL               0x05
+#define MCAP_MDL_BUSY                  0x06
+#define MCAP_INVALID_OPERATION         0x07
+#define MCAP_RESOURCE_UNAVAILABLE      0x08
+#define MCAP_UNSPECIFIED_ERROR         0x09
+#define MCAP_REQUEST_NOT_SUPPORTED     0x0A
+#define MCAP_CONFIGURATION_REJECTED    0x0B
+
+/* MDL IDs */
+#define MCAP_MDLID_RESERVED            0x0000
+#define MCAP_MDLID_INITIAL             0x0001
+#define MCAP_MDLID_FINAL               0xFEFF
+#define MCAP_ALL_MDLIDS                        0xFFFF
+
+/* MDEP IDs */
+#define MCAP_MDEPID_INITIAL            0x00
+#define MCAP_MDEPID_FINAL              0x7F
+
+/* CSP special values */
+#define MCAP_BTCLOCK_IMMEDIATE         0xffffffffUL
+#define MCAP_TMSTAMP_DONTSET           0xffffffffffffffffULL
+#define MCAP_BTCLOCK_MAX               0x0fffffff
+#define MCAP_BTCLOCK_FIELD             (MCAP_BTCLOCK_MAX + 1)
+
+/*
+ * MCAP Request Packet Format
+ */
+
+typedef struct {
+       uint8_t         op;
+       uint16_t        mdl;
+       uint8_t         mdep;
+       uint8_t         conf;
+} __attribute__ ((packed)) mcap_md_create_mdl_req;
+
+typedef struct {
+       uint8_t         op;
+       uint16_t        mdl;
+} __attribute__ ((packed)) mcap_md_req;
+
+/*
+ * MCAP Response Packet Format
+ */
+
+typedef struct {
+       uint8_t         op;
+       uint8_t         rc;
+       uint16_t        mdl;
+       uint8_t         data[0];
+} __attribute__ ((packed)) mcap_rsp;
+
+/*
+ * MCAP Clock Synchronization Protocol
+ */
+
+typedef struct {
+       uint8_t         op;
+       uint16_t        timest;
+} __attribute__ ((packed)) mcap_md_sync_cap_req;
+
+typedef struct {
+       uint8_t         op;
+       uint8_t         rc;
+} __attribute__ ((packed)) mcap_md_sync_rsp;
+
+typedef struct {
+        uint8_t         op;
+       uint8_t         rc;
+       uint8_t         btclock;
+        uint16_t        sltime;
+       uint16_t        timestnr;
+       uint16_t        timestna;
+} __attribute__ ((packed)) mcap_md_sync_cap_rsp;
+
+typedef struct {
+       uint8_t         op;
+       uint8_t         timestui;
+       uint32_t        btclock;
+       uint64_t        timestst;
+} __attribute__ ((packed)) mcap_md_sync_set_req;
+
+typedef struct {
+       int8_t          op;
+       uint8_t         rc;
+       uint32_t        btclock;
+       uint64_t        timestst;
+       uint16_t        timestsa;
+} __attribute__ ((packed)) mcap_md_sync_set_rsp;
+
+typedef struct {
+       uint8_t         op;
+       uint32_t        btclock;
+       uint64_t        timestst;
+       uint16_t        timestsa;
+} __attribute__ ((packed)) mcap_md_sync_info_ind;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MCAP_H */
diff --git a/health/mcap_internal.h b/health/mcap_internal.h
new file mode 100644 (file)
index 0000000..f82e967
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ *
+ *  MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ *  Authors:
+ *  Santiago Carot-Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 __MCAP_INTERNAL_H
+#define __MCAP_INTERNAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+       MCL_CONNECTED,
+       MCL_PENDING,
+       MCL_ACTIVE,
+       MCL_IDLE
+} MCLState;
+
+typedef enum {
+       MCL_ACCEPTOR,
+       MCL_INITIATOR
+} MCLRole;
+
+typedef enum {
+       MCL_AVAILABLE,
+       MCL_WAITING_RSP
+} MCAPCtrl;
+
+typedef enum {
+       MDL_WAITING,
+       MDL_CONNECTED,
+       MDL_DELETING,
+       MDL_CLOSED
+} MDLState;
+
+struct mcap_mdl_cb {
+       mcap_mdl_event_cb               mdl_connected;  /* Remote device has created a MDL */
+       mcap_mdl_event_cb               mdl_closed;     /* Remote device has closed a MDL */
+       mcap_mdl_event_cb               mdl_deleted;    /* Remote device requested deleting a MDL */
+       mcap_mdl_event_cb               mdl_aborted;    /* Remote device aborted the mdl creation */
+       mcap_remote_mdl_conn_req_cb     mdl_conn_req;   /* Remote device requested creating a MDL */
+       mcap_remote_mdl_reconn_req_cb   mdl_reconn_req; /* Remote device requested reconnecting a MDL */
+       gpointer                        user_data;      /* User data */
+};
+
+struct mcap_instance {
+       bdaddr_t                src;                    /* Source address */
+       GIOChannel              *ccio;                  /* Control Channel IO */
+       GIOChannel              *dcio;                  /* Data Channel IO */
+       GSList                  *mcls;                  /* MCAP instance list */
+       GSList                  *cached;                /* List with all cached MCLs (MAX_CACHED macro) */
+       BtIOSecLevel            sec;                    /* Security level */
+       mcap_mcl_event_cb       mcl_connected_cb;       /* New MCL connected */
+       mcap_mcl_event_cb       mcl_reconnected_cb;     /* Old MCL has been reconnected */
+       mcap_mcl_event_cb       mcl_disconnected_cb;    /* MCL disconnected */
+       mcap_mcl_event_cb       mcl_uncached_cb;        /* MCL has been removed from MCAP cache */
+       mcap_info_ind_event_cb  mcl_sync_infoind_cb;    /* (CSP Master) Received info indication */
+       gpointer                user_data;              /* Data to be provided in callbacks */
+       gint                    ref;                    /* Reference counter */
+
+       gboolean                csp_enabled;            /* CSP: functionality enabled */
+};
+
+struct mcap_csp;
+struct mcap_mdl_op_cb;
+
+struct mcap_mcl {
+       struct mcap_instance    *mi;            /* MCAP instance where this MCL belongs */
+       bdaddr_t                addr;           /* Device address */
+       GIOChannel              *cc;            /* MCAP Control Channel IO */
+       guint                   wid;            /* MCL Watcher id */
+       GSList                  *mdls;          /* List of Data Channels shorted by mdlid */
+       MCLState                state;          /* Current MCL State */
+       MCLRole                 role;           /* Initiator or acceptor of this MCL */
+       MCAPCtrl                req;            /* Request control flag */
+       struct mcap_mdl_op_cb   *priv_data;     /* Temporal data to manage responses */
+       struct mcap_mdl_cb      *cb;            /* MDL callbacks */
+       guint                   tid;            /* Timer id for waiting for a response */
+       uint8_t                 *lcmd;          /* Last command sent */
+       gint                    ref;            /* References counter */
+       uint8_t                 ctrl;           /* MCL control flag */
+       uint16_t                next_mdl;       /* id used to create next MDL */
+       struct mcap_csp         *csp;           /* CSP control structure */
+};
+
+#define        MCAP_CTRL_CACHED        0x01    /* MCL is cached */
+#define        MCAP_CTRL_STD_OP        0x02    /* Support for standard op codes */
+#define        MCAP_CTRL_SYNC_OP       0x04    /* Support for synchronization commands */
+#define        MCAP_CTRL_CONN          0x08    /* MCL is in connecting process */
+#define        MCAP_CTRL_FREE          0x10    /* MCL is marked as releasable */
+#define        MCAP_CTRL_NOCACHE       0x20    /* MCL is marked as not cacheable */
+
+struct mcap_mdl {
+       struct mcap_mcl         *mcl;           /* MCL where this MDL belongs */
+       GIOChannel              *dc;            /* MCAP Data Channel IO */
+       guint                   wid;            /* MDL Watcher id */
+       uint16_t                mdlid;          /* MDL id */
+       uint8_t                 mdep_id;        /* MCAP Data End Point */
+       MDLState                state;          /* MDL state */
+       gint                    ref;            /* References counter */
+};
+
+struct sync_info_ind_data {
+       uint32_t        btclock;
+       uint64_t        timestamp;
+       uint16_t        accuracy;
+};
+
+int mcap_send_data(int sock, const void *buf, uint32_t size);
+
+void proc_sync_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len);
+void mcap_sync_init(struct mcap_mcl *mcl);
+void mcap_sync_stop(struct mcap_mcl *mcl);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MCAP_INTERNAL_H */
diff --git a/health/mcap_lib.h b/health/mcap_lib.h
new file mode 100644 (file)
index 0000000..9c1e508
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ *
+ *  MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ *  Authors:
+ *  Santiago Carot-Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *
+ *  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 __MCAP_LIB_H
+#define __MCAP_LIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+/* MCAP Error Response Codes */
+       MCAP_ERROR_INVALID_OP_CODE = 1,
+       MCAP_ERROR_INVALID_PARAM_VALUE,
+       MCAP_ERROR_INVALID_MDEP,
+       MCAP_ERROR_MDEP_BUSY,
+       MCAP_ERROR_INVALID_MDL,
+       MCAP_ERROR_MDL_BUSY,
+       MCAP_ERROR_INVALID_OPERATION,
+       MCAP_ERROR_RESOURCE_UNAVAILABLE,
+       MCAP_ERROR_UNSPECIFIED_ERROR,
+       MCAP_ERROR_REQUEST_NOT_SUPPORTED,
+       MCAP_ERROR_CONFIGURATION_REJECTED,
+/* MCAP Internal Errors */
+       MCAP_ERROR_INVALID_ARGS,
+       MCAP_ERROR_ALREADY_EXISTS,
+       MCAP_ERROR_REQ_IGNORED,
+       MCAP_ERROR_MCL_CLOSED,
+       MCAP_ERROR_FAILED
+} McapError;
+
+typedef enum {
+       MCAP_MDL_CB_INVALID,
+       MCAP_MDL_CB_CONNECTED,          /* mcap_mdl_event_cb */
+       MCAP_MDL_CB_CLOSED,             /* mcap_mdl_event_cb */
+       MCAP_MDL_CB_DELETED,            /* mcap_mdl_event_cb */
+       MCAP_MDL_CB_ABORTED,            /* mcap_mdl_event_cb */
+       MCAP_MDL_CB_REMOTE_CONN_REQ,    /* mcap_remote_mdl_conn_req_cb */
+       MCAP_MDL_CB_REMOTE_RECONN_REQ   /* mcap_remote_mdl_reconn_req_cb */
+} McapMclCb;
+
+struct mcap_instance;
+struct mcap_mcl;
+struct mcap_mdl;
+struct sync_info_ind_data;
+
+/************ Callbacks ************/
+
+/* MDL callbacks */
+
+typedef void (* mcap_mdl_event_cb) (struct mcap_mdl *mdl, gpointer data);
+typedef void (* mcap_mdl_operation_conf_cb) (struct mcap_mdl *mdl, uint8_t conf,
+                                               GError *err, gpointer data);
+typedef void (* mcap_mdl_operation_cb) (struct mcap_mdl *mdl, GError *err,
+                                               gpointer data);
+typedef void (* mcap_mdl_notify_cb) (GError *err, gpointer data);
+
+/* Next function should return an MCAP appropriate response code */
+typedef uint8_t (* mcap_remote_mdl_conn_req_cb) (struct mcap_mcl *mcl,
+                                               uint8_t mdepid, uint16_t mdlid,
+                                               uint8_t *conf, gpointer data);
+typedef uint8_t (* mcap_remote_mdl_reconn_req_cb) (struct mcap_mdl *mdl,
+                                               gpointer data);
+
+/* MCL callbacks */
+
+typedef void (* mcap_mcl_event_cb) (struct mcap_mcl *mcl, gpointer data);
+typedef void (* mcap_mcl_connect_cb) (struct mcap_mcl *mcl, GError *err,
+                                                               gpointer data);
+
+/* CSP callbacks */
+
+typedef void (* mcap_info_ind_event_cb) (struct mcap_mcl *mcl,
+                                       struct sync_info_ind_data *data);
+
+typedef void (* mcap_sync_cap_cb) (struct mcap_mcl *mcl,
+                                       uint8_t mcap_err,
+                                       uint8_t btclockres,
+                                       uint16_t synclead,
+                                       uint16_t tmstampres,
+                                       uint16_t tmstampacc,
+                                       GError *err,
+                                       gpointer data);
+
+typedef void (* mcap_sync_set_cb) (struct mcap_mcl *mcl,
+                                       uint8_t mcap_err,
+                                       uint32_t btclock,
+                                       uint64_t timestamp,
+                                       uint16_t accuracy,
+                                       GError *err,
+                                       gpointer data);
+
+/************ Operations ************/
+
+/* MDL operations */
+
+gboolean mcap_create_mdl(struct mcap_mcl *mcl,
+                               uint8_t mdepid,
+                               uint8_t conf,
+                               mcap_mdl_operation_conf_cb connect_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err);
+gboolean mcap_reconnect_mdl(struct mcap_mdl *mdl,
+                               mcap_mdl_operation_cb reconnect_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err);
+gboolean mcap_delete_all_mdls(struct mcap_mcl *mcl,
+                               mcap_mdl_notify_cb delete_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err);
+gboolean mcap_delete_mdl(struct mcap_mdl *mdl,
+                               mcap_mdl_notify_cb delete_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err);
+gboolean mcap_connect_mdl(struct mcap_mdl *mdl,
+                               uint8_t mode,
+                               uint16_t dcpsm,
+                               mcap_mdl_operation_cb connect_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err);
+gboolean mcap_mdl_abort(struct mcap_mdl *mdl,
+                               mcap_mdl_notify_cb abort_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err);
+
+int mcap_mdl_get_fd(struct mcap_mdl *mdl);
+uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl);
+
+struct mcap_mdl *mcap_mdl_ref(struct mcap_mdl *mdl);
+void mcap_mdl_unref(struct mcap_mdl *mdl);
+
+/* MCL operations */
+
+gboolean mcap_create_mcl(struct mcap_instance *mi,
+                               const bdaddr_t *addr,
+                               uint16_t ccpsm,
+                               mcap_mcl_connect_cb connect_cb,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GError **err);
+void mcap_close_mcl(struct mcap_mcl *mcl, gboolean cache);
+gboolean mcap_mcl_set_cb(struct mcap_mcl *mcl, gpointer user_data,
+                                       GError **gerr, McapMclCb cb1, ...);
+void mcap_mcl_get_addr(struct mcap_mcl *mcl, bdaddr_t *addr);
+
+struct mcap_mcl *mcap_mcl_ref(struct mcap_mcl *mcl);
+void mcap_mcl_unref(struct mcap_mcl *mcl);
+
+/* CSP operations */
+
+void mcap_enable_csp(struct mcap_instance *mi);
+void mcap_disable_csp(struct mcap_instance *mi);
+
+uint64_t mcap_get_timestamp(struct mcap_mcl *mcl,
+                               struct timespec *given_time);
+uint32_t mcap_get_btclock(struct mcap_mcl *mcl);
+
+void mcap_sync_cap_req(struct mcap_mcl *mcl,
+                       uint16_t reqacc,
+                       mcap_sync_cap_cb cb,
+                       gpointer user_data,
+                       GError **err);
+
+void mcap_sync_set_req(struct mcap_mcl *mcl,
+                       uint8_t update,
+                       uint32_t btclock,
+                       uint64_t timestamp,
+                       mcap_sync_set_cb cb,
+                       gpointer user_data,
+                       GError **err);
+
+/* MCAP main operations */
+
+struct mcap_instance *mcap_create_instance(bdaddr_t *src,
+                                       BtIOSecLevel sec, uint16_t ccpsm,
+                                       uint16_t dcpsm,
+                                       mcap_mcl_event_cb mcl_connected,
+                                       mcap_mcl_event_cb mcl_reconnected,
+                                       mcap_mcl_event_cb mcl_disconnected,
+                                       mcap_mcl_event_cb mcl_uncached,
+                                       mcap_info_ind_event_cb mcl_sync_info_ind,
+                                       gpointer user_data,
+                                       GError **gerr);
+void mcap_release_instance(struct mcap_instance *mi);
+
+struct mcap_instance *mcap_instance_ref(struct mcap_instance *mi);
+void mcap_instance_unref(struct mcap_instance *mi);
+
+uint16_t mcap_get_ctrl_psm(struct mcap_instance *mi, GError **err);
+uint16_t mcap_get_data_psm(struct mcap_instance *mi, GError **err);
+
+gboolean mcap_set_data_chan_mode(struct mcap_instance *mi, uint8_t mode,
+                                                               GError **err);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MCAP_LIB_H */
diff --git a/health/mcap_sync.c b/health/mcap_sync.c
new file mode 100644 (file)
index 0000000..f4b005a
--- /dev/null
@@ -0,0 +1,1015 @@
+/*
+ *
+ *  MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *  Copyright (C) 2010 Signove
+ *
+ *  Authors:
+ *  Santiago Carot-Nemesio <sancane at gmail.com>
+ *  Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
+ *  Elvis Pfützenreuter <epx at signove.com>
+ *
+ *  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 "btio.h"
+#include <stdint.h>
+#include <netinet/in.h>
+#include <time.h>
+#include <stdlib.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include "../src/adapter.h"
+#include "../src/manager.h"
+#include <sys/ioctl.h>
+
+#include "config.h"
+#include "log.h"
+
+#include <bluetooth/bluetooth.h>
+#include "mcap.h"
+#include "mcap_lib.h"
+#include "mcap_internal.h"
+
+#define MCAP_BTCLOCK_HALF (MCAP_BTCLOCK_FIELD / 2)
+#define CLK CLOCK_MONOTONIC
+
+#define MCAP_CSP_ERROR g_quark_from_static_string("mcap-csp-error-quark")
+#define MAX_RETRIES    10
+#define SAMPLE_COUNT   20
+
+struct mcap_csp {
+       uint64_t        base_tmstamp;   /* CSP base timestamp */
+       struct timespec base_time;      /* CSP base time when timestamp set */
+       guint           local_caps;     /* CSP-Master: have got remote caps */
+       guint           remote_caps;    /* CSP-Slave: remote master got caps */
+       guint           rem_req_acc;    /* CSP-Slave: accuracy required by master */
+       guint           ind_expected;   /* CSP-Master: indication expected */
+       MCAPCtrl        csp_req;        /* CSP-Master: Request control flag */
+       guint           ind_timer;      /* CSP-Slave: indication timer */
+       guint           set_timer;      /* CSP-Slave: delayed set timer */
+       void            *set_data;      /* CSP-Slave: delayed set data */
+       void            *csp_priv_data; /* CSP-Master: In-flight request data */
+};
+
+struct mcap_sync_cap_cbdata {
+       mcap_sync_cap_cb        cb;
+       gpointer                user_data;
+};
+
+struct mcap_sync_set_cbdata {
+       mcap_sync_set_cb        cb;
+       gpointer                user_data;
+};
+
+struct csp_caps {
+       int ts_acc;             /* timestamp accuracy */
+       int ts_res;             /* timestamp resolution */
+       int latency;            /* Read BT clock latency */
+       int preempt_thresh;     /* Preemption threshold for latency */
+       int syncleadtime_ms;    /* SyncLeadTime in ms */
+};
+
+struct sync_set_data {
+       uint8_t update;
+       uint32_t sched_btclock;
+       uint64_t timestamp;
+       int ind_freq;
+       gboolean role;
+};
+
+#define hton64(x)     ntoh64(x)
+
+static gboolean csp_caps_initialized = FALSE;
+struct csp_caps _caps;
+
+static int send_sync_cmd(struct mcap_mcl *mcl, const void *buf, uint32_t size)
+{
+       int sock;
+
+       if (mcl->cc == NULL)
+               return -1;
+
+       sock = g_io_channel_unix_get_fd(mcl->cc);
+       return mcap_send_data(sock, buf, size);
+}
+
+static int send_unsupported_cap_req(struct mcap_mcl *mcl)
+{
+       mcap_md_sync_cap_rsp *cmd;
+       int sent;
+
+       cmd = g_new0(mcap_md_sync_cap_rsp, 1);
+       cmd->op = MCAP_MD_SYNC_CAP_RSP;
+       cmd->rc = MCAP_REQUEST_NOT_SUPPORTED;
+
+       sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
+       g_free(cmd);
+
+       return sent;
+}
+
+static int send_unsupported_set_req(struct mcap_mcl *mcl)
+{
+       mcap_md_sync_set_rsp *cmd;
+       int sent;
+
+       cmd = g_new0(mcap_md_sync_set_rsp, 1);
+       cmd->op = MCAP_MD_SYNC_SET_RSP;
+       cmd->rc = MCAP_REQUEST_NOT_SUPPORTED;
+
+       sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
+       g_free(cmd);
+
+       return sent;
+}
+
+static void reset_tmstamp(struct mcap_csp *csp, struct timespec *base_time,
+                               uint64_t new_tmstamp)
+{
+       csp->base_tmstamp = new_tmstamp;
+       if (base_time)
+               csp->base_time = *base_time;
+       else
+               clock_gettime(CLK, &csp->base_time);
+}
+
+void mcap_sync_init(struct mcap_mcl *mcl)
+{
+       if (!mcl->mi->csp_enabled) {
+               mcl->csp = NULL;
+               return;
+       }
+
+       mcl->csp = g_new0(struct mcap_csp, 1);
+
+       mcl->csp->rem_req_acc = 10000; /* safe divisor */
+       mcl->csp->set_data = NULL;
+       mcl->csp->csp_priv_data = NULL;
+
+       reset_tmstamp(mcl->csp, NULL, 0);
+}
+
+void mcap_sync_stop(struct mcap_mcl *mcl)
+{
+       if (!mcl->csp)
+               return;
+
+       if (mcl->csp->ind_timer)
+               g_source_remove(mcl->csp->ind_timer);
+
+       if (mcl->csp->set_timer)
+               g_source_remove(mcl->csp->set_timer);
+
+       if (mcl->csp->set_data)
+               g_free(mcl->csp->set_data);
+
+       if (mcl->csp->csp_priv_data)
+               g_free(mcl->csp->csp_priv_data);
+
+       mcl->csp->ind_timer = 0;
+       mcl->csp->set_timer = 0;
+       mcl->csp->set_data = NULL;
+       mcl->csp->csp_priv_data = NULL;
+
+       g_free(mcl->csp);
+       mcl->csp = NULL;
+}
+
+static uint64_t time_us(struct timespec *tv)
+{
+       return tv->tv_sec * 1000000 + tv->tv_nsec / 1000;
+}
+
+static int64_t bt2us(int bt)
+{
+       return bt * 312.5;
+}
+
+static int bt2ms(int bt)
+{
+       return bt * 312.5 / 1000;
+}
+
+static int btoffset(uint32_t btclk1, uint32_t btclk2)
+{
+       int offset = btclk2 - btclk1;
+
+       if (offset <= -MCAP_BTCLOCK_HALF)
+               offset += MCAP_BTCLOCK_FIELD;
+       else if (offset > MCAP_BTCLOCK_HALF)
+               offset -= MCAP_BTCLOCK_FIELD;
+
+       return offset;
+}
+
+static int btdiff(uint32_t btclk1, uint32_t btclk2)
+{
+       return btoffset(btclk1, btclk2);
+}
+
+static gboolean valid_btclock(uint32_t btclk)
+{
+       return btclk <= MCAP_BTCLOCK_MAX;
+}
+
+/* This call may fail; either deal with retry or use read_btclock_retry */
+static gboolean read_btclock(struct mcap_mcl *mcl, uint32_t *btclock,
+                                                       uint16_t *btaccuracy)
+{
+       int which = 1;
+       struct btd_adapter *adapter;
+
+       adapter = manager_find_adapter(&mcl->mi->src);
+
+       if (!adapter)
+               return FALSE;
+
+       if (btd_adapter_read_clock(adapter, &mcl->addr, which, 1000,
+                                               btclock, btaccuracy) < 0)
+               return FALSE;
+
+       return TRUE;
+}
+
+static gboolean read_btclock_retry(struct mcap_mcl *mcl, uint32_t *btclock,
+                                                       uint16_t *btaccuracy)
+{
+       int retries = 5;
+
+       while (--retries >= 0) {
+               if (read_btclock(mcl, btclock, btaccuracy))
+                       return TRUE;
+               DBG("CSP: retrying to read bt clock...");
+       }
+
+       return FALSE;
+}
+
+static gboolean get_btrole(struct mcap_mcl *mcl)
+{
+       int sock, flags;
+       socklen_t len;
+
+       if (mcl->cc == NULL)
+               return -1;
+
+       sock = g_io_channel_unix_get_fd(mcl->cc);
+       len = sizeof(flags);
+
+       if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len))
+               DBG("CSP: could not read role");
+
+       return flags & L2CAP_LM_MASTER;
+}
+
+uint64_t mcap_get_timestamp(struct mcap_mcl *mcl,
+                               struct timespec *given_time)
+{
+       struct timespec now;
+       uint64_t tmstamp;
+
+       if (!mcl->csp)
+               return MCAP_TMSTAMP_DONTSET;
+
+       if (given_time)
+               now = *given_time;
+       else
+               clock_gettime(CLK, &now);
+
+       tmstamp = time_us(&now) - time_us(&mcl->csp->base_time)
+               + mcl->csp->base_tmstamp;
+
+       return tmstamp;
+}
+
+uint32_t mcap_get_btclock(struct mcap_mcl *mcl)
+{
+       uint32_t btclock;
+       uint16_t accuracy;
+
+       if (!mcl->csp)
+               return MCAP_BTCLOCK_IMMEDIATE;
+
+       if (!read_btclock_retry(mcl, &btclock, &accuracy))
+               btclock = 0xffffffff;
+
+       return btclock;
+}
+
+static gboolean initialize_caps(struct mcap_mcl *mcl)
+{
+       struct timespec t1, t2;
+       int latencies[SAMPLE_COUNT];
+       int latency, avg, dev;
+       uint32_t btclock;
+       uint16_t btaccuracy;
+       int i;
+       int retries;
+
+       clock_getres(CLK, &t1);
+
+       _caps.ts_res = time_us(&t1);
+       if (_caps.ts_res < 1)
+               _caps.ts_res = 1;
+
+       _caps.ts_acc = 20; /* ppm, estimated */
+
+       /* A little exercise before measuing latency */
+       clock_gettime(CLK, &t1);
+       read_btclock_retry(mcl, &btclock, &btaccuracy);
+
+       /* Read clock a number of times and measure latency */
+       avg = 0;
+       i = 0;
+       retries = MAX_RETRIES;
+       while (i < SAMPLE_COUNT && retries > 0) {
+               clock_gettime(CLK, &t1);
+               if (!read_btclock(mcl, &btclock, &btaccuracy)) {
+                       retries--;
+                       continue;
+               }
+               clock_gettime(CLK, &t2);
+
+               latency = time_us(&t2) - time_us(&t1);
+               latencies[i] = latency;
+               avg += latency;
+               i++;
+       }
+
+       if (retries <= 0)
+               return FALSE;
+
+       /* Calculate average and deviation */
+       avg /= SAMPLE_COUNT;
+       dev = 0;
+       for (i = 0; i < SAMPLE_COUNT; ++i)
+               dev += abs(latencies[i] - avg);
+       dev /= SAMPLE_COUNT;
+
+       /* Calculate corrected average, without 'freak' latencies */
+       latency = 0;
+       for (i = 0; i < SAMPLE_COUNT; ++i) {
+               if (latencies[i] > (avg + dev * 6))
+                       latency += avg;
+               else
+                       latency += latencies[i];
+       }
+       latency /= SAMPLE_COUNT;
+
+       _caps.latency = latency;
+       _caps.preempt_thresh = latency * 4;
+       _caps.syncleadtime_ms = latency * 50 / 1000;
+
+       csp_caps_initialized = TRUE;
+       return TRUE;
+}
+
+static struct csp_caps *caps(struct mcap_mcl *mcl)
+{
+       if (!csp_caps_initialized)
+               if (!initialize_caps(mcl)) {
+                       /* Temporary failure in reading BT clock */
+                       return NULL;
+               }
+
+       return &_caps;
+}
+
+static int send_sync_cap_rsp(struct mcap_mcl *mcl, uint8_t rspcode,
+                       uint8_t btclockres, uint16_t synclead,
+                       uint16_t tmstampres, uint16_t tmstampacc)
+{
+       mcap_md_sync_cap_rsp *rsp;
+       int sent;
+
+       rsp = g_new0(mcap_md_sync_cap_rsp, 1);
+
+       rsp->op = MCAP_MD_SYNC_CAP_RSP;
+       rsp->rc = rspcode;
+
+       rsp->btclock = btclockres;
+       rsp->sltime = htons(synclead);
+       rsp->timestnr = htons(tmstampres);
+       rsp->timestna = htons(tmstampacc);
+
+       sent = send_sync_cmd(mcl, rsp, sizeof(*rsp));
+       g_free(rsp);
+
+       return sent;
+}
+
+static void proc_sync_cap_req(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       mcap_md_sync_cap_req *req;
+       uint16_t required_accuracy;
+       uint16_t our_accuracy;
+       uint32_t btclock;
+       uint16_t btres;
+
+       if (len != sizeof(mcap_md_sync_cap_req)) {
+               send_sync_cap_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+                                       0, 0, 0, 0);
+               return;
+       }
+
+       if (!caps(mcl)) {
+               send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
+                                       0, 0, 0, 0);
+               return;
+       }
+
+       req = (mcap_md_sync_cap_req *) cmd;
+       required_accuracy = ntohs(req->timest);
+       our_accuracy = caps(mcl)->ts_acc;
+
+       if (required_accuracy < our_accuracy || required_accuracy < 1) {
+               send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
+                                       0, 0, 0, 0);
+               return;
+       }
+
+       if (!read_btclock_retry(mcl, &btclock, &btres)) {
+               send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
+                                       0, 0, 0, 0);
+               return;
+       }
+
+       mcl->csp->remote_caps = 1;
+       mcl->csp->rem_req_acc = required_accuracy;
+
+       send_sync_cap_rsp(mcl, MCAP_SUCCESS, btres,
+                               caps(mcl)->syncleadtime_ms,
+                               caps(mcl)->ts_res, our_accuracy);
+}
+
+static int send_sync_set_rsp(struct mcap_mcl *mcl, uint8_t rspcode,
+                       uint32_t btclock, uint64_t timestamp,
+                       uint16_t tmstampres)
+{
+       mcap_md_sync_set_rsp *rsp;
+       int sent;
+
+       rsp = g_new0(mcap_md_sync_set_rsp, 1);
+
+       rsp->op = MCAP_MD_SYNC_SET_RSP;
+       rsp->rc = rspcode;
+       rsp->btclock = htonl(btclock);
+       rsp->timestst = hton64(timestamp);
+       rsp->timestsa = htons(tmstampres);
+
+       sent = send_sync_cmd(mcl, rsp, sizeof(*rsp));
+       g_free(rsp);
+
+       return sent;
+}
+
+static gboolean get_all_clocks(struct mcap_mcl *mcl, uint32_t *btclock,
+                               struct timespec *base_time,
+                               uint64_t *timestamp)
+{
+       int latency;
+       int retry = 5;
+       uint16_t btres;
+       struct timespec t0;
+
+       if (!caps(mcl))
+               return FALSE;
+
+       latency = caps(mcl)->preempt_thresh + 1;
+
+       while (latency > caps(mcl)->preempt_thresh && --retry >= 0) {
+
+               clock_gettime(CLK, &t0);
+
+               if (!read_btclock(mcl, btclock, &btres))
+                       continue;
+
+               clock_gettime(CLK, base_time);
+
+               /* Tries to detect preemption between clock_gettime
+                * and read_btclock by measuring transaction time
+                */
+               latency = time_us(base_time) - time_us(&t0);
+       }
+
+       *timestamp = mcap_get_timestamp(mcl, base_time);
+
+       return TRUE;
+}
+
+static gboolean sync_send_indication(gpointer user_data)
+{
+       struct mcap_mcl *mcl;
+       mcap_md_sync_info_ind *cmd;
+       uint32_t btclock;
+       uint64_t tmstamp;
+       struct timespec base_time;
+       int sent;
+
+       if (!user_data)
+               return FALSE;
+
+       mcl = user_data;
+
+       if (!caps(mcl))
+               return FALSE;
+
+       if (!get_all_clocks(mcl, &btclock, &base_time, &tmstamp))
+               return FALSE;
+
+       cmd = g_new0(mcap_md_sync_info_ind, 1);
+
+       cmd->op = MCAP_MD_SYNC_INFO_IND;
+       cmd->btclock = htonl(btclock);
+       cmd->timestst = hton64(tmstamp);
+       cmd->timestsa = htons(caps(mcl)->latency);
+
+       sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
+       g_free(cmd);
+
+       return !sent;
+}
+
+static gboolean proc_sync_set_req_phase2(gpointer user_data)
+{
+       struct mcap_mcl *mcl;
+       struct sync_set_data *data;
+       uint8_t update;
+       uint32_t sched_btclock;
+       uint64_t new_tmstamp;
+       int ind_freq;
+       int role;
+       uint32_t btclock;
+       uint64_t tmstamp;
+       struct timespec base_time;
+       uint16_t tmstampacc;
+       gboolean reset;
+       int delay;
+
+       if (!user_data)
+               return FALSE;
+
+       mcl = user_data;
+
+       if (!mcl->csp->set_data)
+               return FALSE;
+
+       data = mcl->csp->set_data;
+       update = data->update;
+       sched_btclock = data->sched_btclock;
+       new_tmstamp = data->timestamp;
+       ind_freq = data->ind_freq;
+       role = data->role;
+
+       if (!caps(mcl)) {
+               send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
+               return FALSE;
+       }
+
+       if (!get_all_clocks(mcl, &btclock, &base_time, &tmstamp)) {
+               send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
+               return FALSE;
+       }
+
+       if (get_btrole(mcl) != role) {
+               send_sync_set_rsp(mcl, MCAP_INVALID_OPERATION, 0, 0, 0);
+               return FALSE;
+       }
+
+       reset = (new_tmstamp != MCAP_TMSTAMP_DONTSET);
+
+       if (reset) {
+               if (sched_btclock != MCAP_BTCLOCK_IMMEDIATE) {
+                       delay = bt2us(btdiff(sched_btclock, btclock));
+                       if (delay >= 0 || ((new_tmstamp - delay) > 0)) {
+                               new_tmstamp += delay;
+                               DBG("CSP: reset w/ delay %dus, compensated",
+                                                                       delay);
+                       } else
+                               DBG("CSP: reset w/ delay %dus, uncompensated",
+                                                                       delay);
+               }
+
+               reset_tmstamp(mcl->csp, &base_time, new_tmstamp);
+               tmstamp = new_tmstamp;
+       }
+
+       tmstampacc = caps(mcl)->latency + caps(mcl)->ts_acc;
+
+       if (mcl->csp->ind_timer) {
+               g_source_remove(mcl->csp->ind_timer);
+               mcl->csp->ind_timer = 0;
+       }
+
+       if (update) {
+               int when = ind_freq + caps(mcl)->syncleadtime_ms;
+               mcl->csp->ind_timer = g_timeout_add(when,
+                                               sync_send_indication,
+                                               mcl);
+       }
+
+       send_sync_set_rsp(mcl, MCAP_SUCCESS, btclock, tmstamp, tmstampacc);
+
+       /* First indication after set is immediate */
+       if (update)
+               sync_send_indication(mcl);
+
+       return FALSE;
+}
+
+static void proc_sync_set_req(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       mcap_md_sync_set_req *req;
+       uint32_t sched_btclock, cur_btclock;
+       uint16_t btres;
+       uint8_t update;
+       uint64_t timestamp;
+       struct sync_set_data *set_data;
+       int phase2_delay, ind_freq, when;
+
+       if (len != sizeof(mcap_md_sync_set_req)) {
+               send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
+               return;
+       }
+
+       req = (mcap_md_sync_set_req *) cmd;
+       sched_btclock = ntohl(req->btclock);
+       update = req->timestui;
+       timestamp = ntoh64(req->timestst);
+
+       if (sched_btclock != MCAP_BTCLOCK_IMMEDIATE &&
+                       !valid_btclock(sched_btclock)) {
+               send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
+               return;
+       }
+
+       if (update > 1) {
+               send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
+               return;
+       }
+
+       if (!mcl->csp->remote_caps) {
+               /* Remote side did not ask our capabilities yet */
+               send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
+               return;
+       }
+
+       if (!caps(mcl)) {
+               send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
+               return;
+       }
+
+       if (!read_btclock_retry(mcl, &cur_btclock, &btres)) {
+               send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
+               return;
+       }
+
+       if (sched_btclock == MCAP_BTCLOCK_IMMEDIATE)
+               phase2_delay = 0;
+       else {
+               phase2_delay = btdiff(cur_btclock, sched_btclock);
+
+               if (phase2_delay < 0) {
+                       /* can not reset in the past tense */
+                       send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+                                               0, 0, 0);
+                       return;
+               }
+
+               /* Convert to miliseconds */
+               phase2_delay = bt2ms(phase2_delay);
+
+               if (phase2_delay > 61*1000) {
+                       /* More than 60 seconds in the future */
+                       send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+                                               0, 0, 0);
+                       return;
+               } else if (phase2_delay < caps(mcl)->latency / 1000) {
+                       /* Too fast for us to do in time */
+                       send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+                                               0, 0, 0);
+                       return;
+               }
+       }
+
+       if (update) {
+               /* Indication frequency: required accuracy divided by ours */
+               /* Converted to milisseconds */
+               ind_freq = (1000 * mcl->csp->rem_req_acc) / caps(mcl)->ts_acc;
+
+               if (ind_freq < MAX(caps(mcl)->latency * 2 / 1000, 100)) {
+                       /* Too frequent, we can't handle */
+                       send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+                                               0, 0, 0);
+                       return;
+               }
+
+               DBG("CSP: indication every %dms", ind_freq);
+       } else
+               ind_freq = 0;
+
+       if (mcl->csp->ind_timer) {
+               /* Old indications are no longer sent */
+               g_source_remove(mcl->csp->ind_timer);
+               mcl->csp->ind_timer = 0;
+       }
+
+       if (!mcl->csp->set_data)
+               mcl->csp->set_data = g_new0(struct sync_set_data, 1);
+
+       set_data = (struct sync_set_data *) mcl->csp->set_data;
+
+       set_data->update = update;
+       set_data->sched_btclock = sched_btclock;
+       set_data->timestamp = timestamp;
+       set_data->ind_freq = ind_freq;
+       set_data->role = get_btrole(mcl);
+
+       /* TODO is there some way to schedule a call based directly on
+        * a BT clock value, instead of this estimation that uses
+        * the SO clock? */
+
+       if (phase2_delay > 0) {
+               when = phase2_delay + caps(mcl)->syncleadtime_ms;
+               mcl->csp->set_timer = g_timeout_add(when,
+                                               proc_sync_set_req_phase2,
+                                               mcl);
+       } else
+               proc_sync_set_req_phase2(mcl);
+
+       /* First indication is immediate */
+       if (update)
+               sync_send_indication(mcl);
+}
+
+static void proc_sync_cap_rsp(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       mcap_md_sync_cap_rsp *rsp;
+       uint8_t mcap_err;
+       uint8_t btclockres;
+       uint16_t synclead;
+       uint16_t tmstampres;
+       uint16_t tmstampacc;
+       struct mcap_sync_cap_cbdata *cbdata;
+       mcap_sync_cap_cb cb;
+       gpointer user_data;
+
+       if (mcl->csp->csp_req != MCAP_MD_SYNC_CAP_REQ) {
+               DBG("CSP: got unexpected cap respose");
+               return;
+       }
+
+       if (!mcl->csp->csp_priv_data) {
+               DBG("CSP: no priv data for cap respose");
+               return;
+       }
+
+       cbdata = mcl->csp->csp_priv_data;
+       cb = cbdata->cb;
+       user_data = cbdata->user_data;
+       g_free(cbdata);
+
+       mcl->csp->csp_priv_data = NULL;
+       mcl->csp->csp_req = 0;
+
+       if (len != sizeof(mcap_md_sync_cap_rsp)) {
+               DBG("CSP: got corrupted cap respose");
+               return;
+       }
+
+       rsp = (mcap_md_sync_cap_rsp *) cmd;
+       mcap_err = rsp->rc;
+       btclockres = rsp->btclock;
+       synclead = ntohs(rsp->sltime);
+       tmstampres = ntohs(rsp->timestnr);
+       tmstampacc = ntohs(rsp->timestna);
+
+       if (!mcap_err)
+               mcl->csp->local_caps = TRUE;
+
+       cb(mcl, mcap_err, btclockres, synclead, tmstampres, tmstampacc, NULL,
+                                                               user_data);
+}
+
+static void proc_sync_set_rsp(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       mcap_md_sync_set_rsp *rsp;
+       uint8_t mcap_err;
+       uint32_t btclock;
+       uint64_t timestamp;
+       uint16_t accuracy;
+       struct mcap_sync_set_cbdata *cbdata;
+       mcap_sync_set_cb cb;
+       gpointer user_data;
+
+       if (mcl->csp->csp_req != MCAP_MD_SYNC_SET_REQ) {
+               DBG("CSP: got unexpected set respose");
+               return;
+       }
+
+       if (!mcl->csp->csp_priv_data) {
+               DBG("CSP: no priv data for set respose");
+               return;
+       }
+
+       cbdata = mcl->csp->csp_priv_data;
+       cb = cbdata->cb;
+       user_data = cbdata->user_data;
+       g_free(cbdata);
+
+       mcl->csp->csp_priv_data = NULL;
+       mcl->csp->csp_req = 0;
+
+       if (len != sizeof(mcap_md_sync_set_rsp)) {
+               DBG("CSP: got corrupted set respose");
+               return;
+       }
+
+       rsp = (mcap_md_sync_set_rsp *) cmd;
+       mcap_err = rsp->rc;
+       btclock = ntohl(rsp->btclock);
+       timestamp = ntoh64(rsp->timestst);
+       accuracy = ntohs(rsp->timestsa);
+
+       if (!mcap_err && !valid_btclock(btclock))
+               mcap_err = MCAP_ERROR_INVALID_ARGS;
+
+       cb(mcl, mcap_err, btclock, timestamp, accuracy, NULL, user_data);
+}
+
+static void proc_sync_info_ind(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       mcap_md_sync_info_ind *req;
+       struct sync_info_ind_data data;
+       uint32_t btclock;
+
+       if (!mcl->csp->ind_expected) {
+               DBG("CSP: received unexpected info indication");
+               return;
+       }
+
+       if (len != sizeof(mcap_md_sync_info_ind))
+               return;
+
+       req = (mcap_md_sync_info_ind *) cmd;
+
+       btclock = ntohl(req->btclock);
+
+       if (!valid_btclock(btclock))
+               return;
+
+       data.btclock = btclock;
+       data.timestamp = ntoh64(req->timestst);
+       data.accuracy = ntohs(req->timestsa);
+
+       if (mcl->mi->mcl_sync_infoind_cb)
+               mcl->mi->mcl_sync_infoind_cb(mcl, &data);
+}
+
+void proc_sync_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+       if (!mcl->mi->csp_enabled || !mcl->csp) {
+               switch (cmd[0]) {
+               case MCAP_MD_SYNC_CAP_REQ:
+                       send_unsupported_cap_req(mcl);
+                       break;
+               case MCAP_MD_SYNC_SET_REQ:
+                       send_unsupported_set_req(mcl);
+                       break;
+               }
+               return;
+       }
+
+       switch (cmd[0]) {
+       case MCAP_MD_SYNC_CAP_REQ:
+               proc_sync_cap_req(mcl, cmd, len);
+               break;
+       case MCAP_MD_SYNC_CAP_RSP:
+               proc_sync_cap_rsp(mcl, cmd, len);
+               break;
+       case MCAP_MD_SYNC_SET_REQ:
+               proc_sync_set_req(mcl, cmd, len);
+               break;
+       case MCAP_MD_SYNC_SET_RSP:
+               proc_sync_set_rsp(mcl, cmd, len);
+               break;
+       case MCAP_MD_SYNC_INFO_IND:
+               proc_sync_info_ind(mcl, cmd, len);
+               break;
+       }
+}
+
+void mcap_sync_cap_req(struct mcap_mcl *mcl, uint16_t reqacc,
+                       mcap_sync_cap_cb cb, gpointer user_data,
+                       GError **err)
+{
+       struct mcap_sync_cap_cbdata *cbdata;
+       mcap_md_sync_cap_req *cmd;
+
+       if (!mcl->mi->csp_enabled || !mcl->csp) {
+               g_set_error(err,
+                       MCAP_CSP_ERROR,
+                       MCAP_ERROR_RESOURCE_UNAVAILABLE,
+                       "CSP not enabled for the instance");
+               return;
+       }
+
+       if (mcl->csp->csp_req) {
+               g_set_error(err,
+                       MCAP_CSP_ERROR,
+                       MCAP_ERROR_RESOURCE_UNAVAILABLE,
+                       "Pending CSP request");
+               return;
+       }
+
+       mcl->csp->csp_req = MCAP_MD_SYNC_CAP_REQ;
+       cmd = g_new0(mcap_md_sync_cap_req, 1);
+
+       cmd->op = MCAP_MD_SYNC_CAP_REQ;
+       cmd->timest = htons(reqacc);
+
+       cbdata = g_new0(struct mcap_sync_cap_cbdata, 1);
+       cbdata->cb = cb;
+       cbdata->user_data = user_data;
+       mcl->csp->csp_priv_data = cbdata;
+
+       send_sync_cmd(mcl, cmd, sizeof(*cmd));
+
+       g_free(cmd);
+}
+
+void mcap_sync_set_req(struct mcap_mcl *mcl, uint8_t update, uint32_t btclock,
+                       uint64_t timestamp, mcap_sync_set_cb cb,
+                       gpointer user_data, GError **err)
+{
+       mcap_md_sync_set_req *cmd;
+       struct mcap_sync_set_cbdata *cbdata;
+
+       if (!mcl->mi->csp_enabled || !mcl->csp) {
+               g_set_error(err,
+                       MCAP_CSP_ERROR,
+                       MCAP_ERROR_RESOURCE_UNAVAILABLE,
+                       "CSP not enabled for the instance");
+               return;
+       }
+
+       if (!mcl->csp->local_caps) {
+               g_set_error(err,
+                       MCAP_CSP_ERROR,
+                       MCAP_ERROR_RESOURCE_UNAVAILABLE,
+                       "Did not get CSP caps from slave yet");
+               return;
+       }
+
+       if (mcl->csp->csp_req) {
+               g_set_error(err,
+                       MCAP_CSP_ERROR,
+                       MCAP_ERROR_RESOURCE_UNAVAILABLE,
+                       "Pending CSP request");
+               return;
+       }
+
+       mcl->csp->csp_req = MCAP_MD_SYNC_SET_REQ;
+       cmd = g_new0(mcap_md_sync_set_req, 1);
+
+       cmd->op = MCAP_MD_SYNC_SET_REQ;
+       cmd->timestui = update;
+       cmd->btclock = htonl(btclock);
+       cmd->timestst = hton64(timestamp);
+
+       mcl->csp->ind_expected = update;
+
+       cbdata = g_new0(struct mcap_sync_set_cbdata, 1);
+       cbdata->cb = cb;
+       cbdata->user_data = user_data;
+       mcl->csp->csp_priv_data = cbdata;
+
+       send_sync_cmd(mcl, cmd, sizeof(*cmd));
+
+       g_free(cmd);
+}
+
+void mcap_enable_csp(struct mcap_instance *mi)
+{
+       mi->csp_enabled = TRUE;
+}
+
+void mcap_disable_csp(struct mcap_instance *mi)
+{
+       mi->csp_enabled = FALSE;
+}
diff --git a/input/device.c b/input/device.c
new file mode 100644 (file)
index 0000000..4f5076a
--- /dev/null
@@ -0,0 +1,1268 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hidp.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "textfile.h"
+#include "uinput.h"
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+#include "../src/storage.h"
+#include "../src/manager.h"
+#include "../src/dbus-common.h"
+
+#include "device.h"
+#include "error.h"
+#include "fakehid.h"
+#include "btio.h"
+
+#define INPUT_DEVICE_INTERFACE "org.bluez.Input"
+
+#define BUF_SIZE               16
+
+#define UPDOWN_ENABLED         1
+
+#define FI_FLAG_CONNECTED      1
+
+struct input_conn {
+       struct fake_input       *fake;
+       DBusMessage             *pending_connect;
+       char                    *uuid;
+       char                    *alias;
+       GIOChannel              *ctrl_io;
+       GIOChannel              *intr_io;
+       guint                   ctrl_watch;
+       guint                   intr_watch;
+       int                     timeout;
+       struct input_device     *idev;
+};
+
+struct input_device {
+       DBusConnection          *conn;
+       char                    *path;
+       bdaddr_t                src;
+       bdaddr_t                dst;
+       uint32_t                handle;
+       guint                   dc_id;
+       char                    *name;
+       struct btd_device       *device;
+       GSList                  *connections;
+};
+
+GSList *devices = NULL;
+
+static struct input_device *find_device_by_path(GSList *list, const char *path)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct input_device *idev = l->data;
+
+               if (!strcmp(idev->path, path))
+                       return idev;
+       }
+
+       return NULL;
+}
+
+static struct input_conn *find_connection(GSList *list, const char *pattern)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct input_conn *iconn = l->data;
+
+               if (!strcasecmp(iconn->uuid, pattern))
+                       return iconn;
+
+               if (!strcasecmp(iconn->alias, pattern))
+                       return iconn;
+       }
+
+       return NULL;
+}
+
+static void input_conn_free(struct input_conn *iconn)
+{
+       if (iconn->pending_connect)
+               dbus_message_unref(iconn->pending_connect);
+
+       if (iconn->ctrl_watch)
+               g_source_remove(iconn->ctrl_watch);
+
+       if (iconn->intr_watch)
+               g_source_remove(iconn->intr_watch);
+
+       if (iconn->intr_io)
+               g_io_channel_unref(iconn->intr_io);
+
+       if (iconn->ctrl_io)
+               g_io_channel_unref(iconn->ctrl_io);
+
+       g_free(iconn->uuid);
+       g_free(iconn->alias);
+       g_free(iconn->fake);
+       g_free(iconn);
+}
+
+static void input_device_free(struct input_device *idev)
+{
+       if (idev->dc_id)
+               device_remove_disconnect_watch(idev->device, idev->dc_id);
+
+       dbus_connection_unref(idev->conn);
+       btd_device_unref(idev->device);
+       g_free(idev->name);
+       g_free(idev->path);
+       g_free(idev);
+}
+
+static int uinput_create(char *name)
+{
+       struct uinput_dev dev;
+       int fd, err;
+
+       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_KEYBIT, KEY_UP);
+       ioctl(fd, UI_SET_KEYBIT, KEY_PAGEUP);
+       ioctl(fd, UI_SET_KEYBIT, KEY_DOWN);
+       ioctl(fd, UI_SET_KEYBIT, KEY_PAGEDOWN);
+
+       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 int decode_key(const char *str)
+{
+       static int mode = UPDOWN_ENABLED, gain = 0;
+
+       uint16_t key;
+       int new_gain;
+
+       /* Switch from key up/down to page up/down */
+       if (strncmp("AT+CKPD=200", str, 11) == 0) {
+               mode = ~mode;
+               return KEY_RESERVED;
+       }
+
+       if (strncmp("AT+VG", str, 5))
+               return KEY_RESERVED;
+
+       /* Gain key pressed */
+       if (strlen(str) != 10)
+               return KEY_RESERVED;
+
+       new_gain = strtol(&str[7], NULL, 10);
+       if (new_gain <= gain)
+               key = (mode == UPDOWN_ENABLED ? KEY_UP : KEY_PAGEUP);
+       else
+               key = (mode == UPDOWN_ENABLED ? KEY_DOWN : KEY_PAGEDOWN);
+
+       gain = new_gain;
+
+       return key;
+}
+
+static void send_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+       struct uinput_event event;
+       int err;
+
+       memset(&event, 0, sizeof(event));
+       event.type      = type;
+       event.code      = code;
+       event.value     = value;
+
+       err = write(fd, &event, sizeof(event));
+}
+
+static void send_key(int fd, uint16_t key)
+{
+       /* Key press */
+       send_event(fd, EV_KEY, key, 1);
+       send_event(fd, EV_SYN, SYN_REPORT, 0);
+       /* Key release */
+       send_event(fd, EV_KEY, key, 0);
+       send_event(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       struct fake_input *fake = data;
+       const char *ok = "\r\nOK\r\n";
+       char buf[BUF_SIZE];
+       ssize_t bread = 0, bwritten;
+       uint16_t key;
+       int fd;
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       if (cond & (G_IO_HUP | G_IO_ERR)) {
+               error("Hangup or error on rfcomm server socket");
+               goto failed;
+       }
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       memset(buf, 0, BUF_SIZE);
+       bread = read(fd, buf, sizeof(buf) - 1);
+       if (bread < 0) {
+               error("IO Channel read error");
+               goto failed;
+       }
+
+       DBG("Received: %s", buf);
+
+       bwritten = write(fd, ok, 6);
+       if (bwritten < 0) {
+               error("IO Channel write error");
+               goto failed;
+       }
+
+       key = decode_key(buf);
+       if (key != KEY_RESERVED)
+               send_key(fake->uinput, key);
+
+       return TRUE;
+
+failed:
+       ioctl(fake->uinput, UI_DEV_DESTROY);
+       close(fake->uinput);
+       fake->uinput = -1;
+       g_io_channel_unref(fake->io);
+
+       return FALSE;
+}
+
+static void rfcomm_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+       struct input_conn *iconn = user_data;
+       struct input_device *idev = iconn->idev;
+       struct fake_input *fake = iconn->fake;
+       DBusMessage *reply;
+
+       if (err) {
+               reply = btd_error_failed(iconn->pending_connect, err->message);
+               goto failed;
+       }
+
+       fake->rfcomm = g_io_channel_unix_get_fd(chan);
+
+       /*
+        * FIXME: Some headsets required a sco connection
+        * first to report volume gain key events
+        */
+       fake->uinput = uinput_create(idev->name);
+       if (fake->uinput < 0) {
+               g_io_channel_shutdown(chan, TRUE, NULL);
+               reply = btd_error_failed(iconn->pending_connect,
+                                                       strerror(errno));
+               goto failed;
+       }
+
+       fake->io = g_io_channel_unix_new(fake->rfcomm);
+       g_io_channel_set_close_on_unref(fake->io, TRUE);
+       g_io_add_watch(fake->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                               (GIOFunc) rfcomm_io_cb, fake);
+
+       /* Replying to the requestor */
+       reply = dbus_message_new_method_return(iconn->pending_connect);
+       g_dbus_send_message(idev->conn, reply);
+
+       dbus_message_unref(iconn->pending_connect);
+       iconn->pending_connect = NULL;
+
+       return;
+
+failed:
+       g_dbus_send_message(idev->conn, reply);
+       dbus_message_unref(iconn->pending_connect);
+       iconn->pending_connect = NULL;
+}
+
+static gboolean rfcomm_connect(struct input_conn *iconn, GError **err)
+{
+       struct input_device *idev = iconn->idev;
+       GIOChannel *io;
+
+       io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, iconn,
+                               NULL, err,
+                               BT_IO_OPT_SOURCE_BDADDR, &idev->src,
+                               BT_IO_OPT_DEST_BDADDR, &idev->dst,
+                               BT_IO_OPT_INVALID);
+       if (!io)
+               return FALSE;
+
+       g_io_channel_unref(io);
+
+       return TRUE;
+}
+
+static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       struct input_conn *iconn = data;
+       struct input_device *idev = iconn->idev;
+       gboolean connected = FALSE;
+
+       /* Checking for ctrl_watch avoids a double g_io_channel_shutdown since
+        * it's likely that ctrl_watch_cb has been queued for dispatching in
+        * this mainloop iteration */
+       if ((cond & (G_IO_HUP | G_IO_ERR)) && iconn->ctrl_watch)
+               g_io_channel_shutdown(chan, TRUE, NULL);
+
+       emit_property_changed(idev->conn, idev->path, INPUT_DEVICE_INTERFACE,
+                               "Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+       device_remove_disconnect_watch(idev->device, idev->dc_id);
+       idev->dc_id = 0;
+
+       iconn->intr_watch = 0;
+
+       g_io_channel_unref(iconn->intr_io);
+       iconn->intr_io = NULL;
+
+       /* Close control channel */
+       if (iconn->ctrl_io && !(cond & G_IO_NVAL))
+               g_io_channel_shutdown(iconn->ctrl_io, TRUE, NULL);
+
+       return FALSE;
+}
+
+static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       struct input_conn *iconn = data;
+
+       /* Checking for intr_watch avoids a double g_io_channel_shutdown since
+        * it's likely that intr_watch_cb has been queued for dispatching in
+        * this mainloop iteration */
+       if ((cond & (G_IO_HUP | G_IO_ERR)) && iconn->intr_watch)
+               g_io_channel_shutdown(chan, TRUE, NULL);
+
+       iconn->ctrl_watch = 0;
+
+       g_io_channel_unref(iconn->ctrl_io);
+       iconn->ctrl_io = NULL;
+
+       /* Close interrupt channel */
+       if (iconn->intr_io && !(cond & G_IO_NVAL))
+               g_io_channel_shutdown(iconn->intr_io, TRUE, NULL);
+
+       return FALSE;
+}
+
+static gboolean fake_hid_connect(struct input_conn *iconn, GError **err)
+{
+       struct fake_hid *fhid = iconn->fake->priv;
+
+       return fhid->connect(iconn->fake, err);
+}
+
+static int fake_hid_disconnect(struct input_conn *iconn)
+{
+       struct fake_hid *fhid = iconn->fake->priv;
+
+       return fhid->disconnect(iconn->fake);
+}
+
+static void epox_endian_quirk(unsigned char *data, int size)
+{
+       /* USAGE_PAGE (Keyboard)        05 07
+        * USAGE_MINIMUM (0)            19 00
+        * USAGE_MAXIMUM (65280)        2A 00 FF   <= must be FF 00
+        * LOGICAL_MINIMUM (0)          15 00
+        * LOGICAL_MAXIMUM (65280)      26 00 FF   <= must be FF 00
+        */
+       unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff,
+                                               0x15, 0x00, 0x26, 0x00, 0xff };
+       unsigned int i;
+
+       if (!data)
+               return;
+
+       for (i = 0; i < size - sizeof(pattern); i++) {
+               if (!memcmp(data + i, pattern, sizeof(pattern))) {
+                       data[i + 5] = 0xff;
+                       data[i + 6] = 0x00;
+                       data[i + 10] = 0xff;
+                       data[i + 11] = 0x00;
+               }
+       }
+}
+
+static void extract_hid_record(sdp_record_t *rec, struct hidp_connadd_req *req)
+{
+       sdp_data_t *pdlist, *pdlist2;
+       uint8_t attr_val;
+
+       pdlist = sdp_data_get(rec, 0x0101);
+       pdlist2 = sdp_data_get(rec, 0x0102);
+       if (pdlist) {
+               if (pdlist2) {
+                       if (strncmp(pdlist->val.str, pdlist2->val.str, 5)) {
+                               strncpy(req->name, pdlist2->val.str, 127);
+                               strcat(req->name, " ");
+                       }
+                       strncat(req->name, pdlist->val.str, 127 - strlen(req->name));
+               } else
+                       strncpy(req->name, pdlist->val.str, 127);
+       } else {
+               pdlist2 = sdp_data_get(rec, 0x0100);
+               if (pdlist2)
+                       strncpy(req->name, pdlist2->val.str, 127);
+       }
+
+       pdlist = sdp_data_get(rec, SDP_ATTR_HID_PARSER_VERSION);
+       req->parser = pdlist ? pdlist->val.uint16 : 0x0100;
+
+       pdlist = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS);
+       req->subclass = pdlist ? pdlist->val.uint8 : 0;
+
+       pdlist = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE);
+       req->country = pdlist ? pdlist->val.uint8 : 0;
+
+       pdlist = sdp_data_get(rec, SDP_ATTR_HID_VIRTUAL_CABLE);
+       attr_val = pdlist ? pdlist->val.uint8 : 0;
+       if (attr_val)
+               req->flags |= (1 << HIDP_VIRTUAL_CABLE_UNPLUG);
+
+       pdlist = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE);
+       attr_val = pdlist ? pdlist->val.uint8 : 0;
+       if (attr_val)
+               req->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE);
+
+       pdlist = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST);
+       if (pdlist) {
+               pdlist = pdlist->val.dataseq;
+               pdlist = pdlist->val.dataseq;
+               pdlist = pdlist->next;
+
+               req->rd_data = g_try_malloc0(pdlist->unitSize);
+               if (req->rd_data) {
+                       memcpy(req->rd_data, (unsigned char *) pdlist->val.str,
+                                                               pdlist->unitSize);
+                       req->rd_size = pdlist->unitSize;
+                       epox_endian_quirk(req->rd_data, req->rd_size);
+               }
+       }
+}
+
+static int ioctl_connadd(struct hidp_connadd_req *req)
+{
+       int ctl, err = 0;
+
+       ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP);
+       if (ctl < 0)
+               return -errno;
+
+       if (ioctl(ctl, HIDPCONNADD, req) < 0)
+               err = errno;
+
+       close(ctl);
+
+       return -err;
+}
+
+static void encrypt_completed(uint8_t status, gpointer user_data)
+{
+       struct hidp_connadd_req *req = user_data;
+       int err;
+
+       if (status) {
+               error("Encryption failed: %s(0x%x)",
+                               strerror(bt_error(status)), status);
+               goto failed;
+       }
+
+       err = ioctl_connadd(req);
+       if (err == 0)
+               goto cleanup;
+
+       error("ioctl_connadd(): %s(%d)", strerror(-err), -err);
+failed:
+       close(req->intr_sock);
+       close(req->ctrl_sock);
+
+cleanup:
+       free(req->rd_data);
+
+       g_free(req);
+}
+
+static int hidp_add_connection(const struct input_device *idev,
+                               const struct input_conn *iconn)
+{
+       struct hidp_connadd_req *req;
+       struct fake_hid *fake_hid;
+       struct fake_input *fake;
+       sdp_record_t *rec;
+       char src_addr[18], dst_addr[18];
+       int err;
+
+       req = g_new0(struct hidp_connadd_req, 1);
+       req->ctrl_sock = g_io_channel_unix_get_fd(iconn->ctrl_io);
+       req->intr_sock = g_io_channel_unix_get_fd(iconn->intr_io);
+       req->flags     = 0;
+       req->idle_to   = iconn->timeout;
+
+       ba2str(&idev->src, src_addr);
+       ba2str(&idev->dst, dst_addr);
+
+       rec = fetch_record(src_addr, dst_addr, idev->handle);
+       if (!rec) {
+               error("Rejected connection from unknown device %s", dst_addr);
+               err = -EPERM;
+               goto cleanup;
+       }
+
+       extract_hid_record(rec, req);
+       sdp_record_free(rec);
+
+       read_device_id(src_addr, dst_addr, NULL,
+                               &req->vendor, &req->product, &req->version);
+
+       fake_hid = get_fake_hid(req->vendor, req->product);
+       if (fake_hid) {
+               err = 0;
+               fake = g_new0(struct fake_input, 1);
+               fake->connect = fake_hid_connect;
+               fake->disconnect = fake_hid_disconnect;
+               fake->priv = fake_hid;
+               fake->idev = idev;
+               fake = fake_hid_connadd(fake, iconn->intr_io, fake_hid);
+               if (fake == NULL)
+                       err = -ENOMEM;
+               else
+                       fake->flags |= FI_FLAG_CONNECTED;
+               goto cleanup;
+       }
+
+       if (idev->name)
+               strncpy(req->name, idev->name, sizeof(req->name) - 1);
+
+       /* Encryption is mandatory for keyboards */
+       if (req->subclass & 0x40) {
+               struct btd_adapter *adapter = device_get_adapter(idev->device);
+
+               err = btd_adapter_encrypt_link(adapter, (bdaddr_t *) &idev->dst,
+                                               encrypt_completed, req);
+               if (err == 0) {
+                       /* Waiting async encryption */
+                       return 0;
+               } else if (err != -EALREADY) {
+                       error("encrypt_link: %s (%d)", strerror(-err), -err);
+                       goto cleanup;
+               }
+       }
+
+       err = ioctl_connadd(req);
+
+cleanup:
+       free(req->rd_data);
+       g_free(req);
+
+       return err;
+}
+
+static int is_connected(struct input_conn *iconn)
+{
+       struct input_device *idev = iconn->idev;
+       struct fake_input *fake = iconn->fake;
+       struct hidp_conninfo ci;
+       int ctl;
+
+       /* Fake input */
+       if (fake)
+               return fake->flags & FI_FLAG_CONNECTED;
+
+       /* Standard HID */
+       ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP);
+       if (ctl < 0)
+               return 0;
+
+       memset(&ci, 0, sizeof(ci));
+       bacpy(&ci.bdaddr, &idev->dst);
+       if (ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) {
+               close(ctl);
+               return 0;
+       }
+
+       close(ctl);
+
+       if (ci.state != BT_CONNECTED)
+               return 0;
+       else
+               return 1;
+}
+
+static int connection_disconnect(struct input_conn *iconn, uint32_t flags)
+{
+       struct input_device *idev = iconn->idev;
+       struct fake_input *fake = iconn->fake;
+       struct hidp_conndel_req req;
+       struct hidp_conninfo ci;
+       int ctl, err;
+
+       /* Fake input disconnect */
+       if (fake) {
+               err = fake->disconnect(iconn);
+               if (err == 0)
+                       fake->flags &= ~FI_FLAG_CONNECTED;
+               return err;
+       }
+
+       /* Standard HID disconnect */
+       if (iconn->intr_io)
+               g_io_channel_shutdown(iconn->intr_io, TRUE, NULL);
+       if (iconn->ctrl_io)
+               g_io_channel_shutdown(iconn->ctrl_io, TRUE, NULL);
+
+       ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP);
+       if (ctl < 0) {
+               error("Can't open HIDP control socket");
+               return -errno;
+       }
+
+       memset(&ci, 0, sizeof(ci));
+       bacpy(&ci.bdaddr, &idev->dst);
+       if ((ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) ||
+                               (ci.state != BT_CONNECTED)) {
+               errno = ENOTCONN;
+               goto fail;
+       }
+
+       memset(&req, 0, sizeof(req));
+       bacpy(&req.bdaddr, &idev->dst);
+       req.flags = flags;
+       if (ioctl(ctl, HIDPCONNDEL, &req) < 0) {
+               error("Can't delete the HID device: %s(%d)",
+                               strerror(errno), errno);
+               goto fail;
+       }
+
+       close(ctl);
+
+       return 0;
+
+fail:
+       err = errno;
+       close(ctl);
+       errno = err;
+
+       return -err;
+}
+
+static int disconnect(struct input_device *idev, uint32_t flags)
+{
+       struct input_conn *iconn = NULL;
+       GSList *l;
+
+       for (l = idev->connections; l; l = l->next) {
+               iconn = l->data;
+
+               if (is_connected(iconn))
+                       break;
+       }
+
+       if (!iconn)
+               return -ENOTCONN;
+
+       return connection_disconnect(iconn, flags);
+}
+
+static void disconnect_cb(struct btd_device *device, gboolean removal,
+                               void *user_data)
+{
+       struct input_device *idev = user_data;
+       int flags;
+
+       info("Input: disconnect %s", idev->path);
+
+       flags = removal ? (1 << HIDP_VIRTUAL_CABLE_UNPLUG) : 0;
+
+       disconnect(idev, flags);
+}
+
+static int input_device_connected(struct input_device *idev,
+                                               struct input_conn *iconn)
+{
+       dbus_bool_t connected;
+       int err;
+
+       if (iconn->intr_io == NULL || iconn->ctrl_io == NULL)
+               return -ENOTCONN;
+
+       err = hidp_add_connection(idev, iconn);
+       if (err < 0)
+               return err;
+
+       iconn->intr_watch = g_io_add_watch(iconn->intr_io,
+                                       G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                                       intr_watch_cb, iconn);
+       iconn->ctrl_watch = g_io_add_watch(iconn->ctrl_io,
+                                       G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                                       ctrl_watch_cb, iconn);
+
+       connected = TRUE;
+       emit_property_changed(idev->conn, idev->path, INPUT_DEVICE_INTERFACE,
+                               "Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+       idev->dc_id = device_add_disconnect_watch(idev->device, disconnect_cb,
+                                                       idev, NULL);
+
+       return 0;
+}
+
+static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err,
+                                                       gpointer user_data)
+{
+       struct input_conn *iconn = user_data;
+       struct input_device *idev = iconn->idev;
+       DBusMessage *reply;
+       int err;
+       const char *err_msg;
+
+       if (conn_err) {
+               err_msg = conn_err->message;
+               goto failed;
+       }
+
+       err = input_device_connected(idev, iconn);
+       if (err < 0) {
+               err_msg = strerror(-err);
+               goto failed;
+       }
+
+       /* Replying to the requestor */
+       g_dbus_send_reply(idev->conn, iconn->pending_connect, DBUS_TYPE_INVALID);
+
+       dbus_message_unref(iconn->pending_connect);
+       iconn->pending_connect = NULL;
+
+       return;
+
+failed:
+       error("%s", err_msg);
+       reply = btd_error_failed(iconn->pending_connect, err_msg);
+       g_dbus_send_message(idev->conn, reply);
+
+       dbus_message_unref(iconn->pending_connect);
+       iconn->pending_connect = NULL;
+
+       /* So we guarantee the interrupt channel is closed before the
+        * control channel (if we only do unref GLib will close it only
+        * after returning control to the mainloop */
+       if (!conn_err)
+               g_io_channel_shutdown(iconn->intr_io, FALSE, NULL);
+
+       g_io_channel_unref(iconn->intr_io);
+       iconn->intr_io = NULL;
+
+       if (iconn->ctrl_io) {
+               g_io_channel_unref(iconn->ctrl_io);
+               iconn->ctrl_io = NULL;
+       }
+}
+
+static void control_connect_cb(GIOChannel *chan, GError *conn_err,
+                                                       gpointer user_data)
+{
+       struct input_conn *iconn = user_data;
+       struct input_device *idev = iconn->idev;
+       DBusMessage *reply;
+       GIOChannel *io;
+       GError *err = NULL;
+
+       if (conn_err) {
+               error("%s", conn_err->message);
+               reply = btd_error_failed(iconn->pending_connect,
+                                               conn_err->message);
+               goto failed;
+       }
+
+       /* Connect to the HID interrupt channel */
+       io = bt_io_connect(BT_IO_L2CAP, interrupt_connect_cb, iconn,
+                               NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, &idev->src,
+                               BT_IO_OPT_DEST_BDADDR, &idev->dst,
+                               BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR,
+                               BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+                               BT_IO_OPT_INVALID);
+       if (!io) {
+               error("%s", err->message);
+               reply = btd_error_failed(iconn->pending_connect,
+                                                       err->message);
+               g_error_free(err);
+               goto failed;
+       }
+
+       iconn->intr_io = io;
+
+       return;
+
+failed:
+       g_io_channel_unref(iconn->ctrl_io);
+       iconn->ctrl_io = NULL;
+       g_dbus_send_message(idev->conn, reply);
+       dbus_message_unref(iconn->pending_connect);
+       iconn->pending_connect = NULL;
+}
+
+static int fake_disconnect(struct input_conn *iconn)
+{
+       struct fake_input *fake = iconn->fake;
+
+       if (!fake->io)
+               return -ENOTCONN;
+
+       g_io_channel_shutdown(fake->io, TRUE, NULL);
+       g_io_channel_unref(fake->io);
+       fake->io = NULL;
+
+       if (fake->uinput >= 0) {
+               ioctl(fake->uinput, UI_DEV_DESTROY);
+               close(fake->uinput);
+               fake->uinput = -1;
+       }
+
+       return 0;
+}
+
+/*
+ * Input Device methods
+ */
+static DBusMessage *input_device_connect(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct input_device *idev = data;
+       struct input_conn *iconn;
+       struct fake_input *fake;
+       DBusMessage *reply;
+       GError *err = NULL;
+
+       iconn = find_connection(idev->connections, "HID");
+       if (!iconn)
+               return btd_error_not_supported(msg);
+
+       if (iconn->pending_connect)
+               return btd_error_in_progress(msg);
+
+       if (is_connected(iconn))
+               return btd_error_already_connected(msg);
+
+       iconn->pending_connect = dbus_message_ref(msg);
+       fake = iconn->fake;
+
+       if (fake) {
+               /* Fake input device */
+               if (fake->connect(iconn, &err))
+                       fake->flags |= FI_FLAG_CONNECTED;
+       } else {
+               /* HID devices */
+               GIOChannel *io;
+
+               io = bt_io_connect(BT_IO_L2CAP, control_connect_cb, iconn,
+                                       NULL, &err,
+                                       BT_IO_OPT_SOURCE_BDADDR, &idev->src,
+                                       BT_IO_OPT_DEST_BDADDR, &idev->dst,
+                                       BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL,
+                                       BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+                                       BT_IO_OPT_INVALID);
+               iconn->ctrl_io = io;
+       }
+
+       if (err == NULL)
+               return NULL;
+
+       error("%s", err->message);
+       dbus_message_unref(iconn->pending_connect);
+       iconn->pending_connect = NULL;
+       reply = btd_error_failed(msg, err->message);
+       g_error_free(err);
+       return reply;
+}
+
+static DBusMessage *input_device_disconnect(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct input_device *idev = data;
+       int err;
+
+       err = disconnect(idev, 0);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void device_unregister(void *data)
+{
+       struct input_device *idev = data;
+
+       DBG("Unregistered interface %s on path %s", INPUT_DEVICE_INTERFACE,
+                                                               idev->path);
+
+       devices = g_slist_remove(devices, idev);
+       input_device_free(idev);
+}
+
+static gint connected_cmp(gpointer a, gpointer b)
+{
+       struct input_conn *iconn = a;
+
+       return !is_connected(iconn);
+}
+
+static DBusMessage *input_device_get_properties(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct input_device *idev = data;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       DBusMessageIter dict;
+       dbus_bool_t connected;
+
+       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 */
+       connected = !!g_slist_find_custom(idev->connections, NULL,
+                                       (GCompareFunc) connected_cmp);
+       dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static GDBusMethodTable device_methods[] = {
+       { "Connect",            "",     "",     input_device_connect,
+                                               G_DBUS_METHOD_FLAG_ASYNC },
+       { "Disconnect",         "",     "",     input_device_disconnect },
+       { "GetProperties",      "",     "a{sv}",input_device_get_properties },
+       { }
+};
+
+static GDBusSignalTable device_signals[] = {
+       { "PropertyChanged",    "sv"    },
+       { }
+};
+
+static struct input_device *input_device_new(DBusConnection *conn,
+                                       struct btd_device *device, const char *path,
+                                       const bdaddr_t *src, const bdaddr_t *dst,
+                                       const uint32_t handle)
+{
+       struct input_device *idev;
+       char name[249], src_addr[18], dst_addr[18];
+
+       idev = g_new0(struct input_device, 1);
+       bacpy(&idev->src, src);
+       bacpy(&idev->dst, dst);
+       idev->device = btd_device_ref(device);
+       idev->path = g_strdup(path);
+       idev->conn = dbus_connection_ref(conn);
+       idev->handle = handle;
+
+       ba2str(src, src_addr);
+       ba2str(dst, dst_addr);
+       if (read_device_name(src_addr, dst_addr, name) == 0)
+               idev->name = g_strdup(name);
+
+       if (g_dbus_register_interface(conn, idev->path, INPUT_DEVICE_INTERFACE,
+                                       device_methods, device_signals, NULL,
+                                       idev, device_unregister) == FALSE) {
+               error("Failed to register interface %s on path %s",
+                       INPUT_DEVICE_INTERFACE, path);
+               input_device_free(idev);
+               return NULL;
+       }
+
+       DBG("Registered interface %s on path %s",
+                       INPUT_DEVICE_INTERFACE, idev->path);
+
+       return idev;
+}
+
+static struct input_conn *input_conn_new(struct input_device *idev,
+                                       const char *uuid, const char *alias,
+                                       int timeout)
+{
+       struct input_conn *iconn;
+
+       iconn = g_new0(struct input_conn, 1);
+       iconn->timeout = timeout;
+       iconn->uuid = g_strdup(uuid);
+       iconn->alias = g_strdup(alias);
+       iconn->idev = idev;
+
+       return iconn;
+}
+
+int input_device_register(DBusConnection *conn, struct btd_device *device,
+                       const char *path, const bdaddr_t *src,
+                       const bdaddr_t *dst, const char *uuid,
+                       uint32_t handle, int timeout)
+{
+       struct input_device *idev;
+       struct input_conn *iconn;
+
+       idev = find_device_by_path(devices, path);
+       if (!idev) {
+               idev = input_device_new(conn, device, path, src, dst, handle);
+               if (!idev)
+                       return -EINVAL;
+               devices = g_slist_append(devices, idev);
+       }
+
+       iconn = input_conn_new(idev, uuid, "hid", timeout);
+       if (!iconn)
+               return -EINVAL;
+
+       idev->connections = g_slist_append(idev->connections, iconn);
+
+       return 0;
+}
+
+int fake_input_register(DBusConnection *conn, struct btd_device *device,
+                       const char *path, bdaddr_t *src, bdaddr_t *dst,
+                       const char *uuid, uint8_t channel)
+{
+       struct input_device *idev;
+       struct input_conn *iconn;
+
+       idev = find_device_by_path(devices, path);
+       if (!idev) {
+               idev = input_device_new(conn, device, path, src, dst, 0);
+               if (!idev)
+                       return -EINVAL;
+               devices = g_slist_append(devices, idev);
+       }
+
+       iconn = input_conn_new(idev, uuid, "hsp", 0);
+       if (!iconn)
+               return -EINVAL;
+
+       iconn->fake = g_new0(struct fake_input, 1);
+       iconn->fake->ch = channel;
+       iconn->fake->connect = rfcomm_connect;
+       iconn->fake->disconnect = fake_disconnect;
+
+       idev->connections = g_slist_append(idev->connections, iconn);
+
+       return 0;
+}
+
+static struct input_device *find_device(const bdaddr_t *src,
+                                       const bdaddr_t *dst)
+{
+       GSList *list;
+
+       for (list = devices; list != NULL; list = list->next) {
+               struct input_device *idev = list->data;
+
+               if (!bacmp(&idev->src, src) && !bacmp(&idev->dst, dst))
+                       return idev;
+       }
+
+       return NULL;
+}
+
+int input_device_unregister(const char *path, const char *uuid)
+{
+       struct input_device *idev;
+       struct input_conn *iconn;
+
+       idev = find_device_by_path(devices, path);
+       if (idev == NULL)
+               return -EINVAL;
+
+       iconn = find_connection(idev->connections, uuid);
+       if (iconn == NULL)
+               return -EINVAL;
+
+       if (iconn->pending_connect) {
+               /* Pending connection running */
+               return -EBUSY;
+       }
+
+       idev->connections = g_slist_remove(idev->connections, iconn);
+       input_conn_free(iconn);
+       if (idev->connections)
+               return 0;
+
+       g_dbus_unregister_interface(idev->conn, path, INPUT_DEVICE_INTERFACE);
+
+       return 0;
+}
+
+static int input_device_connadd(struct input_device *idev,
+                               struct input_conn *iconn)
+{
+       int err;
+
+       err = input_device_connected(idev, iconn);
+       if (err < 0)
+               goto error;
+
+       return 0;
+
+error:
+       if (iconn->ctrl_io) {
+               g_io_channel_shutdown(iconn->ctrl_io, FALSE, NULL);
+               g_io_channel_unref(iconn->ctrl_io);
+               iconn->ctrl_io = NULL;
+       }
+       if (iconn->intr_io) {
+               g_io_channel_shutdown(iconn->intr_io, FALSE, NULL);
+               g_io_channel_unref(iconn->intr_io);
+               iconn->intr_io = NULL;
+       }
+
+       return err;
+}
+
+int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm,
+                                                               GIOChannel *io)
+{
+       struct input_device *idev = find_device(src, dst);
+       struct input_conn *iconn;
+
+       if (!idev)
+               return -ENOENT;
+
+       iconn = find_connection(idev->connections, "hid");
+       if (!iconn)
+               return -ENOENT;
+
+       switch (psm) {
+       case L2CAP_PSM_HIDP_CTRL:
+               if (iconn->ctrl_io)
+                       return -EALREADY;
+               iconn->ctrl_io = g_io_channel_ref(io);
+               break;
+       case L2CAP_PSM_HIDP_INTR:
+               if (iconn->intr_io)
+                       return -EALREADY;
+               iconn->intr_io = g_io_channel_ref(io);
+               break;
+       }
+
+       if (iconn->intr_io && iconn->ctrl_io)
+               input_device_connadd(idev, iconn);
+
+       return 0;
+}
+
+int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst)
+{
+       struct input_device *idev = find_device(src, dst);
+       struct input_conn *iconn;
+
+       if (!idev)
+               return -ENOENT;
+
+       iconn = find_connection(idev->connections, "hid");
+       if (!iconn)
+               return -ENOENT;
+
+       if (iconn->intr_io)
+               g_io_channel_shutdown(iconn->intr_io, TRUE, NULL);
+
+       if (iconn->ctrl_io)
+               g_io_channel_shutdown(iconn->ctrl_io, TRUE, NULL);
+
+       return 0;
+}
diff --git a/input/device.h b/input/device.h
new file mode 100644 (file)
index 0000000..14c0f97
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 HSP_HS_UUID            "00001108-0000-1000-8000-00805F9B34FB"
+#define HID_UUID               "00001124-0000-1000-8000-00805f9b34fb"
+
+#define L2CAP_PSM_HIDP_CTRL    0x11
+#define L2CAP_PSM_HIDP_INTR    0x13
+
+struct input_device;
+struct input_conn;
+
+struct fake_input {
+       int             flags;
+       GIOChannel      *io;
+       int             uinput;         /* uinput socket */
+       int             rfcomm;         /* RFCOMM socket */
+       uint8_t         ch;             /* RFCOMM channel number */
+       gboolean        (*connect) (struct input_conn *iconn, GError **err);
+       int             (*disconnect) (struct input_conn *iconn);
+       void            *priv;
+       const struct input_device *idev;
+};
+
+int fake_input_register(DBusConnection *conn, struct btd_device *device,
+                       const char *path, bdaddr_t *src, bdaddr_t *dst,
+                       const char *uuid, uint8_t channel);
+int input_device_register(DBusConnection *conn, struct btd_device *device,
+                       const char *path, const bdaddr_t *src,
+                       const bdaddr_t *dst, const char *uuid,
+                       uint32_t handle, int timeout);
+int input_device_unregister(const char *path, const char *uuid);
+
+int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm,
+                                                       GIOChannel *io);
+int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst);
diff --git a/input/fakehid.c b/input/fakehid.c
new file mode 100644 (file)
index 0000000..eebca05
--- /dev/null
@@ -0,0 +1,411 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hidp.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "log.h"
+#include "device.h"
+#include "fakehid.h"
+#include "uinput.h"
+
+#define PS3_FLAGS_MASK 0xFFFFFF00
+
+enum ps3remote_special_keys {
+       PS3R_BIT_PS = 0,
+       PS3R_BIT_ENTER = 3,
+       PS3R_BIT_L2 = 8,
+       PS3R_BIT_R2 = 9,
+       PS3R_BIT_L1 = 10,
+       PS3R_BIT_R1 = 11,
+       PS3R_BIT_TRIANGLE = 12,
+       PS3R_BIT_CIRCLE = 13,
+       PS3R_BIT_CROSS = 14,
+       PS3R_BIT_SQUARE = 15,
+       PS3R_BIT_SELECT = 16,
+       PS3R_BIT_L3 = 17,
+       PS3R_BIT_R3 = 18,
+       PS3R_BIT_START = 19,
+       PS3R_BIT_UP = 20,
+       PS3R_BIT_RIGHT = 21,
+       PS3R_BIT_DOWN = 22,
+       PS3R_BIT_LEFT = 23,
+};
+
+static unsigned int ps3remote_bits[] = {
+       [PS3R_BIT_ENTER] = 0x0b,
+       [PS3R_BIT_PS] = 0x43,
+       [PS3R_BIT_SQUARE] = 0x5f,
+       [PS3R_BIT_CROSS] = 0x5e,
+       [PS3R_BIT_CIRCLE] = 0x5d,
+       [PS3R_BIT_TRIANGLE] = 0x5c,
+       [PS3R_BIT_R1] = 0x5b,
+       [PS3R_BIT_L1] = 0x5a,
+       [PS3R_BIT_R2] = 0x59,
+       [PS3R_BIT_L2] = 0x58,
+       [PS3R_BIT_LEFT] = 0x57,
+       [PS3R_BIT_DOWN] = 0x56,
+       [PS3R_BIT_RIGHT] = 0x55,
+       [PS3R_BIT_UP] = 0x54,
+       [PS3R_BIT_START] = 0x53,
+       [PS3R_BIT_R3] = 0x52,
+       [PS3R_BIT_L3] = 0x51,
+       [PS3R_BIT_SELECT] = 0x50,
+};
+
+static unsigned int ps3remote_keymap[] = {
+       [0x16] = KEY_EJECTCD,
+       [0x64] = KEY_AUDIO,
+       [0x65] = KEY_ANGLE,
+       [0x63] = KEY_SUBTITLE,
+       [0x0f] = KEY_CLEAR,
+       [0x28] = KEY_TIME,
+       [0x00] = KEY_1,
+       [0x01] = KEY_2,
+       [0x02] = KEY_3,
+       [0x03] = KEY_4,
+       [0x04] = KEY_5,
+       [0x05] = KEY_6,
+       [0x06] = KEY_7,
+       [0x07] = KEY_8,
+       [0x08] = KEY_9,
+       [0x09] = KEY_0,
+       [0x81] = KEY_RED,
+       [0x82] = KEY_GREEN,
+       [0x80] = KEY_BLUE,
+       [0x83] = KEY_YELLOW,
+       [0x70] = KEY_INFO,              /* display */
+       [0x1a] = KEY_MENU,              /* top menu */
+       [0x40] = KEY_CONTEXT_MENU,      /* pop up/menu */
+       [0x0e] = KEY_ESC,               /* return */
+       [0x5c] = KEY_OPTION,            /* options/triangle */
+       [0x5d] = KEY_BACK,              /* back/circle */
+       [0x5f] = KEY_SCREEN,            /* view/square */
+       [0x5e] = BTN_0,                 /* cross */
+       [0x54] = KEY_UP,
+       [0x56] = KEY_DOWN,
+       [0x57] = KEY_LEFT,
+       [0x55] = KEY_RIGHT,
+       [0x0b] = KEY_ENTER,
+       [0x5a] = BTN_TL,                /* L1 */
+       [0x58] = BTN_TL2,               /* L2 */
+       [0x51] = BTN_THUMBL,            /* L3 */
+       [0x5b] = BTN_TR,                /* R1 */
+       [0x59] = BTN_TR2,               /* R2 */
+       [0x52] = BTN_THUMBR,            /* R3 */
+       [0x43] = KEY_HOMEPAGE,          /* PS button */
+       [0x50] = KEY_SELECT,
+       [0x53] = BTN_START,
+       [0x33] = KEY_REWIND,            /* scan back */
+       [0x32] = KEY_PLAY,
+       [0x34] = KEY_FORWARD,           /* scan forward */
+       [0x30] = KEY_PREVIOUS,
+       [0x38] = KEY_STOP,
+       [0x31] = KEY_NEXT,
+       [0x60] = KEY_FRAMEBACK,         /* slow/step back */
+       [0x39] = KEY_PAUSE,
+       [0x61] = KEY_FRAMEFORWARD,      /* slow/step forward */
+       [0xff] = KEY_MAX,
+};
+
+static int ps3remote_decode(char *buff, int size, unsigned int *value)
+{
+       static unsigned int lastkey = 0;
+       static unsigned int lastmask = 0;
+       unsigned int i, mask;
+       int retval;
+       guint8 key;
+
+       if (size < 12) {
+               error("Got a shorter packet! (size %i)\n", size);
+               return KEY_RESERVED;
+       }
+
+       mask = (buff[2] << 16) + (buff[3] << 8) + buff[4];
+       key = buff[5];
+
+       /* first, check flags */
+       for (i = 0; i < 24; i++) {
+               if ((lastmask & (1 << i)) == (mask & (1 << i)))
+                       continue;
+               if (ps3remote_bits[i] == 0)
+                       goto error;
+               retval = ps3remote_keymap[ps3remote_bits[i]];
+               if (mask & (1 << i))
+                       /* key pressed */
+                       *value = 1;
+               else
+                       /* key released */
+                       *value = 0;
+
+               goto out;
+       }
+
+       *value = buff[11];
+       if (buff[11] == 1) {
+               retval = ps3remote_keymap[key];
+       } else
+               retval = lastkey;
+
+       if (retval == KEY_RESERVED)
+               goto error;
+       if (retval == KEY_MAX)
+               return retval;
+
+       lastkey = retval;
+
+out:
+       fflush(stdout);
+
+       lastmask = mask;
+
+       return retval;
+
+error:
+       error("ps3remote: unrecognized sequence [%#x][%#x][%#x][%#x] [%#x],"
+                       "last: [%#x][%#x][%#x][%#x]",
+                       buff[2], buff[3], buff[4], buff[5], buff[11],
+                               lastmask >> 16, lastmask >> 8 & 0xff,
+                                               lastmask & 0xff, lastkey);
+       return -1;
+}
+
+static gboolean ps3remote_event(GIOChannel *chan, GIOCondition cond,
+                               gpointer data)
+{
+       struct fake_input *fake = data;
+       struct uinput_event event;
+       unsigned int key, value = 0;
+       ssize_t size;
+       char buff[50];
+       int fd;
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       if (cond & (G_IO_HUP | G_IO_ERR)) {
+               error("Hangup or error on rfcomm server socket");
+               goto failed;
+       }
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       memset(buff, 0, sizeof(buff));
+       size = read(fd, buff, sizeof(buff));
+       if (size < 0) {
+               error("IO Channel read error");
+               goto failed;
+       }
+
+       key = ps3remote_decode(buff, size, &value);
+       if (key == KEY_RESERVED) {
+               error("Got invalid key from decode");
+               goto failed;
+       } else if (key == KEY_MAX)
+               return TRUE;
+
+       memset(&event, 0, sizeof(event));
+       gettimeofday(&event.time, NULL);
+       event.type = EV_KEY;
+       event.code = key;
+       event.value = value;
+       if (write(fake->uinput, &event, sizeof(event)) != sizeof(event)) {
+               error("Error writing to uinput device");
+               goto failed;
+       }
+
+       memset(&event, 0, sizeof(event));
+       gettimeofday(&event.time, NULL);
+       event.type = EV_SYN;
+       event.code = SYN_REPORT;
+       if (write(fake->uinput, &event, sizeof(event)) != sizeof(event)) {
+               error("Error writing to uinput device");
+               goto failed;
+       }
+
+       return TRUE;
+
+failed:
+       ioctl(fake->uinput, UI_DEV_DESTROY);
+       close(fake->uinput);
+       fake->uinput = -1;
+       g_io_channel_unref(fake->io);
+
+       return FALSE;
+}
+
+static int ps3remote_setup_uinput(struct fake_input *fake,
+                                               struct fake_hid *fake_hid)
+{
+       struct uinput_dev dev;
+       int i;
+
+       fake->uinput = open("/dev/input/uinput", O_RDWR);
+       if (fake->uinput < 0) {
+               fake->uinput = open("/dev/uinput", O_RDWR);
+               if (fake->uinput < 0) {
+                       fake->uinput = open("/dev/misc/uinput", O_RDWR);
+                       if (fake->uinput < 0) {
+                               error("Error opening uinput device file");
+                               return 1;
+                       }
+               }
+       }
+
+       memset(&dev, 0, sizeof(dev));
+       snprintf(dev.name, sizeof(dev.name), "%s", "PS3 Remote Controller");
+       dev.id.bustype = BUS_BLUETOOTH;
+       dev.id.vendor = fake_hid->vendor;
+       dev.id.product = fake_hid->product;
+
+       if (write(fake->uinput, &dev, sizeof(dev)) != sizeof(dev)) {
+               error("Error creating uinput device");
+               goto err;
+       }
+
+       /* enabling key events */
+       if (ioctl(fake->uinput, UI_SET_EVBIT, EV_KEY) < 0) {
+               error("Error enabling uinput device key events");
+               goto err;
+       }
+
+       /* enabling keys */
+       for (i = 0; i < 256; i++)
+               if (ps3remote_keymap[i] != KEY_RESERVED)
+                       if (ioctl(fake->uinput, UI_SET_KEYBIT,
+                                               ps3remote_keymap[i]) < 0) {
+                               error("Error enabling uinput key %i",
+                                                       ps3remote_keymap[i]);
+                               goto err;
+                       }
+
+       /* creating the device */
+       if (ioctl(fake->uinput, UI_DEV_CREATE) < 0) {
+               error("Error creating uinput device");
+               goto err;
+       }
+
+       return 0;
+
+err:
+       close(fake->uinput);
+       return 1;
+}
+
+static gboolean fake_hid_common_connect(struct fake_input *fake, GError **err)
+{
+       return TRUE;
+}
+
+static int fake_hid_common_disconnect(struct fake_input *fake)
+{
+       return 0;
+}
+
+static struct fake_hid fake_hid_table[] = {
+       /* Sony PS3 remote device */
+       {
+               .vendor         = 0x054c,
+               .product        = 0x0306,
+               .connect        = fake_hid_common_connect,
+               .disconnect     = fake_hid_common_disconnect,
+               .event          = ps3remote_event,
+               .setup_uinput   = ps3remote_setup_uinput,
+               .devices        = NULL,
+       },
+
+       { },
+};
+
+static inline int fake_hid_match_device(uint16_t vendor, uint16_t product,
+                                                       struct fake_hid *fhid)
+{
+       return vendor == fhid->vendor && product == fhid->product;
+}
+
+struct fake_hid *get_fake_hid(uint16_t vendor, uint16_t product)
+{
+       int i;
+
+       for (i = 0; fake_hid_table[i].vendor != 0; i++)
+               if (fake_hid_match_device(vendor, product, &fake_hid_table[i]))
+                       return &fake_hid_table[i];
+
+       return NULL;
+}
+
+struct fake_input *fake_hid_connadd(struct fake_input *fake,
+                                               GIOChannel *intr_io,
+                                               struct fake_hid *fake_hid)
+{
+       GList *l;
+       struct fake_input *old = NULL;
+
+       /* Look for an already setup device */
+       for (l = fake_hid->devices; l != NULL; l = l->next) {
+               old = l->data;
+               if (old->idev == fake->idev) {
+                       g_free(fake);
+                       fake = old;
+                       fake_hid->connect(fake, NULL);
+                       break;
+               }
+               old = NULL;
+       }
+
+       /* New device? Add it to the list of known devices,
+        * and create the uinput necessary */
+       if (old == NULL) {
+               if (fake_hid->setup_uinput(fake, fake_hid)) {
+                       error("Error setting up uinput");
+                       g_free(fake);
+                       return NULL;
+               }
+               fake_hid->devices = g_list_append(fake_hid->devices, fake);
+       }
+
+       fake->io = g_io_channel_ref(intr_io);
+       g_io_channel_set_close_on_unref(fake->io, TRUE);
+       g_io_add_watch(fake->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                       (GIOFunc) fake_hid->event, fake);
+
+       return fake;
+}
diff --git a/input/fakehid.h b/input/fakehid.h
new file mode 100644 (file)
index 0000000..3a5d7c4
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 fake_hid;
+struct fake_input;
+
+struct fake_hid {
+       uint16_t vendor;
+       uint16_t product;
+       gboolean (*connect) (struct fake_input *fake_input, GError **err);
+       int (*disconnect) (struct fake_input *fake_input);
+       gboolean (*event) (GIOChannel *chan, GIOCondition cond, gpointer data);
+       int (*setup_uinput) (struct fake_input *fake, struct fake_hid *fake_hid);
+       GList *devices;
+};
+
+struct fake_hid *get_fake_hid(uint16_t vendor, uint16_t product);
+
+struct fake_input *fake_hid_connadd(struct fake_input *fake, GIOChannel *intr_io,
+                                               struct fake_hid *fake_hid);
diff --git a/input/input.conf b/input/input.conf
new file mode 100644 (file)
index 0000000..abfb64f
--- /dev/null
@@ -0,0 +1,9 @@
+# Configuration file for the input service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+
+# Set idle timeout (in minutes) before the connection will
+# be disconnect (defaults to 0 for no timeout)
+#IdleTimeout=30
diff --git a/input/main.c b/input/main.c
new file mode 100644 (file)
index 0000000..e165ab4
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "manager.h"
+
+static GKeyFile *load_config_file(const char *file)
+{
+       GKeyFile *keyfile;
+       GError *err = NULL;
+
+       keyfile = g_key_file_new();
+
+       if (!g_key_file_load_from_file(keyfile, file, 0, &err)) {
+               error("Parsing %s failed: %s", file, err->message);
+               g_error_free(err);
+               g_key_file_free(keyfile);
+               return NULL;
+       }
+
+       return keyfile;
+}
+
+static DBusConnection *connection;
+
+static int input_init(void)
+{
+       GKeyFile *config;
+
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (connection == NULL)
+               return -EIO;
+
+       config = load_config_file(CONFIGDIR "/input.conf");
+
+       if (input_manager_init(connection, config) < 0) {
+               dbus_connection_unref(connection);
+               return -EIO;
+       }
+
+       if (config)
+               g_key_file_free(config);
+
+       return 0;
+}
+
+static void input_exit(void)
+{
+       input_manager_exit();
+
+       dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(input, VERSION,
+                       BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, input_init, input_exit)
diff --git a/input/manager.c b/input/manager.c
new file mode 100644 (file)
index 0000000..a98a080
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <gdbus.h>
+
+#include "log.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "device.h"
+#include "server.h"
+#include "manager.h"
+
+static int idle_timeout = 0;
+
+static DBusConnection *connection = NULL;
+static GSList *adapters = NULL;
+
+static void input_remove(struct btd_device *device, const char *uuid)
+{
+       const gchar *path = device_get_path(device);
+
+       DBG("path %s", path);
+
+       input_device_unregister(path, uuid);
+}
+
+static int hid_device_probe(struct btd_device *device, GSList *uuids)
+{
+       struct btd_adapter *adapter = device_get_adapter(device);
+       const gchar *path = device_get_path(device);
+       const sdp_record_t *rec = btd_device_get_record(device, uuids->data);
+       bdaddr_t src, dst;
+
+       DBG("path %s", path);
+
+       if (!rec)
+               return -1;
+
+       adapter_get_address(adapter, &src);
+       device_get_address(device, &dst);
+
+       return input_device_register(connection, device, path, &src, &dst,
+                               HID_UUID, rec->handle, idle_timeout * 60);
+}
+
+static void hid_device_remove(struct btd_device *device)
+{
+       input_remove(device, HID_UUID);
+}
+
+static int headset_probe(struct btd_device *device, GSList *uuids)
+{
+       struct btd_adapter *adapter = device_get_adapter(device);
+       const gchar *path = device_get_path(device);
+       const sdp_record_t *record;
+       sdp_list_t *protos;
+       uint8_t ch;
+       bdaddr_t src, dst;
+
+       DBG("path %s", path);
+
+       if (!g_slist_find_custom(uuids, HSP_HS_UUID,
+                                       (GCompareFunc) strcasecmp))
+               return -EINVAL;
+
+       record = btd_device_get_record(device, uuids->data);
+
+       if (!record || sdp_get_access_protos(record, &protos) < 0) {
+               error("Invalid record");
+               return -EINVAL;
+       }
+
+       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("Invalid RFCOMM channel");
+               return -EINVAL;
+       }
+
+       adapter_get_address(adapter, &src);
+       device_get_address(device, &dst);
+
+       return fake_input_register(connection, device, path, &src, &dst,
+                               HSP_HS_UUID, ch);
+}
+
+static void headset_remove(struct btd_device *device)
+{
+       input_remove(device, HSP_HS_UUID);
+}
+
+static int hid_server_probe(struct btd_adapter *adapter)
+{
+       bdaddr_t src;
+       int ret;
+
+       adapter_get_address(adapter, &src);
+
+       ret = server_start(&src);
+       if (ret < 0)
+               return ret;
+
+       adapters = g_slist_append(adapters, btd_adapter_ref(adapter));
+
+       return 0;
+}
+
+static void hid_server_remove(struct btd_adapter *adapter)
+{
+       bdaddr_t src;
+
+       adapter_get_address(adapter, &src);
+
+       server_stop(&src);
+
+       adapters = g_slist_remove(adapters, adapter);
+       btd_adapter_unref(adapter);
+}
+
+static struct btd_device_driver input_hid_driver = {
+       .name   = "input-hid",
+       .uuids  = BTD_UUIDS(HID_UUID),
+       .probe  = hid_device_probe,
+       .remove = hid_device_remove,
+};
+
+static struct btd_device_driver input_headset_driver = {
+       .name   = "input-headset",
+       .uuids  = BTD_UUIDS(HSP_HS_UUID),
+       .probe  = headset_probe,
+       .remove = headset_remove,
+};
+
+static struct btd_adapter_driver input_server_driver = {
+       .name   = "input-server",
+       .probe  = hid_server_probe,
+       .remove = hid_server_remove,
+};
+
+int input_manager_init(DBusConnection *conn, GKeyFile *config)
+{
+       GError *err = NULL;
+
+       if (config) {
+               idle_timeout = g_key_file_get_integer(config, "General",
+                                               "IdleTimeout", &err);
+               if (err) {
+                       DBG("input.conf: %s", err->message);
+                       g_error_free(err);
+               }
+       }
+
+       connection = dbus_connection_ref(conn);
+
+       btd_register_adapter_driver(&input_server_driver);
+
+       btd_register_device_driver(&input_hid_driver);
+       btd_register_device_driver(&input_headset_driver);
+
+       return 0;
+}
+
+void input_manager_exit(void)
+{
+       btd_unregister_device_driver(&input_hid_driver);
+       btd_unregister_device_driver(&input_headset_driver);
+
+       btd_unregister_adapter_driver(&input_server_driver);
+
+       dbus_connection_unref(connection);
+
+       connection = NULL;
+}
diff --git a/input/manager.h b/input/manager.h
new file mode 100644 (file)
index 0000000..7b93c5b
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 input_manager_init(DBusConnection *conn, GKeyFile *config);
+void input_manager_exit(void);
diff --git a/input/server.c b/input/server.c
new file mode 100644 (file)
index 0000000..d98018b
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "log.h"
+
+#include "glib-helper.h"
+#include "btio.h"
+#include "adapter.h"
+#include "device.h"
+#include "server.h"
+
+static GSList *servers = NULL;
+struct input_server {
+       bdaddr_t src;
+       GIOChannel *ctrl;
+       GIOChannel *intr;
+       GIOChannel *confirm;
+};
+
+static gint server_cmp(gconstpointer s, gconstpointer user_data)
+{
+       const struct input_server *server = s;
+       const bdaddr_t *src = user_data;
+
+       return bacmp(&server->src, src);
+}
+
+static void connect_event_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+       uint16_t psm;
+       bdaddr_t src, dst;
+       GError *gerr = NULL;
+       int ret;
+
+       if (err) {
+               error("%s", err->message);
+               return;
+       }
+
+       bt_io_get(chan, BT_IO_L2CAP, &gerr,
+                       BT_IO_OPT_SOURCE_BDADDR, &src,
+                       BT_IO_OPT_DEST_BDADDR, &dst,
+                       BT_IO_OPT_PSM, &psm,
+                       BT_IO_OPT_INVALID);
+       if (gerr) {
+               error("%s", gerr->message);
+               g_error_free(gerr);
+               g_io_channel_shutdown(chan, TRUE, NULL);
+               return;
+       }
+
+       DBG("Incoming connection on PSM %d", psm);
+
+       ret = input_device_set_channel(&src, &dst, psm, chan);
+       if (ret == 0)
+               return;
+
+       /* Send unplug virtual cable to unknown devices */
+       if (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL) {
+               unsigned char unplug = 0x15;
+               int err, sk = g_io_channel_unix_get_fd(chan);
+               err = write(sk, &unplug, sizeof(unplug));
+       }
+
+       g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static void auth_callback(DBusError *derr, void *user_data)
+{
+       struct input_server *server = user_data;
+       bdaddr_t src, dst;
+       GError *err = NULL;
+
+       bt_io_get(server->confirm, BT_IO_L2CAP, &err,
+                       BT_IO_OPT_SOURCE_BDADDR, &src,
+                       BT_IO_OPT_DEST_BDADDR, &dst,
+                       BT_IO_OPT_INVALID);
+       if (err) {
+               error("%s", err->message);
+               g_error_free(err);
+               goto reject;
+       }
+
+       if (derr) {
+               error("Access denied: %s", derr->message);
+               goto reject;
+       }
+
+       if (!bt_io_accept(server->confirm, connect_event_cb, server,
+                               NULL, &err)) {
+               error("bt_io_accept: %s", err->message);
+               g_error_free(err);
+               goto reject;
+       }
+
+       g_io_channel_unref(server->confirm);
+       server->confirm = NULL;
+
+       return;
+
+reject:
+       g_io_channel_shutdown(server->confirm, TRUE, NULL);
+       g_io_channel_unref(server->confirm);
+       server->confirm = NULL;
+       input_device_close_channels(&src, &dst);
+}
+
+static void confirm_event_cb(GIOChannel *chan, gpointer user_data)
+{
+       struct input_server *server = user_data;
+       bdaddr_t src, dst;
+       GError *err = NULL;
+       int ret;
+
+       bt_io_get(chan, BT_IO_L2CAP, &err,
+                       BT_IO_OPT_SOURCE_BDADDR, &src,
+                       BT_IO_OPT_DEST_BDADDR, &dst,
+                       BT_IO_OPT_INVALID);
+       if (err) {
+               error("%s", err->message);
+               g_error_free(err);
+               goto drop;
+       }
+
+       if (server->confirm) {
+               error("Refusing connection: setup in progress");
+               goto drop;
+       }
+
+       server->confirm = g_io_channel_ref(chan);
+
+       ret = btd_request_authorization(&src, &dst, HID_UUID,
+                                       auth_callback, server);
+       if (ret == 0)
+               return;
+
+       g_io_channel_unref(server->confirm);
+       server->confirm = NULL;
+
+drop:
+       input_device_close_channels(&src, &dst);
+       g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+int server_start(const bdaddr_t *src)
+{
+       struct input_server *server;
+       GError *err = NULL;
+
+       server = g_new0(struct input_server, 1);
+       bacpy(&server->src, src);
+
+       server->ctrl = bt_io_listen(BT_IO_L2CAP, connect_event_cb, NULL,
+                               server, NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, src,
+                               BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL,
+                               BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+                               BT_IO_OPT_INVALID);
+       if (!server->ctrl) {
+               error("Failed to listen on control channel");
+               g_error_free(err);
+               g_free(server);
+               return -1;
+       }
+
+       server->intr = bt_io_listen(BT_IO_L2CAP, NULL, confirm_event_cb,
+                               server, NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, src,
+                               BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR,
+                               BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+                               BT_IO_OPT_INVALID);
+       if (!server->intr) {
+               error("Failed to listen on interrupt channel");
+               g_io_channel_unref(server->ctrl);
+               g_error_free(err);
+               g_free(server);
+               return -1;
+       }
+
+       servers = g_slist_append(servers, server);
+
+       return 0;
+}
+
+void server_stop(const bdaddr_t *src)
+{
+       struct input_server *server;
+       GSList *l;
+
+       l = g_slist_find_custom(servers, src, server_cmp);
+       if (!l)
+               return;
+
+       server = l->data;
+
+       g_io_channel_shutdown(server->intr, TRUE, NULL);
+       g_io_channel_unref(server->intr);
+
+       g_io_channel_shutdown(server->ctrl, TRUE, NULL);
+       g_io_channel_unref(server->ctrl);
+
+       servers = g_slist_remove(servers, server);
+       g_free(server);
+}
diff --git a/input/server.h b/input/server.h
new file mode 100644 (file)
index 0000000..74159bb
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 server_start(const bdaddr_t *src);
+void server_stop(const bdaddr_t *src);
diff --git a/lib/bluetooth.c b/lib/bluetooth.c
new file mode 100644 (file)
index 0000000..72ee456
--- /dev/null
@@ -0,0 +1,470 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "bluetooth.h"
+#include "hci.h"
+
+void baswap(bdaddr_t *dst, const bdaddr_t *src)
+{
+       register unsigned char *d = (unsigned char *) dst;
+       register const unsigned char *s = (const unsigned char *) src;
+       register int i;
+
+       for (i = 0; i < 6; i++)
+               d[i] = s[5-i];
+}
+
+char *batostr(const bdaddr_t *ba)
+{
+       char *str = bt_malloc(18);
+       if (!str)
+               return NULL;
+
+       sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+               ba->b[0], ba->b[1], ba->b[2],
+               ba->b[3], ba->b[4], ba->b[5]);
+
+       return str;
+}
+
+bdaddr_t *strtoba(const char *str)
+{
+       bdaddr_t b;
+       bdaddr_t *ba = bt_malloc(sizeof(*ba));
+
+       if (ba) {
+               str2ba(str, &b);
+               baswap(ba, &b);
+       }
+
+       return ba;
+}
+
+int ba2str(const bdaddr_t *ba, char *str)
+{
+       return sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+               ba->b[5], ba->b[4], ba->b[3], ba->b[2], ba->b[1], ba->b[0]);
+}
+
+int str2ba(const char *str, bdaddr_t *ba)
+{
+       bdaddr_t b;
+       int i;
+
+       if (bachk(str) < 0) {
+               memset(ba, 0, sizeof(*ba));
+               return -1;
+       }
+
+       for (i = 0; i < 6; i++, str += 3)
+               b.b[i] = strtol(str, NULL, 16);
+
+       baswap(ba, &b);
+
+       return 0;
+}
+
+int ba2oui(const bdaddr_t *ba, char *str)
+{
+       return sprintf(str, "%2.2X-%2.2X-%2.2X", ba->b[5], ba->b[4], ba->b[3]);
+}
+
+int bachk(const char *str)
+{
+       if (!str)
+               return -1;
+
+       if (strlen(str) != 17)
+               return -1;
+
+       while (*str) {
+               if (!isxdigit(*str++))
+                       return -1;
+
+               if (!isxdigit(*str++))
+                       return -1;
+
+               if (*str == 0)
+                       break;
+
+               if (*str++ != ':')
+                       return -1;
+       }
+
+       return 0;
+}
+
+int baprintf(const char *format, ...)
+{
+       va_list ap;
+       int len;
+
+       va_start(ap, format);
+       len = vprintf(format, ap);
+       va_end(ap);
+
+       return len;
+}
+
+int bafprintf(FILE *stream, const char *format, ...)
+{
+       va_list ap;
+       int len;
+
+       va_start(ap, format);
+       len = vfprintf(stream, format, ap);
+       va_end(ap);
+
+       return len;
+}
+
+int basprintf(char *str, const char *format, ...)
+{
+       va_list ap;
+       int len;
+
+       va_start(ap, format);
+       len = vsnprintf(str, (~0U) >> 1, format, ap);
+       va_end(ap);
+
+       return len;
+}
+
+int basnprintf(char *str, size_t size, const char *format, ...)
+{
+       va_list ap;
+       int len;
+
+       va_start(ap, format);
+       len = vsnprintf(str, size, format, ap);
+       va_end(ap);
+
+       return len;
+}
+
+void *bt_malloc(size_t size)
+{
+       return malloc(size);
+}
+
+void bt_free(void *ptr)
+{
+       free(ptr);
+}
+
+/* Bluetooth error codes to Unix errno mapping */
+int bt_error(uint16_t code)
+{
+       switch (code) {
+       case 0:
+               return 0;
+       case HCI_UNKNOWN_COMMAND:
+               return EBADRQC;
+       case HCI_NO_CONNECTION:
+               return ENOTCONN;
+       case HCI_HARDWARE_FAILURE:
+               return EIO;
+       case HCI_PAGE_TIMEOUT:
+               return EHOSTDOWN;
+       case HCI_AUTHENTICATION_FAILURE:
+               return EACCES;
+       case HCI_PIN_OR_KEY_MISSING:
+               return EINVAL;
+       case HCI_MEMORY_FULL:
+               return ENOMEM;
+       case HCI_CONNECTION_TIMEOUT:
+               return ETIMEDOUT;
+       case HCI_MAX_NUMBER_OF_CONNECTIONS:
+       case HCI_MAX_NUMBER_OF_SCO_CONNECTIONS:
+               return EMLINK;
+       case HCI_ACL_CONNECTION_EXISTS:
+               return EALREADY;
+       case HCI_COMMAND_DISALLOWED:
+       case HCI_TRANSACTION_COLLISION:
+       case HCI_ROLE_SWITCH_PENDING:
+               return EBUSY;
+       case HCI_REJECTED_LIMITED_RESOURCES:
+       case HCI_REJECTED_PERSONAL:
+       case HCI_QOS_REJECTED:
+               return ECONNREFUSED;
+       case HCI_HOST_TIMEOUT:
+               return ETIMEDOUT;
+       case HCI_UNSUPPORTED_FEATURE:
+       case HCI_QOS_NOT_SUPPORTED:
+       case HCI_PAIRING_NOT_SUPPORTED:
+       case HCI_CLASSIFICATION_NOT_SUPPORTED:
+       case HCI_UNSUPPORTED_LMP_PARAMETER_VALUE:
+       case HCI_PARAMETER_OUT_OF_RANGE:
+       case HCI_QOS_UNACCEPTABLE_PARAMETER:
+               return EOPNOTSUPP;
+       case HCI_INVALID_PARAMETERS:
+       case HCI_SLOT_VIOLATION:
+               return EINVAL;
+       case HCI_OE_USER_ENDED_CONNECTION:
+       case HCI_OE_LOW_RESOURCES:
+       case HCI_OE_POWER_OFF:
+               return ECONNRESET;
+       case HCI_CONNECTION_TERMINATED:
+               return ECONNABORTED;
+       case HCI_REPEATED_ATTEMPTS:
+               return ELOOP;
+       case HCI_REJECTED_SECURITY:
+       case HCI_PAIRING_NOT_ALLOWED:
+       case HCI_INSUFFICIENT_SECURITY:
+               return EACCES;
+       case HCI_UNSUPPORTED_REMOTE_FEATURE:
+               return EPROTONOSUPPORT;
+       case HCI_SCO_OFFSET_REJECTED:
+               return ECONNREFUSED;
+       case HCI_UNKNOWN_LMP_PDU:
+       case HCI_INVALID_LMP_PARAMETERS:
+       case HCI_LMP_ERROR_TRANSACTION_COLLISION:
+       case HCI_LMP_PDU_NOT_ALLOWED:
+       case HCI_ENCRYPTION_MODE_NOT_ACCEPTED:
+               return EPROTO;
+       default:
+               return ENOSYS;
+       }
+}
+
+char *bt_compidtostr(int compid)
+{
+       switch (compid) {
+       case 0:
+               return "Ericsson Technology Licensing";
+       case 1:
+               return "Nokia Mobile Phones";
+       case 2:
+               return "Intel Corp.";
+       case 3:
+               return "IBM Corp.";
+       case 4:
+               return "Toshiba Corp.";
+       case 5:
+               return "3Com";
+       case 6:
+               return "Microsoft";
+       case 7:
+               return "Lucent";
+       case 8:
+               return "Motorola";
+       case 9:
+               return "Infineon Technologies AG";
+       case 10:
+               return "Cambridge Silicon Radio";
+       case 11:
+               return "Silicon Wave";
+       case 12:
+               return "Digianswer A/S";
+       case 13:
+               return "Texas Instruments Inc.";
+       case 14:
+               return "Parthus Technologies Inc.";
+       case 15:
+               return "Broadcom Corporation";
+       case 16:
+               return "Mitel Semiconductor";
+       case 17:
+               return "Widcomm, Inc.";
+       case 18:
+               return "Zeevo, Inc.";
+       case 19:
+               return "Atmel Corporation";
+       case 20:
+               return "Mitsubishi Electric Corporation";
+       case 21:
+               return "RTX Telecom A/S";
+       case 22:
+               return "KC Technology Inc.";
+       case 23:
+               return "Newlogic";
+       case 24:
+               return "Transilica, Inc.";
+       case 25:
+               return "Rohde & Schwartz GmbH & Co. KG";
+       case 26:
+               return "TTPCom Limited";
+       case 27:
+               return "Signia Technologies, Inc.";
+       case 28:
+               return "Conexant Systems Inc.";
+       case 29:
+               return "Qualcomm";
+       case 30:
+               return "Inventel";
+       case 31:
+               return "AVM Berlin";
+       case 32:
+               return "BandSpeed, Inc.";
+       case 33:
+               return "Mansella Ltd";
+       case 34:
+               return "NEC Corporation";
+       case 35:
+               return "WavePlus Technology Co., Ltd.";
+       case 36:
+               return "Alcatel";
+       case 37:
+               return "Philips Semiconductors";
+       case 38:
+               return "C Technologies";
+       case 39:
+               return "Open Interface";
+       case 40:
+               return "R F Micro Devices";
+       case 41:
+               return "Hitachi Ltd";
+       case 42:
+               return "Symbol Technologies, Inc.";
+       case 43:
+               return "Tenovis";
+       case 44:
+               return "Macronix International Co. Ltd.";
+       case 45:
+               return "GCT Semiconductor";
+       case 46:
+               return "Norwood Systems";
+       case 47:
+               return "MewTel Technology Inc.";
+       case 48:
+               return "ST Microelectronics";
+       case 49:
+               return "Synopsys";
+       case 50:
+               return "Red-M (Communications) Ltd";
+       case 51:
+               return "Commil Ltd";
+       case 52:
+               return "Computer Access Technology Corporation (CATC)";
+       case 53:
+               return "Eclipse (HQ Espana) S.L.";
+       case 54:
+               return "Renesas Technology Corp.";
+       case 55:
+               return "Mobilian Corporation";
+       case 56:
+               return "Terax";
+       case 57:
+               return "Integrated System Solution Corp.";
+       case 58:
+               return "Matsushita Electric Industrial Co., Ltd.";
+       case 59:
+               return "Gennum Corporation";
+       case 60:
+               return "Research In Motion";
+       case 61:
+               return "IPextreme, Inc.";
+       case 62:
+               return "Systems and Chips, Inc";
+       case 63:
+               return "Bluetooth SIG, Inc";
+       case 64:
+               return "Seiko Epson Corporation";
+       case 65:
+               return "Integrated Silicon Solution Taiwain, Inc.";
+       case 66:
+               return "CONWISE Technology Corporation Ltd";
+       case 67:
+               return "PARROT SA";
+       case 68:
+               return "Socket Communications";
+       case 69:
+               return "Atheros Communications, Inc.";
+       case 70:
+               return "MediaTek, Inc.";
+       case 71:
+               return "Bluegiga";
+       case 72:
+               return "Marvell Technology Group Ltd.";
+       case 73:
+               return "3DSP Corporation";
+       case 74:
+               return "Accel Semiconductor Ltd.";
+       case 75:
+               return "Continental Automotive Systems";
+       case 76:
+               return "Apple, Inc.";
+       case 77:
+               return "Staccato Communications, Inc.";
+       case 78:
+               return "Avago Technologies";
+       case 79:
+               return "APT Ltd.";
+       case 80:
+               return "SiRF Technology, Inc.";
+       case 81:
+               return "Tzero Technologies, Inc.";
+       case 82:
+               return "J&M Corporation";
+       case 83:
+               return "Free2move AB";
+       case 84:
+               return "3DiJoy Corporation";
+       case 85:
+               return "Plantronics, Inc.";
+       case 86:
+               return "Sony Ericsson Mobile Communications";
+       case 87:
+               return "Harman International Industries, Inc.";
+       case 88:
+               return "Vizio, Inc.";
+       case 89:
+               return "Nordic Semiconductor ASA";
+       case 90:
+               return "EM Microelectronic-Marin SA";
+       case 91:
+               return "Ralink Technology Corporation";
+       case 92:
+               return "Belkin International, Inc.";
+       case 93:
+               return "Realtek Semiconductor Corporation";
+       case 94:
+               return "Stonestreet One, LLC";
+       case 95:
+               return "Wicentric, Inc.";
+       case 96:
+               return "RivieraWaves S.A.S";
+       case 97:
+               return "RDA Microelectronics";
+       case 98:
+               return "Gibson Guitars";
+       case 65535:
+               return "internal use";
+       default:
+               return "not assigned";
+       }
+}
diff --git a/lib/bluetooth.h b/lib/bluetooth.h
new file mode 100644 (file)
index 0000000..738e07a
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __BLUETOOTH_H
+#define __BLUETOOTH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <endian.h>
+#include <byteswap.h>
+#include <netinet/in.h>
+
+#ifndef AF_BLUETOOTH
+#define AF_BLUETOOTH   31
+#define PF_BLUETOOTH   AF_BLUETOOTH
+#endif
+
+#define BTPROTO_L2CAP  0
+#define BTPROTO_HCI    1
+#define BTPROTO_SCO    2
+#define BTPROTO_RFCOMM 3
+#define BTPROTO_BNEP   4
+#define BTPROTO_CMTP   5
+#define BTPROTO_HIDP   6
+#define BTPROTO_AVDTP  7
+
+#define SOL_HCI                0
+#define SOL_L2CAP      6
+#define SOL_SCO                17
+#define SOL_RFCOMM     18
+
+#ifndef SOL_BLUETOOTH
+#define SOL_BLUETOOTH  274
+#endif
+
+#define BT_SECURITY    4
+struct bt_security {
+       uint8_t level;
+};
+#define BT_SECURITY_SDP                0
+#define BT_SECURITY_LOW                1
+#define BT_SECURITY_MEDIUM     2
+#define BT_SECURITY_HIGH       3
+
+#define BT_DEFER_SETUP 7
+
+#define BT_FLUSHABLE   8
+
+#define BT_FLUSHABLE_OFF       0
+#define BT_FLUSHABLE_ON                1
+
+/* Connection and socket states */
+enum {
+       BT_CONNECTED = 1, /* Equal to TCP_ESTABLISHED to make net code happy */
+       BT_OPEN,
+       BT_BOUND,
+       BT_LISTEN,
+       BT_CONNECT,
+       BT_CONNECT2,
+       BT_CONFIG,
+       BT_DISCONN,
+       BT_CLOSED
+};
+
+/* Byte order conversions */
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define htobs(d)  (d)
+#define htobl(d)  (d)
+#define btohs(d)  (d)
+#define btohl(d)  (d)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define htobs(d)  bswap_16(d)
+#define htobl(d)  bswap_32(d)
+#define btohs(d)  bswap_16(d)
+#define btohl(d)  bswap_32(d)
+#else
+#error "Unknown byte order"
+#endif
+
+/* Bluetooth unaligned access */
+#define bt_get_unaligned(ptr)                  \
+({                                             \
+       struct __attribute__((packed)) {        \
+               typeof(*(ptr)) __v;             \
+       } *__p = (void *) (ptr);                \
+       __p->__v;                               \
+})
+
+#define bt_put_unaligned(val, ptr)             \
+do {                                           \
+       struct __attribute__((packed)) {        \
+               typeof(*(ptr)) __v;             \
+       } *__p = (void *) (ptr);                \
+       __p->__v = (val);                       \
+} while(0)
+
+/* BD Address */
+typedef struct {
+       uint8_t b[6];
+} __attribute__((packed)) bdaddr_t;
+
+#define BDADDR_ANY   (&(bdaddr_t) {{0, 0, 0, 0, 0, 0}})
+#define BDADDR_ALL   (&(bdaddr_t) {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}})
+#define BDADDR_LOCAL (&(bdaddr_t) {{0, 0, 0, 0xff, 0xff, 0xff}})
+
+/* Copy, swap, convert BD Address */
+static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2)
+{
+       return memcmp(ba1, ba2, sizeof(bdaddr_t));
+}
+static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src)
+{
+       memcpy(dst, src, sizeof(bdaddr_t));
+}
+
+void baswap(bdaddr_t *dst, const bdaddr_t *src);
+bdaddr_t *strtoba(const char *str);
+char *batostr(const bdaddr_t *ba);
+int ba2str(const bdaddr_t *ba, char *str);
+int str2ba(const char *str, bdaddr_t *ba);
+int ba2oui(const bdaddr_t *ba, char *oui);
+int bachk(const char *str);
+
+int baprintf(const char *format, ...);
+int bafprintf(FILE *stream, const char *format, ...);
+int basprintf(char *str, const char *format, ...);
+int basnprintf(char *str, size_t size, const char *format, ...);
+
+void *bt_malloc(size_t size);
+void bt_free(void *ptr);
+
+int bt_error(uint16_t code);
+char *bt_compidtostr(int id);
+
+typedef struct {
+       uint8_t data[16];
+} uint128_t;
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+
+#define ntoh64(x) (x)
+
+static inline void ntoh128(const uint128_t *src, uint128_t *dst)
+{
+       memcpy(dst, src, sizeof(uint128_t));
+}
+
+static inline void btoh128(const uint128_t *src, uint128_t *dst)
+{
+       int i;
+
+       for (i = 0; i < 16; i++)
+               dst->data[15 - i] = src->data[i];
+}
+
+#else
+
+static inline uint64_t ntoh64(uint64_t n)
+{
+       uint64_t h;
+       uint64_t tmp = ntohl(n & 0x00000000ffffffff);
+
+       h = ntohl(n >> 32);
+       h |= tmp << 32;
+
+       return h;
+}
+
+static inline void ntoh128(const uint128_t *src, uint128_t *dst)
+{
+       int i;
+
+       for (i = 0; i < 16; i++)
+               dst->data[15 - i] = src->data[i];
+}
+
+static inline void btoh128(const uint128_t *src, uint128_t *dst)
+{
+       memcpy(dst, src, sizeof(uint128_t));
+}
+
+#endif
+
+#define hton64(x)     ntoh64(x)
+#define hton128(x, y) ntoh128(x, y)
+#define htob128(x, y) btoh128(x, y)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __BLUETOOTH_H */
diff --git a/lib/bnep.h b/lib/bnep.h
new file mode 100644 (file)
index 0000000..2bbfb17
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __BNEP_H
+#define __BNEP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <bluetooth/bluetooth.h>
+
+#ifndef ETH_ALEN
+#define ETH_ALEN       6               /* from <net/ethernet.h> */
+#endif
+
+/* BNEP UUIDs */
+#define BNEP_BASE_UUID 0x0000000000001000800000805F9B34FB
+#define BNEP_UUID16    0x02
+#define BNEP_UUID32    0x04
+#define BNEP_UUID128   0x16
+
+#define BNEP_SVC_PANU  0x1115
+#define BNEP_SVC_NAP   0x1116
+#define BNEP_SVC_GN    0x1117
+
+/* BNEP packet types */
+#define BNEP_GENERAL               0x00
+#define BNEP_CONTROL               0x01
+#define BNEP_COMPRESSED            0x02
+#define BNEP_COMPRESSED_SRC_ONLY   0x03
+#define BNEP_COMPRESSED_DST_ONLY   0x04
+
+/* BNEP control types */
+#define BNEP_CMD_NOT_UNDERSTOOD    0x00
+#define BNEP_SETUP_CONN_REQ        0x01
+#define BNEP_SETUP_CONN_RSP        0x02
+#define BNEP_FILTER_NET_TYPE_SET   0x03
+#define BNEP_FILTER_NET_TYPE_RSP   0x04
+#define BNEP_FILTER_MULT_ADDR_SET  0x05
+#define BNEP_FILTER_MULT_ADDR_RSP  0x06
+
+/* BNEP response messages */
+#define BNEP_SUCCESS               0x00
+
+#define BNEP_CONN_INVALID_DST      0x01
+#define BNEP_CONN_INVALID_SRC      0x02
+#define BNEP_CONN_INVALID_SVC      0x03
+#define BNEP_CONN_NOT_ALLOWED      0x04
+
+#define BNEP_FILTER_UNSUPPORTED_REQ    0x01
+#define BNEP_FILTER_INVALID_RANGE      0x02
+#define BNEP_FILTER_INVALID_MCADDR     0x02
+#define BNEP_FILTER_LIMIT_REACHED      0x03
+#define BNEP_FILTER_DENIED_SECURITY    0x04
+
+/* L2CAP settings */
+#define BNEP_MTU         1691
+#define BNEP_FLUSH_TO    0xffff
+#define BNEP_CONNECT_TO  15
+#define BNEP_FILTER_TO   15
+
+#ifndef BNEP_PSM
+#define BNEP_PSM        0x0f
+#endif
+
+/* BNEP headers */
+#define BNEP_TYPE_MASK  0x7f
+#define BNEP_EXT_HEADER         0x80
+
+struct bnep_setup_conn_req {
+       uint8_t  type;
+       uint8_t  ctrl;
+       uint8_t  uuid_size;
+       uint8_t  service[0];
+} __attribute__((packed));
+
+struct bnep_set_filter_req {
+       uint8_t  type;
+       uint8_t  ctrl;
+       uint16_t len;
+       uint8_t  list[0];
+} __attribute__((packed));
+
+struct bnep_control_rsp {
+       uint8_t  type;
+       uint8_t  ctrl;
+       uint16_t resp;
+} __attribute__((packed));
+
+struct bnep_ext_hdr {
+       uint8_t  type;
+       uint8_t  len;
+       uint8_t  data[0];
+} __attribute__((packed));
+
+/* BNEP ioctl defines */
+#define BNEPCONNADD    _IOW('B', 200, int)
+#define BNEPCONNDEL    _IOW('B', 201, int)
+#define BNEPGETCONNLIST        _IOR('B', 210, int)
+#define BNEPGETCONNINFO        _IOR('B', 211, int)
+
+struct bnep_connadd_req {
+       int      sock;          /* Connected socket */
+       uint32_t flags;
+       uint16_t role;
+       char     device[16];    /* Name of the Ethernet device */
+};
+
+struct bnep_conndel_req {
+       uint32_t flags;
+       uint8_t  dst[ETH_ALEN];
+};
+
+struct bnep_conninfo {
+       uint32_t flags;
+       uint16_t role;
+       uint16_t state;
+       uint8_t  dst[ETH_ALEN];
+       char     device[16];
+};
+
+struct bnep_connlist_req {
+       uint32_t cnum;
+       struct bnep_conninfo *ci;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __BNEP_H */
diff --git a/lib/cmtp.h b/lib/cmtp.h
new file mode 100644 (file)
index 0000000..ce937bd
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __CMTP_H
+#define __CMTP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* CMTP defaults */
+#define CMTP_MINIMUM_MTU 152
+#define CMTP_DEFAULT_MTU 672
+
+/* CMTP ioctl defines */
+#define CMTPCONNADD    _IOW('C', 200, int)
+#define CMTPCONNDEL    _IOW('C', 201, int)
+#define CMTPGETCONNLIST        _IOR('C', 210, int)
+#define CMTPGETCONNINFO        _IOR('C', 211, int)
+
+#define CMTP_LOOPBACK  0
+
+struct cmtp_connadd_req {
+       int sock;       /* Connected socket */
+       uint32_t flags;
+};
+
+struct cmtp_conndel_req {
+       bdaddr_t bdaddr;
+       uint32_t flags;
+};
+
+struct cmtp_conninfo {
+       bdaddr_t bdaddr;
+       uint32_t flags;
+       uint16_t state;
+       int      num;
+};
+
+struct cmtp_connlist_req {
+       uint32_t cnum;
+       struct cmtp_conninfo *ci;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __CMTP_H */
diff --git a/lib/hci.c b/lib/hci.c
new file mode 100644 (file)
index 0000000..eb00730
--- /dev/null
+++ b/lib/hci.c
@@ -0,0 +1,2913 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include "bluetooth.h"
+#include "hci.h"
+#include "hci_lib.h"
+
+#ifndef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+typedef struct {
+       char *str;
+       unsigned int val;
+} hci_map;
+
+static char *hci_bit2str(hci_map *m, unsigned int val)
+{
+       char *str = malloc(120);
+       char *ptr = str;
+
+       if (!str)
+               return NULL;
+
+       *ptr = 0;
+       while (m->str) {
+               if ((unsigned int) m->val & val)
+                       ptr += sprintf(ptr, "%s ", m->str);
+               m++;
+       }
+       return str;
+}
+
+static int hci_str2bit(hci_map *map, char *str, unsigned int *val)
+{
+       char *t, *ptr;
+       hci_map *m;
+       int set;
+
+       if (!str || !(str = ptr = strdup(str)))
+               return 0;
+
+       *val = set = 0;
+
+       while ((t = strsep(&ptr, ","))) {
+               for (m = map; m->str; m++) {
+                       if (!strcasecmp(m->str, t)) {
+                               *val |= (unsigned int) m->val;
+                               set = 1;
+                       }
+               }
+       }
+       free(str);
+
+       return set;
+}
+
+static char *hci_uint2str(hci_map *m, unsigned int val)
+{
+       char *str = malloc(50);
+       char *ptr = str;
+
+       if (!str)
+               return NULL;
+
+       *ptr = 0;
+       while (m->str) {
+               if ((unsigned int) m->val == val) {
+                       ptr += sprintf(ptr, "%s", m->str);
+                       break;
+               }
+               m++;
+       }
+       return str;
+}
+
+static int hci_str2uint(hci_map *map, char *str, unsigned int *val)
+{
+       char *t, *ptr;
+       hci_map *m;
+       int set = 0;
+
+       if (!str)
+               return 0;
+
+       str = ptr = strdup(str);
+
+       while ((t = strsep(&ptr, ","))) {
+               for (m = map; m->str; m++) {
+                       if (!strcasecmp(m->str,t)) {
+                               *val = (unsigned int) m->val;
+                               set = 1;
+                               break;
+                       }
+               }
+       }
+       free(str);
+
+       return set;
+}
+
+char *hci_bustostr(int bus)
+{
+       switch (bus) {
+       case HCI_VIRTUAL:
+               return "VIRTUAL";
+       case HCI_USB:
+               return "USB";
+       case HCI_PCCARD:
+               return "PCCARD";
+       case HCI_UART:
+               return "UART";
+       case HCI_RS232:
+               return "RS232";
+       case HCI_PCI:
+               return "PCI";
+       case HCI_SDIO:
+               return "SDIO";
+       default:
+               return "UNKNOWN";
+       }
+}
+
+char *hci_dtypetostr(int type)
+{
+       return hci_bustostr(type & 0x0f);
+}
+
+char *hci_typetostr(int type)
+{
+       switch (type) {
+       case HCI_BREDR:
+               return "BR/EDR";
+       case HCI_AMP:
+               return "AMP";
+       default:
+               return "UNKNOWN";
+       }
+}
+
+/* HCI dev flags mapping */
+static hci_map dev_flags_map[] = {
+       { "UP",      HCI_UP      },
+       { "INIT",    HCI_INIT    },
+       { "RUNNING", HCI_RUNNING },
+       { "RAW",     HCI_RAW     },
+       { "PSCAN",   HCI_PSCAN   },
+       { "ISCAN",   HCI_ISCAN   },
+       { "INQUIRY", HCI_INQUIRY },
+       { "AUTH",    HCI_AUTH    },
+       { "ENCRYPT", HCI_ENCRYPT },
+       { NULL }
+};
+
+char *hci_dflagstostr(uint32_t flags)
+{
+       char *str = bt_malloc(50);
+       char *ptr = str;
+       hci_map *m = dev_flags_map;
+
+       if (!str)
+               return NULL;
+
+       *ptr = 0;
+
+       if (!hci_test_bit(HCI_UP, &flags))
+               ptr += sprintf(ptr, "DOWN ");
+
+       while (m->str) {
+               if (hci_test_bit(m->val, &flags))
+                       ptr += sprintf(ptr, "%s ", m->str);
+               m++;
+       }
+       return str;
+}
+
+/* HCI packet type mapping */
+static hci_map pkt_type_map[] = {
+       { "DM1",   HCI_DM1  },
+       { "DM3",   HCI_DM3  },
+       { "DM5",   HCI_DM5  },
+       { "DH1",   HCI_DH1  },
+       { "DH3",   HCI_DH3  },
+       { "DH5",   HCI_DH5  },
+       { "HV1",   HCI_HV1  },
+       { "HV2",   HCI_HV2  },
+       { "HV3",   HCI_HV3  },
+       { "2-DH1", HCI_2DH1 },
+       { "2-DH3", HCI_2DH3 },
+       { "2-DH5", HCI_2DH5 },
+       { "3-DH1", HCI_3DH1 },
+       { "3-DH3", HCI_3DH3 },
+       { "3-DH5", HCI_3DH5 },
+       { NULL }
+};
+
+static hci_map sco_ptype_map[] = {
+       { "HV1",   0x0001   },
+       { "HV2",   0x0002   },
+       { "HV3",   0x0004   },
+       { "EV3",   HCI_EV3  },
+       { "EV4",   HCI_EV4  },
+       { "EV5",   HCI_EV5  },
+       { "2-EV3", HCI_2EV3 },
+       { "2-EV5", HCI_2EV5 },
+       { "3-EV3", HCI_3EV3 },
+       { "3-EV5", HCI_3EV5 },
+       { NULL }
+};
+
+char *hci_ptypetostr(unsigned int ptype)
+{
+       return hci_bit2str(pkt_type_map, ptype);
+}
+
+int hci_strtoptype(char *str, unsigned int *val)
+{
+       return hci_str2bit(pkt_type_map, str, val);
+}
+
+char *hci_scoptypetostr(unsigned int ptype)
+{
+       return hci_bit2str(sco_ptype_map, ptype);
+}
+
+int hci_strtoscoptype(char *str, unsigned int *val)
+{
+       return hci_str2bit(sco_ptype_map, str, val);
+}
+
+/* Link policy mapping */
+static hci_map link_policy_map[] = {
+       { "NONE",       0               },
+       { "RSWITCH",    HCI_LP_RSWITCH  },
+       { "HOLD",       HCI_LP_HOLD     },
+       { "SNIFF",      HCI_LP_SNIFF    },
+       { "PARK",       HCI_LP_PARK     },
+       { NULL }
+};
+
+char *hci_lptostr(unsigned int lp)
+{
+       return hci_bit2str(link_policy_map, lp);
+}
+
+int hci_strtolp(char *str, unsigned int *val)
+{
+       return hci_str2bit(link_policy_map, str, val);
+}
+
+/* Link mode mapping */
+static hci_map link_mode_map[] = {
+       { "NONE",       0               },
+       { "ACCEPT",     HCI_LM_ACCEPT   },
+       { "MASTER",     HCI_LM_MASTER   },
+       { "AUTH",       HCI_LM_AUTH     },
+       { "ENCRYPT",    HCI_LM_ENCRYPT  },
+       { "TRUSTED",    HCI_LM_TRUSTED  },
+       { "RELIABLE",   HCI_LM_RELIABLE },
+       { "SECURE",     HCI_LM_SECURE   },
+       { NULL }
+};
+
+char *hci_lmtostr(unsigned int lm)
+{
+       char *s, *str = bt_malloc(50);
+       if (!str)
+               return NULL;
+
+       *str = 0;
+       if (!(lm & HCI_LM_MASTER))
+               strcpy(str, "SLAVE ");
+
+       s = hci_bit2str(link_mode_map, lm);
+       if (!s) {
+               bt_free(str);
+               return NULL;
+       }
+
+       strcat(str, s);
+       free(s);
+       return str;
+}
+
+int hci_strtolm(char *str, unsigned int *val)
+{
+       return hci_str2bit(link_mode_map, str, val);
+}
+
+/* Command mapping */
+static hci_map commands_map[] = {
+       { "Inquiry",                                    0   },
+       { "Inquiry Cancel",                             1   },
+       { "Periodic Inquiry Mode",                      2   },
+       { "Exit Periodic Inquiry Mode",                 3   },
+       { "Create Connection",                          4   },
+       { "Disconnect",                                 5   },
+       { "Add SCO Connection",                         6   },
+       { "Cancel Create Connection",                   7   },
+
+       { "Accept Connection Request",                  8   },
+       { "Reject Connection Request",                  9   },
+       { "Link Key Request Reply",                     10  },
+       { "Link Key Request Negative Reply",            11  },
+       { "PIN Code Request Reply",                     12  },
+       { "PIN Code Request Negative Reply",            13  },
+       { "Change Connection Packet Type",              14  },
+       { "Authentication Requested",                   15  },
+
+       { "Set Connection Encryption",                  16  },
+       { "Change Connection Link Key",                 17  },
+       { "Master Link Key",                            18  },
+       { "Remote Name Request",                        19  },
+       { "Cancel Remote Name Request",                 20  },
+       { "Read Remote Supported Features",             21  },
+       { "Read Remote Extended Features",              22  },
+       { "Read Remote Version Information",            23  },
+
+       { "Read Clock Offset",                          24  },
+       { "Read LMP Handle",                            25  },
+       { "Reserved",                                   26  },
+       { "Reserved",                                   27  },
+       { "Reserved",                                   28  },
+       { "Reserved",                                   29  },
+       { "Reserved",                                   30  },
+       { "Reserved",                                   31  },
+
+       { "Reserved",                                   32  },
+       { "Hold Mode",                                  33  },
+       { "Sniff Mode",                                 34  },
+       { "Exit Sniff Mode",                            35  },
+       { "Park State",                                 36  },
+       { "Exit Park State",                            37  },
+       { "QoS Setup",                                  38  },
+       { "Role Discovery",                             39  },
+
+       { "Switch Role",                                40  },
+       { "Read Link Policy Settings",                  41  },
+       { "Write Link Policy Settings",                 42  },
+       { "Read Default Link Policy Settings",          43  },
+       { "Write Default Link Policy Settings",         44  },
+       { "Flow Specification",                         45  },
+       { "Set Event Mask",                             46  },
+       { "Reset",                                      47  },
+
+       { "Set Event Filter",                           48  },
+       { "Flush",                                      49  },
+       { "Read PIN Type",                              50  },
+       { "Write PIN Type",                             51  },
+       { "Create New Unit Key",                        52  },
+       { "Read Stored Link Key",                       53  },
+       { "Write Stored Link Key",                      54  },
+       { "Delete Stored Link Key",                     55  },
+
+       { "Write Local Name",                           56  },
+       { "Read Local Name",                            57  },
+       { "Read Connection Accept Timeout",             58  },
+       { "Write Connection Accept Timeout",            59  },
+       { "Read Page Timeout",                          60  },
+       { "Write Page Timeout",                         61  },
+       { "Read Scan Enable",                           62  },
+       { "Write Scan Enable",                          63  },
+
+       { "Read Page Scan Activity",                    64  },
+       { "Write Page Scan Activity",                   65  },
+       { "Read Inquiry Scan Activity",                 66  },
+       { "Write Inquiry Scan Activity",                67  },
+       { "Read Authentication Enable",                 68  },
+       { "Write Authentication Enable",                69  },
+       { "Read Encryption Mode",                       70  },
+       { "Write Encryption Mode",                      71  },
+
+       { "Read Class Of Device",                       72  },
+       { "Write Class Of Device",                      73  },
+       { "Read Voice Setting",                         74  },
+       { "Write Voice Setting",                        75  },
+       { "Read Automatic Flush Timeout",               76  },
+       { "Write Automatic Flush Timeout",              77  },
+       { "Read Num Broadcast Retransmissions",         78  },
+       { "Write Num Broadcast Retransmissions",        79  },
+
+       { "Read Hold Mode Activity",                    80  },
+       { "Write Hold Mode Activity",                   81  },
+       { "Read Transmit Power Level",                  82  },
+       { "Read Synchronous Flow Control Enable",       83  },
+       { "Write Synchronous Flow Control Enable",      84  },
+       { "Set Host Controller To Host Flow Control",   85  },
+       { "Host Buffer Size",                           86  },
+       { "Host Number Of Completed Packets",           87  },
+
+       { "Read Link Supervision Timeout",              88  },
+       { "Write Link Supervision Timeout",             89  },
+       { "Read Number of Supported IAC",               90  },
+       { "Read Current IAC LAP",                       91  },
+       { "Write Current IAC LAP",                      92  },
+       { "Read Page Scan Period Mode",                 93  },
+       { "Write Page Scan Period Mode",                94  },
+       { "Read Page Scan Mode",                        95  },
+
+       { "Write Page Scan Mode",                       96  },
+       { "Set AFH Channel Classification",             97  },
+       { "Reserved",                                   98  },
+       { "Reserved",                                   99  },
+       { "Read Inquiry Scan Type",                     100 },
+       { "Write Inquiry Scan Type",                    101 },
+       { "Read Inquiry Mode",                          102 },
+       { "Write Inquiry Mode",                         103 },
+
+       { "Read Page Scan Type",                        104 },
+       { "Write Page Scan Type",                       105 },
+       { "Read AFH Channel Assessment Mode",           106 },
+       { "Write AFH Channel Assessment Mode",          107 },
+       { "Reserved",                                   108 },
+       { "Reserved",                                   109 },
+       { "Reserved",                                   110 },
+       { "Reserved",                                   111 },
+
+       { "Reserved",                                   112 },
+       { "Reserved",                                   113 },
+       { "Reserved",                                   114 },
+       { "Read Local Version Information",             115 },
+       { "Read Local Supported Commands",              116 },
+       { "Read Local Supported Features",              117 },
+       { "Read Local Extended Features",               118 },
+       { "Read Buffer Size",                           119 },
+
+       { "Read Country Code",                          120 },
+       { "Read BD ADDR",                               121 },
+       { "Read Failed Contact Counter",                122 },
+       { "Reset Failed Contact Counter",               123 },
+       { "Get Link Quality",                           124 },
+       { "Read RSSI",                                  125 },
+       { "Read AFH Channel Map",                       126 },
+       { "Read BD Clock",                              127 },
+
+       { "Read Loopback Mode",                         128 },
+       { "Write Loopback Mode",                        129 },
+       { "Enable Device Under Test Mode",              130 },
+       { "Setup Synchronous Connection",               131 },
+       { "Accept Synchronous Connection",              132 },
+       { "Reject Synchronous Connection",              133 },
+       { "Reserved",                                   134 },
+       { "Reserved",                                   135 },
+
+       { "Read Extended Inquiry Response",             136 },
+       { "Write Extended Inquiry Response",            137 },
+       { "Refresh Encryption Key",                     138 },
+       { "Reserved",                                   139 },
+       { "Sniff Subrating",                            140 },
+       { "Read Simple Pairing Mode",                   141 },
+       { "Write Simple Pairing Mode",                  142 },
+       { "Read Local OOB Data",                        143 },
+
+       { "Read Inquiry Response Transmit Power Level", 144 },
+       { "Write Inquiry Transmit Power Level",         145 },
+       { "Read Default Erroneous Data Reporting",      146 },
+       { "Write Default Erroneous Data Reporting",     147 },
+       { "Reserved",                                   148 },
+       { "Reserved",                                   149 },
+       { "Reserved",                                   150 },
+       { "IO Capability Request Reply",                151 },
+
+       { "User Confirmation Request Reply",            152 },
+       { "User Confirmation Request Negative Reply",   153 },
+       { "User Passkey Request Reply",                 154 },
+       { "User Passkey Request Negative Reply",        155 },
+       { "Remote OOB Data Request Reply",              156 },
+       { "Write Simple Pairing Debug Mode",            157 },
+       { "Enhanced Flush",                             158 },
+       { "Remote OOB Data Request Negative Reply",     159 },
+
+       { "Reserved",                                   160 },
+       { "Reserved",                                   161 },
+       { "Send Keypress Notification",                 162 },
+       { "IO Capability Request Negative Reply",       163 },
+       { "Read Encryption Key Size",                   164 },
+       { "Reserved",                                   165 },
+       { "Reserved",                                   166 },
+       { "Reserved",                                   167 },
+
+       { "Create Physical Link",                       168 },
+       { "Accept Physical Link",                       169 },
+       { "Disconnect Physical Link",                   170 },
+       { "Create Logical Link",                        171 },
+       { "Accept Logical Link",                        172 },
+       { "Disconnect Logical Link",                    173 },
+       { "Logical Link Cancel",                        174 },
+       { "Flow Specification Modify",                  175 },
+
+       { "Read Logical Link Accept Timeout",           176 },
+       { "Write Logical Link Accept Timeout",          177 },
+       { "Set Event Mask Page 2",                      178 },
+       { "Read Location Data",                         179 },
+       { "Write Location Data",                        180 },
+       { "Read Local AMP Info",                        181 },
+       { "Read Local AMP_ASSOC",                       182 },
+       { "Write Remote AMP_ASSOC",                     183 },
+
+       { "Read Flow Control Mode",                     184 },
+       { "Write Flow Control Mode",                    185 },
+       { "Read Data Block Size",                       186 },
+       { "Reserved",                                   187 },
+       { "Reserved",                                   188 },
+       { "Enable AMP Receiver Reports",                189 },
+       { "AMP Test End",                               190 },
+       { "AMP Test Command",                           191 },
+
+       { "Read Enhanced Transmit Power Level",         192 },
+       { "Reserved",                                   193 },
+       { "Read Best Effort Flush Timeout",             194 },
+       { "Write Best Effort Flush Timeout",            195 },
+       { "Short Range Mode",                           196 },
+       { "Read LE Host Support",                       197 },
+       { "Write LE Host Support",                      198 },
+       { "Reserved",                                   199 },
+
+       { "LE Set Event Mask",                          200 },
+       { "LE Read Buffer Size",                        201 },
+       { "LE Read Local Supported Features",           202 },
+       { "Reserved",                                   203 },
+       { "LE Set Random Address",                      204 },
+       { "LE Set Advertising Parameters",              205 },
+       { "LE Read Advertising Channel TX Power",       206 },
+       { "LE Set Advertising Data",                    207 },
+
+       { "LE Set Scan Response Data",                  208 },
+       { "LE Set Advertise Enable",                    209 },
+       { "LE Set Scan Parameters",                     210 },
+       { "LE Set Scan Enable",                         211 },
+       { "LE Create Connection",                       212 },
+       { "LE Create Connection Cancel",                213 },
+       { "LE Read White List Size",                    214 },
+       { "LE Clear White List",                        215 },
+
+       { "LE Add Device To White List",                216 },
+       { "LE Remove Device From White List",           217 },
+       { "LE Connection Update",                       218 },
+       { "LE Set Host Channel Classification",         219 },
+       { "LE Read Channel Map",                        220 },
+       { "LE Read Remote Used Features",               221 },
+       { "LE Encrypt",                                 222 },
+       { "LE Rand",                                    223 },
+
+       { "LE Start Encryption",                        224 },
+       { "LE Long Term Key Request Reply",             225 },
+       { "LE Long Term Key Request Negative Reply",    226 },
+       { "LE Read Supported States",                   227 },
+       { "LE Receiver Test",                           228 },
+       { "LE Transmitter Test",                        229 },
+       { "LE Test End",                                230 },
+       { "Reserved",                                   231 },
+
+       { NULL }
+};
+
+char *hci_cmdtostr(unsigned int cmd)
+{
+       return hci_uint2str(commands_map, cmd);
+}
+
+char *hci_commandstostr(uint8_t *commands, char *pref, int width)
+{
+       unsigned int maxwidth = width - 3;
+       hci_map *m;
+       char *off, *ptr, *str;
+       int size = 10;
+
+       m = commands_map;
+
+       while (m->str) {
+               if (commands[m->val / 8] & (1 << (m->val % 8)))
+                       size += strlen(m->str) + (pref ? strlen(pref) : 0) + 3;
+               m++;
+       }
+
+       str = bt_malloc(size);
+       if (!str)
+               return NULL;
+
+       ptr = str; *ptr = '\0';
+
+       if (pref)
+               ptr += sprintf(ptr, "%s", pref);
+
+       off = ptr;
+
+       m = commands_map;
+
+       while (m->str) {
+               if (commands[m->val / 8] & (1 << (m->val % 8))) {
+                       if (strlen(off) + strlen(m->str) > maxwidth) {
+                               ptr += sprintf(ptr, "\n%s", pref ? pref : "");
+                               off = ptr;
+                       }
+                       ptr += sprintf(ptr, "'%s' ", m->str);
+               }
+               m++;
+       }
+
+       return str;
+}
+
+/* Version mapping */
+static hci_map ver_map[] = {
+       { "1.0b",       0x00 },
+       { "1.1",        0x01 },
+       { "1.2",        0x02 },
+       { "2.0",        0x03 },
+       { "2.1",        0x04 },
+       { "3.0",        0x05 },
+       { "4.0",        0x06 },
+       { NULL }
+};
+
+char *hci_vertostr(unsigned int ver)
+{
+       return hci_uint2str(ver_map, ver);
+}
+
+int hci_strtover(char *str, unsigned int *ver)
+{
+       return hci_str2uint(ver_map, str, ver);
+}
+
+char *lmp_vertostr(unsigned int ver)
+{
+       return hci_uint2str(ver_map, ver);
+}
+
+int lmp_strtover(char *str, unsigned int *ver)
+{
+       return hci_str2uint(ver_map, str, ver);
+}
+
+/* LMP features mapping */
+static hci_map lmp_features_map[8][9] = {
+       {       /* Byte 0 */
+               { "<3-slot packets>",   LMP_3SLOT       },      /* Bit 0 */
+               { "<5-slot packets>",   LMP_5SLOT       },      /* Bit 1 */
+               { "<encryption>",       LMP_ENCRYPT     },      /* Bit 2 */
+               { "<slot offset>",      LMP_SOFFSET     },      /* Bit 3 */
+               { "<timing accuracy>",  LMP_TACCURACY   },      /* Bit 4 */
+               { "<role switch>",      LMP_RSWITCH     },      /* Bit 5 */
+               { "<hold mode>",        LMP_HOLD        },      /* Bit 6 */
+               { "<sniff mode>",       LMP_SNIFF       },      /* Bit 7 */
+               { NULL }
+       },
+       {       /* Byte 1 */
+               { "<park state>",       LMP_PARK        },      /* Bit 0 */
+               { "<RSSI>",             LMP_RSSI        },      /* Bit 1 */
+               { "<channel quality>",  LMP_QUALITY     },      /* Bit 2 */
+               { "<SCO link>",         LMP_SCO         },      /* Bit 3 */
+               { "<HV2 packets>",      LMP_HV2         },      /* Bit 4 */
+               { "<HV3 packets>",      LMP_HV3         },      /* Bit 5 */
+               { "<u-law log>",        LMP_ULAW        },      /* Bit 6 */
+               { "<A-law log>",        LMP_ALAW        },      /* Bit 7 */
+               { NULL }
+       },
+       {       /* Byte 2 */
+               { "<CVSD>",             LMP_CVSD        },      /* Bit 0 */
+               { "<paging scheme>",    LMP_PSCHEME     },      /* Bit 1 */
+               { "<power control>",    LMP_PCONTROL    },      /* Bit 2 */
+               { "<transparent SCO>",  LMP_TRSP_SCO    },      /* Bit 3 */
+               { "<broadcast encrypt>",LMP_BCAST_ENC   },      /* Bit 7 */
+               { NULL }
+       },
+       {       /* Byte 3 */
+               { "<no. 24>",           0x01            },      /* Bit 0 */
+               { "<EDR ACL 2 Mbps>",   LMP_EDR_ACL_2M  },      /* Bit 1 */
+               { "<EDR ACL 3 Mbps>",   LMP_EDR_ACL_3M  },      /* Bit 2 */
+               { "<enhanced iscan>",   LMP_ENH_ISCAN   },      /* Bit 3 */
+               { "<interlaced iscan>", LMP_ILACE_ISCAN },      /* Bit 4 */
+               { "<interlaced pscan>", LMP_ILACE_PSCAN },      /* Bit 5 */
+               { "<inquiry with RSSI>",LMP_RSSI_INQ    },      /* Bit 6 */
+               { "<extended SCO>",     LMP_ESCO        },      /* Bit 7 */
+               { NULL }
+       },
+       {       /* Byte 4 */
+               { "<EV4 packets>",      LMP_EV4         },      /* Bit 0 */
+               { "<EV5 packets>",      LMP_EV5         },      /* Bit 1 */
+               { "<no. 34>",           0x04            },      /* Bit 2 */
+               { "<AFH cap. slave>",   LMP_AFH_CAP_SLV },      /* Bit 3 */
+               { "<AFH class. slave>", LMP_AFH_CLS_SLV },      /* Bit 4 */
+               { "<BR/EDR not supp.>", LMP_NO_BREDR    },      /* Bit 5 */
+               { "<LE support>",       LMP_LE          },      /* Bit 6 */
+               { "<3-slot EDR ACL>",   LMP_EDR_3SLOT   },      /* Bit 7 */
+               { NULL }
+       },
+       {       /* Byte 5 */
+               { "<5-slot EDR ACL>",   LMP_EDR_5SLOT   },      /* Bit 0 */
+               { "<sniff subrating>",  LMP_SNIFF_SUBR  },      /* Bit 1 */
+               { "<pause encryption>", LMP_PAUSE_ENC   },      /* Bit 2 */
+               { "<AFH cap. master>",  LMP_AFH_CAP_MST },      /* Bit 3 */
+               { "<AFH class. master>",LMP_AFH_CLS_MST },      /* Bit 4 */
+               { "<EDR eSCO 2 Mbps>",  LMP_EDR_ESCO_2M },      /* Bit 5 */
+               { "<EDR eSCO 3 Mbps>",  LMP_EDR_ESCO_3M },      /* Bit 6 */
+               { "<3-slot EDR eSCO>",  LMP_EDR_3S_ESCO },      /* Bit 7 */
+               { NULL }
+       },
+       {       /* Byte 6 */
+               { "<extended inquiry>", LMP_EXT_INQ     },      /* Bit 0 */
+               { "<LE and BR/EDR>",    LMP_LE_BREDR    },      /* Bit 1 */
+               { "<no. 50>",           0x04            },      /* Bit 2 */
+               { "<simple pairing>",   LMP_SIMPLE_PAIR },      /* Bit 3 */
+               { "<encapsulated PDU>", LMP_ENCAPS_PDU  },      /* Bit 4 */
+               { "<err. data report>", LMP_ERR_DAT_REP },      /* Bit 5 */
+               { "<non-flush flag>",   LMP_NFLUSH_PKTS },      /* Bit 6 */
+               { "<no. 55>",           0x80            },      /* Bit 7 */
+               { NULL }
+       },
+       {       /* Byte 7 */
+               { "<LSTO>",             LMP_LSTO        },      /* Bit 1 */
+               { "<inquiry TX power>", LMP_INQ_TX_PWR  },      /* Bit 1 */
+               { "<EPC>",              LMP_EPC         },      /* Bit 2 */
+               { "<no. 59>",           0x08            },      /* Bit 3 */
+               { "<no. 60>",           0x10            },      /* Bit 4 */
+               { "<no. 61>",           0x20            },      /* Bit 5 */
+               { "<no. 62>",           0x40            },      /* Bit 6 */
+               { "<extended features>",LMP_EXT_FEAT    },      /* Bit 7 */
+               { NULL }
+       },
+};
+
+char *lmp_featurestostr(uint8_t *features, char *pref, int width)
+{
+       unsigned int maxwidth = width - 1;
+       char *off, *ptr, *str;
+       int i, size = 10;
+
+       for (i = 0; i < 8; i++) {
+               hci_map *m = lmp_features_map[i];
+
+               while (m->str) {
+                       if (m->val & features[i])
+                               size += strlen(m->str) +
+                                               (pref ? strlen(pref) : 0) + 1;
+                       m++;
+               }
+       }
+
+       str = bt_malloc(size);
+       if (!str)
+               return NULL;
+
+       ptr = str; *ptr = '\0';
+
+       if (pref)
+               ptr += sprintf(ptr, "%s", pref);
+
+       off = ptr;
+
+       for (i = 0; i < 8; i++) {
+               hci_map *m = lmp_features_map[i];
+
+               while (m->str) {
+                       if (m->val & features[i]) {
+                               if (strlen(off) + strlen(m->str) > maxwidth) {
+                                       ptr += sprintf(ptr, "\n%s",
+                                                       pref ? pref : "");
+                                       off = ptr;
+                               }
+                               ptr += sprintf(ptr, "%s ", m->str);
+                       }
+                       m++;
+               }
+       }
+
+       return str;
+}
+
+/* HCI functions that do not require open device */
+int hci_for_each_dev(int flag, int (*func)(int dd, int dev_id, long arg),
+                       long arg)
+{
+       struct hci_dev_list_req *dl;
+       struct hci_dev_req *dr;
+       int dev_id = -1;
+       int i, sk, err = 0;
+
+       sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+       if (sk < 0)
+               return -1;
+
+       dl = malloc(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl));
+       if (!dl) {
+               err = errno;
+               goto done;
+       }
+
+       memset(dl, 0, HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl));
+
+       dl->dev_num = HCI_MAX_DEV;
+       dr = dl->dev_req;
+
+       if (ioctl(sk, HCIGETDEVLIST, (void *) dl) < 0) {
+               err = errno;
+               goto free;
+       }
+
+       for (i = 0; i < dl->dev_num; i++, dr++) {
+               if (hci_test_bit(flag, &dr->dev_opt))
+                       if (!func || func(sk, dr->dev_id, arg)) {
+                               dev_id = dr->dev_id;
+                               break;
+                       }
+       }
+
+       if (dev_id < 0)
+               err = ENODEV;
+
+free:
+       free(dl);
+
+done:
+       close(sk);
+       errno = err;
+
+       return dev_id;
+}
+
+static int __other_bdaddr(int dd, int dev_id, long arg)
+{
+       struct hci_dev_info di = { dev_id: dev_id };
+
+       if (ioctl(dd, HCIGETDEVINFO, (void *) &di))
+               return 0;
+
+       if (hci_test_bit(HCI_RAW, &di.flags))
+               return 0;
+
+       return bacmp((bdaddr_t *) arg, &di.bdaddr);
+}
+
+static int __same_bdaddr(int dd, int dev_id, long arg)
+{
+       struct hci_dev_info di = { dev_id: dev_id };
+
+       if (ioctl(dd, HCIGETDEVINFO, (void *) &di))
+               return 0;
+
+       return !bacmp((bdaddr_t *) arg, &di.bdaddr);
+}
+
+int hci_get_route(bdaddr_t *bdaddr)
+{
+       return hci_for_each_dev(HCI_UP, __other_bdaddr,
+                               (long) (bdaddr ? bdaddr : BDADDR_ANY));
+}
+
+int hci_devid(const char *str)
+{
+       bdaddr_t ba;
+       int id = -1;
+
+       if (!strncmp(str, "hci", 3) && strlen(str) >= 4) {
+               id = atoi(str + 3);
+               if (hci_devba(id, &ba) < 0)
+                       return -1;
+       } else {
+               errno = ENODEV;
+               str2ba(str, &ba);
+               id = hci_for_each_dev(HCI_UP, __same_bdaddr, (long) &ba);
+       }
+
+       return id;
+}
+
+int hci_devinfo(int dev_id, struct hci_dev_info *di)
+{
+       int dd, err, ret;
+
+       dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+       if (dd < 0)
+               return dd;
+
+       memset(di, 0, sizeof(struct hci_dev_info));
+
+       di->dev_id = dev_id;
+       ret = ioctl(dd, HCIGETDEVINFO, (void *) di);
+
+       err = errno;
+       close(dd);
+       errno = err;
+
+       return ret;
+}
+
+int hci_devba(int dev_id, bdaddr_t *bdaddr)
+{
+       struct hci_dev_info di;
+
+       memset(&di, 0, sizeof(di));
+
+       if (hci_devinfo(dev_id, &di))
+               return -1;
+
+       if (!hci_test_bit(HCI_UP, &di.flags)) {
+               errno = ENETDOWN;
+               return -1;
+       }
+
+       bacpy(bdaddr, &di.bdaddr);
+
+       return 0;
+}
+
+int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap,
+               inquiry_info **ii, long flags)
+{
+       struct hci_inquiry_req *ir;
+       uint8_t num_rsp = nrsp;
+       void *buf;
+       int dd, size, err, ret = -1;
+
+       if (nrsp <= 0) {
+               num_rsp = 0;
+               nrsp = 255;
+       }
+
+       if (dev_id < 0) {
+               dev_id = hci_get_route(NULL);
+               if (dev_id < 0) {
+                       errno = ENODEV;
+                       return -1;
+               }
+       }
+
+       dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+       if (dd < 0)
+               return dd;
+
+       buf = malloc(sizeof(*ir) + (sizeof(inquiry_info) * (nrsp)));
+       if (!buf)
+               goto done;
+
+       ir = buf;
+       ir->dev_id  = dev_id;
+       ir->num_rsp = num_rsp;
+       ir->length  = len;
+       ir->flags   = flags;
+
+       if (lap) {
+               memcpy(ir->lap, lap, 3);
+       } else {
+               ir->lap[0] = 0x33;
+               ir->lap[1] = 0x8b;
+               ir->lap[2] = 0x9e;
+       }
+
+       ret = ioctl(dd, HCIINQUIRY, (unsigned long) buf);
+       if (ret < 0)
+               goto free;
+
+       size = sizeof(inquiry_info) * ir->num_rsp;
+
+       if (!*ii)
+               *ii = malloc(size);
+
+       if (*ii) {
+               memcpy((void *) *ii, buf + sizeof(*ir), size);
+               ret = ir->num_rsp;
+       } else
+               ret = -1;
+
+free:
+       free(buf);
+
+done:
+       err = errno;
+       close(dd);
+       errno = err;
+
+       return ret;
+}
+
+/* Open HCI device.
+ * Returns device descriptor (dd). */
+int hci_open_dev(int dev_id)
+{
+       struct sockaddr_hci a;
+       int dd, err;
+
+       /* Create HCI socket */
+       dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+       if (dd < 0)
+               return dd;
+
+       /* Bind socket to the HCI device */
+       memset(&a, 0, sizeof(a));
+       a.hci_family = AF_BLUETOOTH;
+       a.hci_dev = dev_id;
+       if (bind(dd, (struct sockaddr *) &a, sizeof(a)) < 0)
+               goto failed;
+
+       return dd;
+
+failed:
+       err = errno;
+       close(dd);
+       errno = err;
+
+       return -1;
+}
+
+int hci_close_dev(int dd)
+{
+       return close(dd);
+}
+
+/* HCI functions that require open device
+ * dd - Device descriptor returned by hci_open_dev. */
+
+int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)
+{
+       uint8_t type = HCI_COMMAND_PKT;
+       hci_command_hdr hc;
+       struct iovec iv[3];
+       int ivn;
+
+       hc.opcode = htobs(cmd_opcode_pack(ogf, ocf));
+       hc.plen= plen;
+
+       iv[0].iov_base = &type;
+       iv[0].iov_len  = 1;
+       iv[1].iov_base = &hc;
+       iv[1].iov_len  = HCI_COMMAND_HDR_SIZE;
+       ivn = 2;
+
+       if (plen) {
+               iv[2].iov_base = param;
+               iv[2].iov_len  = plen;
+               ivn = 3;
+       }
+
+       while (writev(dd, iv, ivn) < 0) {
+               if (errno == EAGAIN || errno == EINTR)
+                       continue;
+               return -1;
+       }
+       return 0;
+}
+
+int hci_send_req(int dd, struct hci_request *r, int to)
+{
+       unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
+       uint16_t opcode = htobs(cmd_opcode_pack(r->ogf, r->ocf));
+       struct hci_filter nf, of;
+       socklen_t olen;
+       hci_event_hdr *hdr;
+       int err, try;
+
+       olen = sizeof(of);
+       if (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0)
+               return -1;
+
+       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_CMD_COMPLETE, &nf);
+       hci_filter_set_event(EVT_LE_META_EVENT, &nf);
+       hci_filter_set_event(r->event, &nf);
+       hci_filter_set_opcode(opcode, &nf);
+       if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0)
+               return -1;
+
+       if (hci_send_cmd(dd, r->ogf, r->ocf, r->clen, r->cparam) < 0)
+               goto failed;
+
+       try = 10;
+       while (try--) {
+               evt_cmd_complete *cc;
+               evt_cmd_status *cs;
+               evt_remote_name_req_complete *rn;
+               evt_le_meta_event *me;
+               remote_name_req_cp *cp;
+               int len;
+
+               if (to) {
+                       struct pollfd p;
+                       int n;
+
+                       p.fd = dd; p.events = POLLIN;
+                       while ((n = poll(&p, 1, to)) < 0) {
+                               if (errno == EAGAIN || errno == EINTR)
+                                       continue;
+                               goto failed;
+                       }
+
+                       if (!n) {
+                               errno = ETIMEDOUT;
+                               goto failed;
+                       }
+
+                       to -= 10;
+                       if (to < 0)
+                               to = 0;
+
+               }
+
+               while ((len = read(dd, buf, sizeof(buf))) < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       goto failed;
+               }
+
+               hdr = (void *) (buf + 1);
+               ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
+               len -= (1 + HCI_EVENT_HDR_SIZE);
+
+               switch (hdr->evt) {
+               case EVT_CMD_STATUS:
+                       cs = (void *) ptr;
+
+                       if (cs->opcode != opcode)
+                               continue;
+
+                       if (r->event != EVT_CMD_STATUS) {
+                               if (cs->status) {
+                                       errno = EIO;
+                                       goto failed;
+                               }
+                               break;
+                       }
+
+                       r->rlen = MIN(len, r->rlen);
+                       memcpy(r->rparam, ptr, r->rlen);
+                       goto done;
+
+               case EVT_CMD_COMPLETE:
+                       cc = (void *) ptr;
+
+                       if (cc->opcode != opcode)
+                               continue;
+
+                       ptr += EVT_CMD_COMPLETE_SIZE;
+                       len -= EVT_CMD_COMPLETE_SIZE;
+
+                       r->rlen = MIN(len, r->rlen);
+                       memcpy(r->rparam, ptr, r->rlen);
+                       goto done;
+
+               case EVT_REMOTE_NAME_REQ_COMPLETE:
+                       if (hdr->evt != r->event)
+                               break;
+
+                       rn = (void *) ptr;
+                       cp = r->cparam;
+
+                       if (bacmp(&rn->bdaddr, &cp->bdaddr))
+                               continue;
+
+                       r->rlen = MIN(len, r->rlen);
+                       memcpy(r->rparam, ptr, r->rlen);
+                       goto done;
+
+               case EVT_LE_META_EVENT:
+                       me = (void *) ptr;
+
+                       if (me->subevent != r->event)
+                               continue;
+
+                       len -= 1;
+                       r->rlen = MIN(len, r->rlen);
+                       memcpy(r->rparam, me->data, r->rlen);
+                       goto done;
+
+               default:
+                       if (hdr->evt != r->event)
+                               break;
+
+                       r->rlen = MIN(len, r->rlen);
+                       memcpy(r->rparam, ptr, r->rlen);
+                       goto done;
+               }
+       }
+       errno = ETIMEDOUT;
+
+failed:
+       err = errno;
+       setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
+       errno = err;
+       return -1;
+
+done:
+       setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
+       return 0;
+}
+
+int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype,
+                               uint16_t clkoffset, uint8_t rswitch,
+                               uint16_t *handle, int to)
+{
+       evt_conn_complete rp;
+       create_conn_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+       cp.pkt_type       = ptype;
+       cp.pscan_rep_mode = 0x02;
+       cp.clock_offset   = clkoffset;
+       cp.role_switch    = rswitch;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_CREATE_CONN;
+       rq.event  = EVT_CONN_COMPLETE;
+       rq.cparam = &cp;
+       rq.clen   = CREATE_CONN_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_CONN_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *handle = rp.handle;
+       return 0;
+}
+
+int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to)
+{
+       evt_disconn_complete rp;
+       disconnect_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = handle;
+       cp.reason = reason;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_DISCONNECT;
+       rq.event  = EVT_DISCONN_COMPLETE;
+       rq.cparam = &cp;
+       rq.clen   = DISCONNECT_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_DISCONN_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+       return 0;
+}
+
+int hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to)
+{
+       struct hci_request rq;
+       le_add_device_to_white_list_cp cp;
+       uint8_t status;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.bdaddr_type = type;
+       bacpy(&cp.bdaddr, bdaddr);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_ADD_DEVICE_TO_WHITE_LIST;
+       rq.cparam = &cp;
+       rq.clen = LE_ADD_DEVICE_TO_WHITE_LIST_CP_SIZE;
+       rq.rparam = &status;
+       rq.rlen = 1;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_le_rm_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to)
+{
+       struct hci_request rq;
+       le_remove_device_from_white_list_cp cp;
+       uint8_t status;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.bdaddr_type = type;
+       bacpy(&cp.bdaddr, bdaddr);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST;
+       rq.cparam = &cp;
+       rq.clen = LE_REMOVE_DEVICE_FROM_WHITE_LIST_CP_SIZE;
+       rq.rparam = &status;
+       rq.rlen = 1;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_le_read_white_list_size(int dd, uint8_t *size, int to)
+{
+       struct hci_request rq;
+       le_read_white_list_size_rp rp;
+
+       memset(&rp, 0, sizeof(rp));
+       memset(&rq, 0, sizeof(rq));
+
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_READ_WHITE_LIST_SIZE;
+       rq.rparam = &rp;
+       rq.rlen = LE_READ_WHITE_LIST_SIZE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (size)
+               *size = rp.size;
+
+       return 0;
+}
+
+int hci_le_clear_white_list(int dd, int to)
+{
+       struct hci_request rq;
+       uint8_t status;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_CLEAR_WHITE_LIST;
+       rq.rparam = &status;
+       rq.rlen = 1;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_local_name(int dd, int len, char *name, int to)
+{
+       read_local_name_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_LOCAL_NAME;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LOCAL_NAME_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       rp.name[247] = '\0';
+       strncpy(name, (char *) rp.name, len);
+       return 0;
+}
+
+int hci_write_local_name(int dd, const char *name, int to)
+{
+       change_local_name_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       strncpy((char *) cp.name, name, sizeof(cp.name));
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_CHANGE_LOCAL_NAME;
+       rq.cparam = &cp;
+       rq.clen   = CHANGE_LOCAL_NAME_CP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       return 0;
+}
+
+int hci_read_remote_name_with_clock_offset(int dd, const bdaddr_t *bdaddr,
+                                               uint8_t pscan_rep_mode,
+                                               uint16_t clkoffset,
+                                               int len, char *name, int to)
+{
+       evt_remote_name_req_complete rn;
+       remote_name_req_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+       cp.pscan_rep_mode = pscan_rep_mode;
+       cp.clock_offset   = clkoffset;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_REMOTE_NAME_REQ;
+       rq.cparam = &cp;
+       rq.clen   = REMOTE_NAME_REQ_CP_SIZE;
+       rq.event  = EVT_REMOTE_NAME_REQ_COMPLETE;
+       rq.rparam = &rn;
+       rq.rlen   = EVT_REMOTE_NAME_REQ_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rn.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       rn.name[247] = '\0';
+       strncpy(name, (char *) rn.name, len);
+       return 0;
+}
+
+int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name,
+                               int to)
+{
+       return hci_read_remote_name_with_clock_offset(dd, bdaddr, 0x02, 0x0000,
+                                                       len, name, to);
+}
+
+int hci_read_remote_name_cancel(int dd, const bdaddr_t *bdaddr, int to)
+{
+       remote_name_req_cancel_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_REMOTE_NAME_REQ_CANCEL;
+       rq.cparam = &cp;
+       rq.clen   = REMOTE_NAME_REQ_CANCEL_CP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       return 0;
+}
+
+int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver,
+                               int to)
+{
+       evt_read_remote_version_complete rp;
+       read_remote_version_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = handle;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_READ_REMOTE_VERSION;
+       rq.event  = EVT_READ_REMOTE_VERSION_COMPLETE;
+       rq.cparam = &cp;
+       rq.clen   = READ_REMOTE_VERSION_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_READ_REMOTE_VERSION_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       ver->manufacturer = btohs(rp.manufacturer);
+       ver->lmp_ver      = rp.lmp_ver;
+       ver->lmp_subver   = btohs(rp.lmp_subver);
+       return 0;
+}
+
+int hci_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to)
+{
+       evt_read_remote_features_complete rp;
+       read_remote_features_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = handle;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_READ_REMOTE_FEATURES;
+       rq.event  = EVT_READ_REMOTE_FEATURES_COMPLETE;
+       rq.cparam = &cp;
+       rq.clen   = READ_REMOTE_FEATURES_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (features)
+               memcpy(features, rp.features, 8);
+
+       return 0;
+}
+
+int hci_read_remote_ext_features(int dd, uint16_t handle, uint8_t page,
+                                       uint8_t *max_page, uint8_t *features,
+                                       int to)
+{
+       evt_read_remote_ext_features_complete rp;
+       read_remote_ext_features_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle   = handle;
+       cp.page_num = page;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_READ_REMOTE_EXT_FEATURES;
+       rq.event  = EVT_READ_REMOTE_EXT_FEATURES_COMPLETE;
+       rq.cparam = &cp;
+       rq.clen   = READ_REMOTE_EXT_FEATURES_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (max_page)
+               *max_page = rp.max_page_num;
+
+       if (features)
+               memcpy(features, rp.features, 8);
+
+       return 0;
+}
+
+int hci_read_clock_offset(int dd, uint16_t handle, uint16_t *clkoffset, int to)
+{
+       evt_read_clock_offset_complete rp;
+       read_clock_offset_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = handle;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_READ_CLOCK_OFFSET;
+       rq.event  = EVT_READ_CLOCK_OFFSET_COMPLETE;
+       rq.cparam = &cp;
+       rq.clen   = READ_CLOCK_OFFSET_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *clkoffset = rp.clock_offset;
+       return 0;
+}
+
+int hci_read_local_version(int dd, struct hci_version *ver, int to)
+{
+       read_local_version_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_INFO_PARAM;
+       rq.ocf    = OCF_READ_LOCAL_VERSION;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LOCAL_VERSION_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       ver->manufacturer = btohs(rp.manufacturer);
+       ver->hci_ver      = rp.hci_ver;
+       ver->hci_rev      = btohs(rp.hci_rev);
+       ver->lmp_ver      = rp.lmp_ver;
+       ver->lmp_subver   = btohs(rp.lmp_subver);
+       return 0;
+}
+
+int hci_read_local_commands(int dd, uint8_t *commands, int to)
+{
+       read_local_commands_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_INFO_PARAM;
+       rq.ocf    = OCF_READ_LOCAL_COMMANDS;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LOCAL_COMMANDS_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (commands)
+               memcpy(commands, rp.commands, 64);
+
+       return 0;
+}
+
+int hci_read_local_features(int dd, uint8_t *features, int to)
+{
+       read_local_features_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_INFO_PARAM;
+       rq.ocf    = OCF_READ_LOCAL_FEATURES;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LOCAL_FEATURES_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (features)
+               memcpy(features, rp.features, 8);
+
+       return 0;
+}
+
+int hci_read_local_ext_features(int dd, uint8_t page, uint8_t *max_page,
+                               uint8_t *features, int to)
+{
+       read_local_ext_features_cp cp;
+       read_local_ext_features_rp rp;
+       struct hci_request rq;
+
+       cp.page_num = page;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_INFO_PARAM;
+       rq.ocf    = OCF_READ_LOCAL_EXT_FEATURES;
+       rq.cparam = &cp;
+       rq.clen   = READ_LOCAL_EXT_FEATURES_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LOCAL_EXT_FEATURES_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (max_page)
+               *max_page = rp.max_page_num;
+
+       if (features)
+               memcpy(features, rp.features, 8);
+
+       return 0;
+}
+
+int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to)
+{
+       read_bd_addr_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_INFO_PARAM;
+       rq.ocf    = OCF_READ_BD_ADDR;
+       rq.rparam = &rp;
+       rq.rlen   = READ_BD_ADDR_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (bdaddr)
+               bacpy(bdaddr, &rp.bdaddr);
+
+       return 0;
+}
+
+int hci_read_class_of_dev(int dd, uint8_t *cls, int to)
+{
+       read_class_of_dev_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_CLASS_OF_DEV;
+       rq.rparam = &rp;
+       rq.rlen   = READ_CLASS_OF_DEV_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       memcpy(cls, rp.dev_class, 3);
+       return 0;
+}
+
+int hci_write_class_of_dev(int dd, uint32_t cls, int to)
+{
+       write_class_of_dev_cp cp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       cp.dev_class[0] = cls & 0xff;
+       cp.dev_class[1] = (cls >> 8) & 0xff;
+       cp.dev_class[2] = (cls >> 16) & 0xff;
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_CLASS_OF_DEV;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_CLASS_OF_DEV_CP_SIZE;
+       return hci_send_req(dd, &rq, to);
+}
+
+int hci_read_voice_setting(int dd, uint16_t *vs, int to)
+{
+       read_voice_setting_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_VOICE_SETTING;
+       rq.rparam = &rp;
+       rq.rlen   = READ_VOICE_SETTING_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *vs = rp.voice_setting;
+       return 0;
+}
+
+int hci_write_voice_setting(int dd, uint16_t vs, int to)
+{
+       write_voice_setting_cp cp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       cp.voice_setting = vs;
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_VOICE_SETTING;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_VOICE_SETTING_CP_SIZE;
+
+       return hci_send_req(dd, &rq, to);
+}
+
+int hci_read_current_iac_lap(int dd, uint8_t *num_iac, uint8_t *lap, int to)
+{
+       read_current_iac_lap_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_CURRENT_IAC_LAP;
+       rq.rparam = &rp;
+       rq.rlen   = READ_CURRENT_IAC_LAP_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *num_iac = rp.num_current_iac;
+       memcpy(lap, rp.lap, rp.num_current_iac * 3);
+       return 0;
+}
+
+int hci_write_current_iac_lap(int dd, uint8_t num_iac, uint8_t *lap, int to)
+{
+       write_current_iac_lap_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.num_current_iac = num_iac;
+       memcpy(&cp.lap, lap, num_iac * 3);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_CURRENT_IAC_LAP;
+       rq.cparam = &cp;
+       rq.clen   = num_iac * 3 + 1;
+
+       return hci_send_req(dd, &rq, to);
+}
+
+int hci_read_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to)
+{
+       read_stored_link_key_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+       cp.read_all = all;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_STORED_LINK_KEY;
+       rq.cparam = &cp;
+       rq.clen   = READ_STORED_LINK_KEY_CP_SIZE;
+
+       return hci_send_req(dd, &rq, to);
+}
+
+int hci_write_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t *key, int to)
+{
+       unsigned char cp[WRITE_STORED_LINK_KEY_CP_SIZE + 6 + 16];
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 1;
+       bacpy((bdaddr_t *) (cp + 1), bdaddr);
+       memcpy(cp + 7, key, 16);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_STORED_LINK_KEY;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_STORED_LINK_KEY_CP_SIZE + 6 + 16;
+
+       return hci_send_req(dd, &rq, to);
+}
+
+int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to)
+{
+       delete_stored_link_key_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+       cp.delete_all = all;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_DELETE_STORED_LINK_KEY;
+       rq.cparam = &cp;
+       rq.clen   = DELETE_STORED_LINK_KEY_CP_SIZE;
+
+       return hci_send_req(dd, &rq, to);
+}
+
+int hci_authenticate_link(int dd, uint16_t handle, int to)
+{
+       auth_requested_cp cp;
+       evt_auth_complete rp;
+       struct hci_request rq;
+
+       cp.handle = handle;
+
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_AUTH_REQUESTED;
+       rq.event  = EVT_AUTH_COMPLETE;
+       rq.cparam = &cp;
+       rq.clen   = AUTH_REQUESTED_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_AUTH_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to)
+{
+       set_conn_encrypt_cp cp;
+       evt_encrypt_change rp;
+       struct hci_request rq;
+
+       cp.handle  = handle;
+       cp.encrypt = encrypt;
+
+       rq.ogf     = OGF_LINK_CTL;
+       rq.ocf     = OCF_SET_CONN_ENCRYPT;
+       rq.event   = EVT_ENCRYPT_CHANGE;
+       rq.cparam  = &cp;
+       rq.clen    = SET_CONN_ENCRYPT_CP_SIZE;
+       rq.rparam  = &rp;
+       rq.rlen    = EVT_ENCRYPT_CHANGE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_change_link_key(int dd, uint16_t handle, int to)
+{
+       change_conn_link_key_cp cp;
+       evt_change_conn_link_key_complete rp;
+       struct hci_request rq;
+
+       cp.handle = handle;
+
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_CHANGE_CONN_LINK_KEY;
+       rq.event  = EVT_CHANGE_CONN_LINK_KEY_COMPLETE;
+       rq.cparam = &cp;
+       rq.clen   = CHANGE_CONN_LINK_KEY_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to)
+{
+       switch_role_cp cp;
+       evt_role_change rp;
+       struct hci_request rq;
+
+       bacpy(&cp.bdaddr, bdaddr);
+       cp.role   = role;
+       rq.ogf    = OGF_LINK_POLICY;
+       rq.ocf    = OCF_SWITCH_ROLE;
+       rq.cparam = &cp;
+       rq.clen   = SWITCH_ROLE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_ROLE_CHANGE_SIZE;
+       rq.event  = EVT_ROLE_CHANGE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval,
+                       uint16_t min_interval, int to)
+{
+       park_mode_cp cp;
+       evt_mode_change rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof (cp));
+       cp.handle       = handle;
+       cp.max_interval = max_interval;
+       cp.min_interval = min_interval;
+
+       memset(&rq, 0, sizeof (rq));
+       rq.ogf    = OGF_LINK_POLICY;
+       rq.ocf    = OCF_PARK_MODE;
+       rq.event  = EVT_MODE_CHANGE;
+       rq.cparam = &cp;
+       rq.clen   = PARK_MODE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_MODE_CHANGE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_exit_park_mode(int dd, uint16_t handle, int to)
+{
+       exit_park_mode_cp cp;
+       evt_mode_change rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof (cp));
+       cp.handle = handle;
+
+       memset (&rq, 0, sizeof (rq));
+       rq.ogf    = OGF_LINK_POLICY;
+       rq.ocf    = OCF_EXIT_PARK_MODE;
+       rq.event  = EVT_MODE_CHANGE;
+       rq.cparam = &cp;
+       rq.clen   = EXIT_PARK_MODE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_MODE_CHANGE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to)
+{
+       read_inquiry_scan_type_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_INQUIRY_SCAN_TYPE;
+       rq.rparam = &rp;
+       rq.rlen   = READ_INQUIRY_SCAN_TYPE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *type = rp.type;
+       return 0;
+}
+
+int hci_write_inquiry_scan_type(int dd, uint8_t type, int to)
+{
+       write_inquiry_scan_type_cp cp;
+       write_inquiry_scan_type_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.type = type;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_INQUIRY_SCAN_TYPE;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_INQUIRY_SCAN_TYPE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = WRITE_INQUIRY_SCAN_TYPE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_inquiry_mode(int dd, uint8_t *mode, int to)
+{
+       read_inquiry_mode_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_INQUIRY_MODE;
+       rq.rparam = &rp;
+       rq.rlen   = READ_INQUIRY_MODE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *mode = rp.mode;
+       return 0;
+}
+
+int hci_write_inquiry_mode(int dd, uint8_t mode, int to)
+{
+       write_inquiry_mode_cp cp;
+       write_inquiry_mode_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.mode = mode;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_INQUIRY_MODE;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_INQUIRY_MODE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = WRITE_INQUIRY_MODE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_afh_mode(int dd, uint8_t *mode, int to)
+{
+       read_afh_mode_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_AFH_MODE;
+       rq.rparam = &rp;
+       rq.rlen   = READ_AFH_MODE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *mode = rp.mode;
+       return 0;
+}
+
+int hci_write_afh_mode(int dd, uint8_t mode, int to)
+{
+       write_afh_mode_cp cp;
+       write_afh_mode_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.mode = mode;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_AFH_MODE;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_AFH_MODE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = WRITE_AFH_MODE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_ext_inquiry_response(int dd, uint8_t *fec, uint8_t *data, int to)
+{
+       read_ext_inquiry_response_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_EXT_INQUIRY_RESPONSE;
+       rq.rparam = &rp;
+       rq.rlen   = READ_EXT_INQUIRY_RESPONSE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *fec = rp.fec;
+       memcpy(data, rp.data, 240);
+
+       return 0;
+}
+
+int hci_write_ext_inquiry_response(int dd, uint8_t fec, uint8_t *data, int to)
+{
+       write_ext_inquiry_response_cp cp;
+       write_ext_inquiry_response_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.fec = fec;
+       memcpy(cp.data, data, 240);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_EXT_INQUIRY_RESPONSE;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_simple_pairing_mode(int dd, uint8_t *mode, int to)
+{
+       read_simple_pairing_mode_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_SIMPLE_PAIRING_MODE;
+       rq.rparam = &rp;
+       rq.rlen   = READ_SIMPLE_PAIRING_MODE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *mode = rp.mode;
+       return 0;
+}
+
+int hci_write_simple_pairing_mode(int dd, uint8_t mode, int to)
+{
+       write_simple_pairing_mode_cp cp;
+       write_simple_pairing_mode_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.mode = mode;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_SIMPLE_PAIRING_MODE;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_SIMPLE_PAIRING_MODE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = WRITE_SIMPLE_PAIRING_MODE_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_local_oob_data(int dd, uint8_t *hash, uint8_t *randomizer, int to)
+{
+       read_local_oob_data_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_LOCAL_OOB_DATA;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LOCAL_OOB_DATA_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       memcpy(hash, rp.hash, 16);
+       memcpy(randomizer, rp.randomizer, 16);
+       return 0;
+}
+
+int hci_read_inq_response_tx_power_level(int dd, int8_t *level, int to)
+{
+       read_inq_response_tx_power_level_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL;
+       rq.rparam = &rp;
+       rq.rlen   = READ_INQ_RESPONSE_TX_POWER_LEVEL_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *level = rp.level;
+       return 0;
+}
+
+int hci_read_inquiry_transmit_power_level(int dd, int8_t *level, int to)
+{
+       return hci_read_inq_response_tx_power_level(dd, level, to);
+}
+
+int hci_write_inquiry_transmit_power_level(int dd, int8_t level, int to)
+{
+       write_inquiry_transmit_power_level_cp cp;
+       write_inquiry_transmit_power_level_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.level = level;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_transmit_power_level(int dd, uint16_t handle, uint8_t type,
+                                       int8_t *level, int to)
+{
+       read_transmit_power_level_cp cp;
+       read_transmit_power_level_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = handle;
+       cp.type   = type;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_TRANSMIT_POWER_LEVEL;
+       rq.cparam = &cp;
+       rq.clen   = READ_TRANSMIT_POWER_LEVEL_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = READ_TRANSMIT_POWER_LEVEL_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *level = rp.level;
+       return 0;
+}
+
+int hci_read_link_policy(int dd, uint16_t handle, uint16_t *policy, int to)
+{
+       read_link_policy_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_POLICY;
+       rq.ocf    = OCF_READ_LINK_POLICY;
+       rq.cparam = &handle;
+       rq.clen   = 2;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LINK_POLICY_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *policy = rp.policy;
+       return 0;
+}
+
+int hci_write_link_policy(int dd, uint16_t handle, uint16_t policy, int to)
+{
+       write_link_policy_cp cp;
+       write_link_policy_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = handle;
+       cp.policy = policy;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_POLICY;
+       rq.ocf    = OCF_WRITE_LINK_POLICY;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_LINK_POLICY_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = WRITE_LINK_POLICY_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_link_supervision_timeout(int dd, uint16_t handle,
+                                       uint16_t *timeout, int to)
+{
+       read_link_supervision_timeout_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_READ_LINK_SUPERVISION_TIMEOUT;
+       rq.cparam = &handle;
+       rq.clen   = 2;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *timeout = rp.timeout;
+       return 0;
+}
+
+int hci_write_link_supervision_timeout(int dd, uint16_t handle,
+                                       uint16_t timeout, int to)
+{
+       write_link_supervision_timeout_cp cp;
+       write_link_supervision_timeout_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle  = handle;
+       cp.timeout = timeout;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_WRITE_LINK_SUPERVISION_TIMEOUT;
+       rq.cparam = &cp;
+       rq.clen   = WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_set_afh_classification(int dd, uint8_t *map, int to)
+{
+       set_afh_classification_cp cp;
+       set_afh_classification_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       memcpy(cp.map, map, 10);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_HOST_CTL;
+       rq.ocf    = OCF_SET_AFH_CLASSIFICATION;
+       rq.cparam = &cp;
+       rq.clen   = SET_AFH_CLASSIFICATION_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = SET_AFH_CLASSIFICATION_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_read_link_quality(int dd, uint16_t handle, uint8_t *link_quality,
+                               int to)
+{
+       read_link_quality_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_STATUS_PARAM;
+       rq.ocf    = OCF_READ_LINK_QUALITY;
+       rq.cparam = &handle;
+       rq.clen   = 2;
+       rq.rparam = &rp;
+       rq.rlen   = READ_LINK_QUALITY_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *link_quality = rp.link_quality;
+       return 0;
+}
+
+int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to)
+{
+       read_rssi_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_STATUS_PARAM;
+       rq.ocf    = OCF_READ_RSSI;
+       rq.cparam = &handle;
+       rq.clen   = 2;
+       rq.rparam = &rp;
+       rq.rlen   = READ_RSSI_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *rssi = rp.rssi;
+       return 0;
+}
+
+int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map,
+                       int to)
+{
+       read_afh_map_rp rp;
+       struct hci_request rq;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_STATUS_PARAM;
+       rq.ocf    = OCF_READ_AFH_MAP;
+       rq.cparam = &handle;
+       rq.clen   = 2;
+       rq.rparam = &rp;
+       rq.rlen   = READ_AFH_MAP_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *mode = rp.mode;
+       memcpy(map, rp.map, 10);
+       return 0;
+}
+
+int hci_read_clock(int dd, uint16_t handle, uint8_t which, uint32_t *clock,
+                       uint16_t *accuracy, int to)
+{
+       read_clock_cp cp;
+       read_clock_rp rp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle      = handle;
+       cp.which_clock = which;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_STATUS_PARAM;
+       rq.ocf    = OCF_READ_CLOCK;
+       rq.cparam = &cp;
+       rq.clen   = READ_CLOCK_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = READ_CLOCK_RP_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       *clock    = rp.clock;
+       *accuracy = rp.accuracy;
+       return 0;
+}
+
+int hci_le_set_scan_enable(int dd, uint8_t enable, uint8_t filter_dup, int to)
+{
+       struct hci_request rq;
+       le_set_scan_enable_cp scan_cp;
+       uint8_t status;
+
+       memset(&scan_cp, 0, sizeof(scan_cp));
+       scan_cp.enable = enable;
+       scan_cp.filter_dup = filter_dup;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_SET_SCAN_ENABLE;
+       rq.cparam = &scan_cp;
+       rq.clen = LE_SET_SCAN_ENABLE_CP_SIZE;
+       rq.rparam = &status;
+       rq.rlen = 1;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_le_set_scan_parameters(int dd, uint8_t type,
+                                       uint16_t interval, uint16_t window,
+                                       uint8_t own_type, uint8_t filter, int to)
+{
+       struct hci_request rq;
+       le_set_scan_parameters_cp param_cp;
+       uint8_t status;
+
+       memset(&param_cp, 0, sizeof(param_cp));
+       param_cp.type = type;
+       param_cp.interval = interval;
+       param_cp.window = window;
+       param_cp.own_bdaddr_type = own_type;
+       param_cp.filter = filter;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_SET_SCAN_PARAMETERS;
+       rq.cparam = &param_cp;
+       rq.clen = LE_SET_SCAN_PARAMETERS_CP_SIZE;
+       rq.rparam = &status;
+       rq.rlen = 1;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_le_set_advertise_enable(int dd, uint8_t enable, int to)
+{
+       struct hci_request rq;
+       le_set_advertise_enable_cp adv_cp;
+       uint8_t status;
+
+       memset(&adv_cp, 0, sizeof(adv_cp));
+       adv_cp.enable = enable;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_SET_ADVERTISE_ENABLE;
+       rq.cparam = &adv_cp;
+       rq.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE;
+       rq.rparam = &status;
+       rq.rlen = 1;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int hci_le_create_conn(int dd, uint16_t interval, uint16_t window,
+               uint8_t initiator_filter, uint8_t peer_bdaddr_type,
+               bdaddr_t peer_bdaddr, uint8_t own_bdaddr_type,
+               uint16_t min_interval,  uint16_t max_interval,
+               uint16_t latency, uint16_t supervision_timeout,
+               uint16_t min_ce_length, uint16_t max_ce_length,
+               uint16_t *handle, int to)
+{
+       struct hci_request rq;
+       le_create_connection_cp create_conn_cp;
+       evt_le_connection_complete conn_complete_rp;
+
+       memset(&create_conn_cp, 0, sizeof(create_conn_cp));
+       create_conn_cp.interval = interval;
+       create_conn_cp.window = window;
+       create_conn_cp.initiator_filter = initiator_filter;
+       create_conn_cp.peer_bdaddr_type = peer_bdaddr_type;
+       create_conn_cp.peer_bdaddr = peer_bdaddr;
+       create_conn_cp.own_bdaddr_type = own_bdaddr_type;
+       create_conn_cp.min_interval = min_interval;
+       create_conn_cp.max_interval = max_interval;
+       create_conn_cp.latency = latency;
+       create_conn_cp.supervision_timeout = supervision_timeout;
+       create_conn_cp.min_ce_length = min_ce_length;
+       create_conn_cp.max_ce_length = max_ce_length;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_CREATE_CONN;
+       rq.event = EVT_LE_CONN_COMPLETE;
+       rq.cparam = &create_conn_cp;
+       rq.clen = LE_CREATE_CONN_CP_SIZE;
+       rq.rparam = &conn_complete_rp;
+       rq.rlen = EVT_CONN_COMPLETE_SIZE;
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (conn_complete_rp.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       if (handle)
+               *handle = conn_complete_rp.handle;
+
+       return 0;
+}
+
+int hci_le_conn_update(int dd, uint16_t handle, uint16_t min_interval,
+                       uint16_t max_interval, uint16_t latency,
+                       uint16_t supervision_timeout, int to)
+{
+       evt_le_connection_update_complete evt;
+       le_connection_update_cp cp;
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = handle;
+       cp.min_interval = min_interval;
+       cp.max_interval = max_interval;
+       cp.latency = latency;
+       cp.supervision_timeout = supervision_timeout;
+       cp.min_ce_length = htobs(0x0001);
+       cp.max_ce_length = htobs(0x0001);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_CONN_UPDATE;
+       rq.cparam = &cp;
+       rq.clen = LE_CONN_UPDATE_CP_SIZE;
+       rq.event = EVT_LE_CONN_UPDATE_COMPLETE;
+       rq.rparam = &evt;
+       rq.rlen = sizeof(evt);
+
+       if (hci_send_req(dd, &rq, to) < 0)
+               return -1;
+
+       if (evt.status) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/lib/hci.h b/lib/hci.h
new file mode 100644 (file)
index 0000000..d2d4f77
--- /dev/null
+++ b/lib/hci.h
@@ -0,0 +1,2367 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __HCI_H
+#define __HCI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/socket.h>
+
+#define HCI_MAX_DEV    16
+
+#define HCI_MAX_ACL_SIZE       1024
+#define HCI_MAX_SCO_SIZE       255
+#define HCI_MAX_EVENT_SIZE     260
+#define HCI_MAX_FRAME_SIZE     (HCI_MAX_ACL_SIZE + 4)
+
+/* HCI dev events */
+#define HCI_DEV_REG    1
+#define HCI_DEV_UNREG  2
+#define HCI_DEV_UP     3
+#define HCI_DEV_DOWN   4
+#define HCI_DEV_SUSPEND        5
+#define HCI_DEV_RESUME 6
+
+/* HCI bus types */
+#define HCI_VIRTUAL    0
+#define HCI_USB                1
+#define HCI_PCCARD     2
+#define HCI_UART       3
+#define HCI_RS232      4
+#define HCI_PCI                5
+#define HCI_SDIO       6
+
+/* HCI controller types */
+#define HCI_BREDR      0x00
+#define HCI_AMP                0x01
+
+/* HCI device flags */
+enum {
+       HCI_UP,
+       HCI_INIT,
+       HCI_RUNNING,
+
+       HCI_PSCAN,
+       HCI_ISCAN,
+       HCI_AUTH,
+       HCI_ENCRYPT,
+       HCI_INQUIRY,
+
+       HCI_RAW,
+};
+
+/* LE address type */
+enum {
+       LE_PUBLIC_ADDRESS = 0x00,
+       LE_RANDOM_ADDRESS = 0x01
+};
+
+/* HCI ioctl defines */
+#define HCIDEVUP       _IOW('H', 201, int)
+#define HCIDEVDOWN     _IOW('H', 202, int)
+#define HCIDEVRESET    _IOW('H', 203, int)
+#define HCIDEVRESTAT   _IOW('H', 204, int)
+
+#define HCIGETDEVLIST  _IOR('H', 210, int)
+#define HCIGETDEVINFO  _IOR('H', 211, int)
+#define HCIGETCONNLIST _IOR('H', 212, int)
+#define HCIGETCONNINFO _IOR('H', 213, int)
+#define HCIGETAUTHINFO _IOR('H', 215, int)
+
+#define HCISETRAW      _IOW('H', 220, int)
+#define HCISETSCAN     _IOW('H', 221, int)
+#define HCISETAUTH     _IOW('H', 222, int)
+#define HCISETENCRYPT  _IOW('H', 223, int)
+#define HCISETPTYPE    _IOW('H', 224, int)
+#define HCISETLINKPOL  _IOW('H', 225, int)
+#define HCISETLINKMODE _IOW('H', 226, int)
+#define HCISETACLMTU   _IOW('H', 227, int)
+#define HCISETSCOMTU   _IOW('H', 228, int)
+
+#define HCIBLOCKADDR   _IOW('H', 230, int)
+#define HCIUNBLOCKADDR _IOW('H', 231, int)
+
+#define HCIINQUIRY     _IOR('H', 240, int)
+
+#ifndef __NO_HCI_DEFS
+
+/* HCI Packet types */
+#define HCI_COMMAND_PKT                0x01
+#define HCI_ACLDATA_PKT                0x02
+#define HCI_SCODATA_PKT                0x03
+#define HCI_EVENT_PKT          0x04
+#define HCI_VENDOR_PKT         0xff
+
+/* HCI Packet types */
+#define HCI_2DH1       0x0002
+#define HCI_3DH1       0x0004
+#define HCI_DM1                0x0008
+#define HCI_DH1                0x0010
+#define HCI_2DH3       0x0100
+#define HCI_3DH3       0x0200
+#define HCI_DM3                0x0400
+#define HCI_DH3                0x0800
+#define HCI_2DH5       0x1000
+#define HCI_3DH5       0x2000
+#define HCI_DM5                0x4000
+#define HCI_DH5                0x8000
+
+#define HCI_HV1                0x0020
+#define HCI_HV2                0x0040
+#define HCI_HV3                0x0080
+
+#define HCI_EV3                0x0008
+#define HCI_EV4                0x0010
+#define HCI_EV5                0x0020
+#define HCI_2EV3       0x0040
+#define HCI_3EV3       0x0080
+#define HCI_2EV5       0x0100
+#define HCI_3EV5       0x0200
+
+#define SCO_PTYPE_MASK (HCI_HV1 | HCI_HV2 | HCI_HV3)
+#define ACL_PTYPE_MASK (HCI_DM1 | HCI_DH1 | HCI_DM3 | HCI_DH3 | HCI_DM5 | HCI_DH5)
+
+/* HCI Error codes */
+#define HCI_UNKNOWN_COMMAND                    0x01
+#define HCI_NO_CONNECTION                      0x02
+#define HCI_HARDWARE_FAILURE                   0x03
+#define HCI_PAGE_TIMEOUT                       0x04
+#define HCI_AUTHENTICATION_FAILURE             0x05
+#define HCI_PIN_OR_KEY_MISSING                 0x06
+#define HCI_MEMORY_FULL                                0x07
+#define HCI_CONNECTION_TIMEOUT                 0x08
+#define HCI_MAX_NUMBER_OF_CONNECTIONS          0x09
+#define HCI_MAX_NUMBER_OF_SCO_CONNECTIONS      0x0a
+#define HCI_ACL_CONNECTION_EXISTS              0x0b
+#define HCI_COMMAND_DISALLOWED                 0x0c
+#define HCI_REJECTED_LIMITED_RESOURCES         0x0d
+#define HCI_REJECTED_SECURITY                  0x0e
+#define HCI_REJECTED_PERSONAL                  0x0f
+#define HCI_HOST_TIMEOUT                       0x10
+#define HCI_UNSUPPORTED_FEATURE                        0x11
+#define HCI_INVALID_PARAMETERS                 0x12
+#define HCI_OE_USER_ENDED_CONNECTION           0x13
+#define HCI_OE_LOW_RESOURCES                   0x14
+#define HCI_OE_POWER_OFF                       0x15
+#define HCI_CONNECTION_TERMINATED              0x16
+#define HCI_REPEATED_ATTEMPTS                  0x17
+#define HCI_PAIRING_NOT_ALLOWED                        0x18
+#define HCI_UNKNOWN_LMP_PDU                    0x19
+#define HCI_UNSUPPORTED_REMOTE_FEATURE         0x1a
+#define HCI_SCO_OFFSET_REJECTED                        0x1b
+#define HCI_SCO_INTERVAL_REJECTED              0x1c
+#define HCI_AIR_MODE_REJECTED                  0x1d
+#define HCI_INVALID_LMP_PARAMETERS             0x1e
+#define HCI_UNSPECIFIED_ERROR                  0x1f
+#define HCI_UNSUPPORTED_LMP_PARAMETER_VALUE    0x20
+#define HCI_ROLE_CHANGE_NOT_ALLOWED            0x21
+#define HCI_LMP_RESPONSE_TIMEOUT               0x22
+#define HCI_LMP_ERROR_TRANSACTION_COLLISION    0x23
+#define HCI_LMP_PDU_NOT_ALLOWED                        0x24
+#define HCI_ENCRYPTION_MODE_NOT_ACCEPTED       0x25
+#define HCI_UNIT_LINK_KEY_USED                 0x26
+#define HCI_QOS_NOT_SUPPORTED                  0x27
+#define HCI_INSTANT_PASSED                     0x28
+#define HCI_PAIRING_NOT_SUPPORTED              0x29
+#define HCI_TRANSACTION_COLLISION              0x2a
+#define HCI_QOS_UNACCEPTABLE_PARAMETER         0x2c
+#define HCI_QOS_REJECTED                       0x2d
+#define HCI_CLASSIFICATION_NOT_SUPPORTED       0x2e
+#define HCI_INSUFFICIENT_SECURITY              0x2f
+#define HCI_PARAMETER_OUT_OF_RANGE             0x30
+#define HCI_ROLE_SWITCH_PENDING                        0x32
+#define HCI_SLOT_VIOLATION                     0x34
+#define HCI_ROLE_SWITCH_FAILED                 0x35
+#define HCI_EIR_TOO_LARGE                      0x36
+#define HCI_SIMPLE_PAIRING_NOT_SUPPORTED       0x37
+#define HCI_HOST_BUSY_PAIRING                  0x38
+
+/* ACL flags */
+#define ACL_START_NO_FLUSH     0x00
+#define ACL_CONT               0x01
+#define ACL_START              0x02
+#define ACL_ACTIVE_BCAST       0x04
+#define ACL_PICO_BCAST         0x08
+
+/* Baseband links */
+#define SCO_LINK       0x00
+#define ACL_LINK       0x01
+#define ESCO_LINK      0x02
+
+/* LMP features */
+#define LMP_3SLOT      0x01
+#define LMP_5SLOT      0x02
+#define LMP_ENCRYPT    0x04
+#define LMP_SOFFSET    0x08
+#define LMP_TACCURACY  0x10
+#define LMP_RSWITCH    0x20
+#define LMP_HOLD       0x40
+#define LMP_SNIFF      0x80
+
+#define LMP_PARK       0x01
+#define LMP_RSSI       0x02
+#define LMP_QUALITY    0x04
+#define LMP_SCO                0x08
+#define LMP_HV2                0x10
+#define LMP_HV3                0x20
+#define LMP_ULAW       0x40
+#define LMP_ALAW       0x80
+
+#define LMP_CVSD       0x01
+#define LMP_PSCHEME    0x02
+#define LMP_PCONTROL   0x04
+#define LMP_TRSP_SCO   0x08
+#define LMP_BCAST_ENC  0x80
+
+#define LMP_EDR_ACL_2M 0x02
+#define LMP_EDR_ACL_3M 0x04
+#define LMP_ENH_ISCAN  0x08
+#define LMP_ILACE_ISCAN        0x10
+#define LMP_ILACE_PSCAN        0x20
+#define LMP_RSSI_INQ   0x40
+#define LMP_ESCO       0x80
+
+#define LMP_EV4                0x01
+#define LMP_EV5                0x02
+#define LMP_AFH_CAP_SLV        0x08
+#define LMP_AFH_CLS_SLV        0x10
+#define LMP_NO_BREDR   0x20
+#define LMP_LE         0x40
+#define LMP_EDR_3SLOT  0x80
+
+#define LMP_EDR_5SLOT  0x01
+#define LMP_SNIFF_SUBR 0x02
+#define LMP_PAUSE_ENC  0x04
+#define LMP_AFH_CAP_MST        0x08
+#define LMP_AFH_CLS_MST        0x10
+#define LMP_EDR_ESCO_2M        0x20
+#define LMP_EDR_ESCO_3M        0x40
+#define LMP_EDR_3S_ESCO        0x80
+
+#define LMP_EXT_INQ    0x01
+#define LMP_LE_BREDR   0x02
+#define LMP_SIMPLE_PAIR        0x08
+#define LMP_ENCAPS_PDU 0x10
+#define LMP_ERR_DAT_REP        0x20
+#define LMP_NFLUSH_PKTS        0x40
+
+#define LMP_LSTO       0x01
+#define LMP_INQ_TX_PWR 0x02
+#define LMP_EPC                0x04
+#define LMP_EXT_FEAT   0x80
+
+/* Extended LMP features */
+#define LMP_HOST_LE    0x02
+
+/* Link policies */
+#define HCI_LP_RSWITCH 0x0001
+#define HCI_LP_HOLD    0x0002
+#define HCI_LP_SNIFF   0x0004
+#define HCI_LP_PARK    0x0008
+
+/* Link mode */
+#define HCI_LM_ACCEPT  0x8000
+#define HCI_LM_MASTER  0x0001
+#define HCI_LM_AUTH    0x0002
+#define HCI_LM_ENCRYPT 0x0004
+#define HCI_LM_TRUSTED 0x0008
+#define HCI_LM_RELIABLE        0x0010
+#define HCI_LM_SECURE  0x0020
+
+/* -----  HCI Commands ----- */
+
+/* Link Control */
+#define OGF_LINK_CTL           0x01
+
+#define OCF_INQUIRY                    0x0001
+typedef struct {
+       uint8_t         lap[3];
+       uint8_t         length;         /* 1.28s units */
+       uint8_t         num_rsp;
+} __attribute__ ((packed)) inquiry_cp;
+#define INQUIRY_CP_SIZE 5
+
+typedef struct {
+       uint8_t         status;
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) status_bdaddr_rp;
+#define STATUS_BDADDR_RP_SIZE 7
+
+#define OCF_INQUIRY_CANCEL             0x0002
+
+#define OCF_PERIODIC_INQUIRY           0x0003
+typedef struct {
+       uint16_t        max_period;     /* 1.28s units */
+       uint16_t        min_period;     /* 1.28s units */
+       uint8_t         lap[3];
+       uint8_t         length;         /* 1.28s units */
+       uint8_t         num_rsp;
+} __attribute__ ((packed)) periodic_inquiry_cp;
+#define PERIODIC_INQUIRY_CP_SIZE 9
+
+#define OCF_EXIT_PERIODIC_INQUIRY      0x0004
+
+#define OCF_CREATE_CONN                        0x0005
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint16_t        pkt_type;
+       uint8_t         pscan_rep_mode;
+       uint8_t         pscan_mode;
+       uint16_t        clock_offset;
+       uint8_t         role_switch;
+} __attribute__ ((packed)) create_conn_cp;
+#define CREATE_CONN_CP_SIZE 13
+
+#define OCF_DISCONNECT                 0x0006
+typedef struct {
+       uint16_t        handle;
+       uint8_t         reason;
+} __attribute__ ((packed)) disconnect_cp;
+#define DISCONNECT_CP_SIZE 3
+
+#define OCF_ADD_SCO                    0x0007
+typedef struct {
+       uint16_t        handle;
+       uint16_t        pkt_type;
+} __attribute__ ((packed)) add_sco_cp;
+#define ADD_SCO_CP_SIZE 4
+
+#define OCF_CREATE_CONN_CANCEL         0x0008
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) create_conn_cancel_cp;
+#define CREATE_CONN_CANCEL_CP_SIZE 6
+
+#define OCF_ACCEPT_CONN_REQ            0x0009
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         role;
+} __attribute__ ((packed)) accept_conn_req_cp;
+#define ACCEPT_CONN_REQ_CP_SIZE        7
+
+#define OCF_REJECT_CONN_REQ            0x000A
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         reason;
+} __attribute__ ((packed)) reject_conn_req_cp;
+#define REJECT_CONN_REQ_CP_SIZE        7
+
+#define OCF_LINK_KEY_REPLY             0x000B
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         link_key[16];
+} __attribute__ ((packed)) link_key_reply_cp;
+#define LINK_KEY_REPLY_CP_SIZE 22
+
+#define OCF_LINK_KEY_NEG_REPLY         0x000C
+
+#define OCF_PIN_CODE_REPLY             0x000D
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         pin_len;
+       uint8_t         pin_code[16];
+} __attribute__ ((packed)) pin_code_reply_cp;
+#define PIN_CODE_REPLY_CP_SIZE 23
+
+#define OCF_PIN_CODE_NEG_REPLY         0x000E
+
+#define OCF_SET_CONN_PTYPE             0x000F
+typedef struct {
+       uint16_t         handle;
+       uint16_t         pkt_type;
+} __attribute__ ((packed)) set_conn_ptype_cp;
+#define SET_CONN_PTYPE_CP_SIZE 4
+
+#define OCF_AUTH_REQUESTED             0x0011
+typedef struct {
+       uint16_t         handle;
+} __attribute__ ((packed)) auth_requested_cp;
+#define AUTH_REQUESTED_CP_SIZE 2
+
+#define OCF_SET_CONN_ENCRYPT           0x0013
+typedef struct {
+       uint16_t        handle;
+       uint8_t         encrypt;
+} __attribute__ ((packed)) set_conn_encrypt_cp;
+#define SET_CONN_ENCRYPT_CP_SIZE 3
+
+#define OCF_CHANGE_CONN_LINK_KEY       0x0015
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) change_conn_link_key_cp;
+#define CHANGE_CONN_LINK_KEY_CP_SIZE 2
+
+#define OCF_MASTER_LINK_KEY            0x0017
+typedef struct {
+       uint8_t         key_flag;
+} __attribute__ ((packed)) master_link_key_cp;
+#define MASTER_LINK_KEY_CP_SIZE 1
+
+#define OCF_REMOTE_NAME_REQ            0x0019
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         pscan_rep_mode;
+       uint8_t         pscan_mode;
+       uint16_t        clock_offset;
+} __attribute__ ((packed)) remote_name_req_cp;
+#define REMOTE_NAME_REQ_CP_SIZE 10
+
+#define OCF_REMOTE_NAME_REQ_CANCEL     0x001A
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) remote_name_req_cancel_cp;
+#define REMOTE_NAME_REQ_CANCEL_CP_SIZE 6
+
+#define OCF_READ_REMOTE_FEATURES       0x001B
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) read_remote_features_cp;
+#define READ_REMOTE_FEATURES_CP_SIZE 2
+
+#define OCF_READ_REMOTE_EXT_FEATURES   0x001C
+typedef struct {
+       uint16_t        handle;
+       uint8_t         page_num;
+} __attribute__ ((packed)) read_remote_ext_features_cp;
+#define READ_REMOTE_EXT_FEATURES_CP_SIZE 3
+
+#define OCF_READ_REMOTE_VERSION                0x001D
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) read_remote_version_cp;
+#define READ_REMOTE_VERSION_CP_SIZE 2
+
+#define OCF_READ_CLOCK_OFFSET          0x001F
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) read_clock_offset_cp;
+#define READ_CLOCK_OFFSET_CP_SIZE 2
+
+#define OCF_READ_LMP_HANDLE            0x0020
+
+#define OCF_SETUP_SYNC_CONN            0x0028
+typedef struct {
+       uint16_t        handle;
+       uint32_t        tx_bandwith;
+       uint32_t        rx_bandwith;
+       uint16_t        max_latency;
+       uint16_t        voice_setting;
+       uint8_t         retrans_effort;
+       uint16_t        pkt_type;
+} __attribute__ ((packed)) setup_sync_conn_cp;
+#define SETUP_SYNC_CONN_CP_SIZE 17
+
+#define OCF_ACCEPT_SYNC_CONN_REQ       0x0029
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint32_t        tx_bandwith;
+       uint32_t        rx_bandwith;
+       uint16_t        max_latency;
+       uint16_t        voice_setting;
+       uint8_t         retrans_effort;
+       uint16_t        pkt_type;
+} __attribute__ ((packed)) accept_sync_conn_req_cp;
+#define ACCEPT_SYNC_CONN_REQ_CP_SIZE 21
+
+#define OCF_REJECT_SYNC_CONN_REQ       0x002A
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         reason;
+} __attribute__ ((packed)) reject_sync_conn_req_cp;
+#define REJECT_SYNC_CONN_REQ_CP_SIZE 7
+
+#define OCF_IO_CAPABILITY_REPLY                0x002B
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         capability;
+       uint8_t         oob_data;
+       uint8_t         authentication;
+} __attribute__ ((packed)) io_capability_reply_cp;
+#define IO_CAPABILITY_REPLY_CP_SIZE 9
+
+#define OCF_USER_CONFIRM_REPLY         0x002C
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) user_confirm_reply_cp;
+#define USER_CONFIRM_REPLY_CP_SIZE 6
+
+#define OCF_USER_CONFIRM_NEG_REPLY     0x002D
+
+#define OCF_USER_PASSKEY_REPLY         0x002E
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint32_t        passkey;
+} __attribute__ ((packed)) user_passkey_reply_cp;
+#define USER_PASSKEY_REPLY_CP_SIZE 10
+
+#define OCF_USER_PASSKEY_NEG_REPLY     0x002F
+
+#define OCF_REMOTE_OOB_DATA_REPLY      0x0030
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         hash[16];
+       uint8_t         randomizer[16];
+} __attribute__ ((packed)) remote_oob_data_reply_cp;
+#define REMOTE_OOB_DATA_REPLY_CP_SIZE 38
+
+#define OCF_REMOTE_OOB_DATA_NEG_REPLY  0x0033
+
+#define OCF_IO_CAPABILITY_NEG_REPLY    0x0034
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         reason;
+} __attribute__ ((packed)) io_capability_neg_reply_cp;
+#define IO_CAPABILITY_NEG_REPLY_CP_SIZE 7
+
+#define OCF_CREATE_PHYSICAL_LINK               0x0035
+typedef struct {
+       uint8_t         handle;
+       uint8_t         key_length;
+       uint8_t         key_type;
+       uint8_t         key[32];
+} __attribute__ ((packed)) create_physical_link_cp;
+#define CREATE_PHYSICAL_LINK_CP_SIZE 35
+
+#define OCF_ACCEPT_PHYSICAL_LINK               0x0036
+
+#define OCF_DISCONNECT_PHYSICAL_LINK           0x0037
+typedef struct {
+       uint8_t         handle;
+       uint8_t         reason;
+} __attribute__ ((packed)) disconnect_physical_link_cp;
+#define DISCONNECT_PHYSICAL_LINK_CP_SIZE 2
+
+#define OCF_CREATE_LOGICAL_LINK                0x0038
+typedef struct {
+       uint8_t         handle;
+       uint8_t         tx_flow[16];
+       uint8_t         rx_flow[16];
+} __attribute__ ((packed)) create_logical_link_cp;
+#define CREATE_LOGICAL_LINK_CP_SIZE 33
+
+#define OCF_ACCEPT_LOGICAL_LINK                0x0039
+
+#define OCF_DISCONNECT_LOGICAL_LINK            0x003A
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) disconnect_logical_link_cp;
+#define DISCONNECT_LOGICAL_LINK_CP_SIZE 2
+
+#define OCF_LOGICAL_LINK_CANCEL                0x003B
+typedef struct {
+       uint8_t         handle;
+       uint8_t         tx_flow_id;
+} __attribute__ ((packed)) cancel_logical_link_cp;
+#define LOGICAL_LINK_CANCEL_CP_SIZE 2
+typedef struct {
+       uint8_t         status;
+       uint8_t         handle;
+       uint8_t         tx_flow_id;
+} __attribute__ ((packed)) cancel_logical_link_rp;
+#define LOGICAL_LINK_CANCEL_RP_SIZE 3
+
+#define OCF_FLOW_SPEC_MODIFY           0x003C
+
+/* Link Policy */
+#define OGF_LINK_POLICY                0x02
+
+#define OCF_HOLD_MODE                  0x0001
+typedef struct {
+       uint16_t        handle;
+       uint16_t        max_interval;
+       uint16_t        min_interval;
+} __attribute__ ((packed)) hold_mode_cp;
+#define HOLD_MODE_CP_SIZE 6
+
+#define OCF_SNIFF_MODE                 0x0003
+typedef struct {
+       uint16_t        handle;
+       uint16_t        max_interval;
+       uint16_t        min_interval;
+       uint16_t        attempt;
+       uint16_t        timeout;
+} __attribute__ ((packed)) sniff_mode_cp;
+#define SNIFF_MODE_CP_SIZE 10
+
+#define OCF_EXIT_SNIFF_MODE            0x0004
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) exit_sniff_mode_cp;
+#define EXIT_SNIFF_MODE_CP_SIZE 2
+
+#define OCF_PARK_MODE                  0x0005
+typedef struct {
+       uint16_t        handle;
+       uint16_t        max_interval;
+       uint16_t        min_interval;
+} __attribute__ ((packed)) park_mode_cp;
+#define PARK_MODE_CP_SIZE 6
+
+#define OCF_EXIT_PARK_MODE             0x0006
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) exit_park_mode_cp;
+#define EXIT_PARK_MODE_CP_SIZE 2
+
+#define OCF_QOS_SETUP                  0x0007
+typedef struct {
+       uint8_t         service_type;           /* 1 = best effort */
+       uint32_t        token_rate;             /* Byte per seconds */
+       uint32_t        peak_bandwidth;         /* Byte per seconds */
+       uint32_t        latency;                /* Microseconds */
+       uint32_t        delay_variation;        /* Microseconds */
+} __attribute__ ((packed)) hci_qos;
+#define HCI_QOS_CP_SIZE 17
+typedef struct {
+       uint16_t        handle;
+       uint8_t         flags;                  /* Reserved */
+       hci_qos         qos;
+} __attribute__ ((packed)) qos_setup_cp;
+#define QOS_SETUP_CP_SIZE (3 + HCI_QOS_CP_SIZE)
+
+#define OCF_ROLE_DISCOVERY             0x0009
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) role_discovery_cp;
+#define ROLE_DISCOVERY_CP_SIZE 2
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         role;
+} __attribute__ ((packed)) role_discovery_rp;
+#define ROLE_DISCOVERY_RP_SIZE 4
+
+#define OCF_SWITCH_ROLE                        0x000B
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         role;
+} __attribute__ ((packed)) switch_role_cp;
+#define SWITCH_ROLE_CP_SIZE 7
+
+#define OCF_READ_LINK_POLICY           0x000C
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) read_link_policy_cp;
+#define READ_LINK_POLICY_CP_SIZE 2
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint16_t        policy;
+} __attribute__ ((packed)) read_link_policy_rp;
+#define READ_LINK_POLICY_RP_SIZE 5
+
+#define OCF_WRITE_LINK_POLICY          0x000D
+typedef struct {
+       uint16_t        handle;
+       uint16_t        policy;
+} __attribute__ ((packed)) write_link_policy_cp;
+#define WRITE_LINK_POLICY_CP_SIZE 4
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+} __attribute__ ((packed)) write_link_policy_rp;
+#define WRITE_LINK_POLICY_RP_SIZE 3
+
+#define OCF_READ_DEFAULT_LINK_POLICY   0x000E
+
+#define OCF_WRITE_DEFAULT_LINK_POLICY  0x000F
+
+#define OCF_FLOW_SPECIFICATION         0x0010
+
+#define OCF_SNIFF_SUBRATING            0x0011
+typedef struct {
+       uint16_t        handle;
+       uint16_t        max_latency;
+       uint16_t        min_remote_timeout;
+       uint16_t        min_local_timeout;
+} __attribute__ ((packed)) sniff_subrating_cp;
+#define SNIFF_SUBRATING_CP_SIZE 8
+
+/* Host Controller and Baseband */
+#define OGF_HOST_CTL           0x03
+
+#define OCF_SET_EVENT_MASK             0x0001
+typedef struct {
+       uint8_t         mask[8];
+} __attribute__ ((packed)) set_event_mask_cp;
+#define SET_EVENT_MASK_CP_SIZE 8
+
+#define OCF_RESET                      0x0003
+
+#define OCF_SET_EVENT_FLT              0x0005
+typedef struct {
+       uint8_t         flt_type;
+       uint8_t         cond_type;
+       uint8_t         condition[0];
+} __attribute__ ((packed)) set_event_flt_cp;
+#define SET_EVENT_FLT_CP_SIZE 2
+
+/* Filter types */
+#define FLT_CLEAR_ALL                  0x00
+#define FLT_INQ_RESULT                 0x01
+#define FLT_CONN_SETUP                 0x02
+/* INQ_RESULT Condition types */
+#define INQ_RESULT_RETURN_ALL          0x00
+#define INQ_RESULT_RETURN_CLASS                0x01
+#define INQ_RESULT_RETURN_BDADDR       0x02
+/* CONN_SETUP Condition types */
+#define CONN_SETUP_ALLOW_ALL           0x00
+#define CONN_SETUP_ALLOW_CLASS         0x01
+#define CONN_SETUP_ALLOW_BDADDR                0x02
+/* CONN_SETUP Conditions */
+#define CONN_SETUP_AUTO_OFF            0x01
+#define CONN_SETUP_AUTO_ON             0x02
+
+#define OCF_FLUSH                      0x0008
+
+#define OCF_READ_PIN_TYPE              0x0009
+typedef struct {
+       uint8_t         status;
+       uint8_t         pin_type;
+} __attribute__ ((packed)) read_pin_type_rp;
+#define READ_PIN_TYPE_RP_SIZE 2
+
+#define OCF_WRITE_PIN_TYPE             0x000A
+typedef struct {
+       uint8_t         pin_type;
+} __attribute__ ((packed)) write_pin_type_cp;
+#define WRITE_PIN_TYPE_CP_SIZE 1
+
+#define OCF_CREATE_NEW_UNIT_KEY                0x000B
+
+#define OCF_READ_STORED_LINK_KEY       0x000D
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         read_all;
+} __attribute__ ((packed)) read_stored_link_key_cp;
+#define READ_STORED_LINK_KEY_CP_SIZE 7
+typedef struct {
+       uint8_t         status;
+       uint16_t        max_keys;
+       uint16_t        num_keys;
+} __attribute__ ((packed)) read_stored_link_key_rp;
+#define READ_STORED_LINK_KEY_RP_SIZE 5
+
+#define OCF_WRITE_STORED_LINK_KEY      0x0011
+typedef struct {
+       uint8_t         num_keys;
+       /* variable length part */
+} __attribute__ ((packed)) write_stored_link_key_cp;
+#define WRITE_STORED_LINK_KEY_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+       uint8_t         num_keys;
+} __attribute__ ((packed)) write_stored_link_key_rp;
+#define READ_WRITE_LINK_KEY_RP_SIZE 2
+
+#define OCF_DELETE_STORED_LINK_KEY     0x0012
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         delete_all;
+} __attribute__ ((packed)) delete_stored_link_key_cp;
+#define DELETE_STORED_LINK_KEY_CP_SIZE 7
+typedef struct {
+       uint8_t         status;
+       uint16_t        num_keys;
+} __attribute__ ((packed)) delete_stored_link_key_rp;
+#define DELETE_STORED_LINK_KEY_RP_SIZE 3
+
+#define OCF_CHANGE_LOCAL_NAME          0x0013
+typedef struct {
+       uint8_t         name[248];
+} __attribute__ ((packed)) change_local_name_cp;
+#define CHANGE_LOCAL_NAME_CP_SIZE 248
+
+#define OCF_READ_LOCAL_NAME            0x0014
+typedef struct {
+       uint8_t         status;
+       uint8_t         name[248];
+} __attribute__ ((packed)) read_local_name_rp;
+#define READ_LOCAL_NAME_RP_SIZE 249
+
+#define OCF_READ_CONN_ACCEPT_TIMEOUT   0x0015
+typedef struct {
+       uint8_t         status;
+       uint16_t        timeout;
+} __attribute__ ((packed)) read_conn_accept_timeout_rp;
+#define READ_CONN_ACCEPT_TIMEOUT_RP_SIZE 3
+
+#define OCF_WRITE_CONN_ACCEPT_TIMEOUT  0x0016
+typedef struct {
+       uint16_t        timeout;
+} __attribute__ ((packed)) write_conn_accept_timeout_cp;
+#define WRITE_CONN_ACCEPT_TIMEOUT_CP_SIZE 2
+
+#define OCF_READ_PAGE_TIMEOUT          0x0017
+typedef struct {
+       uint8_t         status;
+       uint16_t        timeout;
+} __attribute__ ((packed)) read_page_timeout_rp;
+#define READ_PAGE_TIMEOUT_RP_SIZE 3
+
+#define OCF_WRITE_PAGE_TIMEOUT         0x0018
+typedef struct {
+       uint16_t        timeout;
+} __attribute__ ((packed)) write_page_timeout_cp;
+#define WRITE_PAGE_TIMEOUT_CP_SIZE 2
+
+#define OCF_READ_SCAN_ENABLE           0x0019
+typedef struct {
+       uint8_t         status;
+       uint8_t         enable;
+} __attribute__ ((packed)) read_scan_enable_rp;
+#define READ_SCAN_ENABLE_RP_SIZE 2
+
+#define OCF_WRITE_SCAN_ENABLE          0x001A
+       #define SCAN_DISABLED           0x00
+       #define SCAN_INQUIRY            0x01
+       #define SCAN_PAGE               0x02
+
+#define OCF_READ_PAGE_ACTIVITY         0x001B
+typedef struct {
+       uint8_t         status;
+       uint16_t        interval;
+       uint16_t        window;
+} __attribute__ ((packed)) read_page_activity_rp;
+#define READ_PAGE_ACTIVITY_RP_SIZE 5
+
+#define OCF_WRITE_PAGE_ACTIVITY                0x001C
+typedef struct {
+       uint16_t        interval;
+       uint16_t        window;
+} __attribute__ ((packed)) write_page_activity_cp;
+#define WRITE_PAGE_ACTIVITY_CP_SIZE 4
+
+#define OCF_READ_INQ_ACTIVITY          0x001D
+typedef struct {
+       uint8_t         status;
+       uint16_t        interval;
+       uint16_t        window;
+} __attribute__ ((packed)) read_inq_activity_rp;
+#define READ_INQ_ACTIVITY_RP_SIZE 5
+
+#define OCF_WRITE_INQ_ACTIVITY         0x001E
+typedef struct {
+       uint16_t        interval;
+       uint16_t        window;
+} __attribute__ ((packed)) write_inq_activity_cp;
+#define WRITE_INQ_ACTIVITY_CP_SIZE 4
+
+#define OCF_READ_AUTH_ENABLE           0x001F
+
+#define OCF_WRITE_AUTH_ENABLE          0x0020
+       #define AUTH_DISABLED           0x00
+       #define AUTH_ENABLED            0x01
+
+#define OCF_READ_ENCRYPT_MODE          0x0021
+
+#define OCF_WRITE_ENCRYPT_MODE         0x0022
+       #define ENCRYPT_DISABLED        0x00
+       #define ENCRYPT_P2P             0x01
+       #define ENCRYPT_BOTH            0x02
+
+#define OCF_READ_CLASS_OF_DEV          0x0023
+typedef struct {
+       uint8_t         status;
+       uint8_t         dev_class[3];
+} __attribute__ ((packed)) read_class_of_dev_rp;
+#define READ_CLASS_OF_DEV_RP_SIZE 4
+
+#define OCF_WRITE_CLASS_OF_DEV         0x0024
+typedef struct {
+       uint8_t         dev_class[3];
+} __attribute__ ((packed)) write_class_of_dev_cp;
+#define WRITE_CLASS_OF_DEV_CP_SIZE 3
+
+#define OCF_READ_VOICE_SETTING         0x0025
+typedef struct {
+       uint8_t         status;
+       uint16_t        voice_setting;
+} __attribute__ ((packed)) read_voice_setting_rp;
+#define READ_VOICE_SETTING_RP_SIZE 3
+
+#define OCF_WRITE_VOICE_SETTING                0x0026
+typedef struct {
+       uint16_t        voice_setting;
+} __attribute__ ((packed)) write_voice_setting_cp;
+#define WRITE_VOICE_SETTING_CP_SIZE 2
+
+#define OCF_READ_AUTOMATIC_FLUSH_TIMEOUT       0x0027
+
+#define OCF_WRITE_AUTOMATIC_FLUSH_TIMEOUT      0x0028
+
+#define OCF_READ_NUM_BROADCAST_RETRANS 0x0029
+
+#define OCF_WRITE_NUM_BROADCAST_RETRANS        0x002A
+
+#define OCF_READ_HOLD_MODE_ACTIVITY    0x002B
+
+#define OCF_WRITE_HOLD_MODE_ACTIVITY   0x002C
+
+#define OCF_READ_TRANSMIT_POWER_LEVEL  0x002D
+typedef struct {
+       uint16_t        handle;
+       uint8_t         type;
+} __attribute__ ((packed)) read_transmit_power_level_cp;
+#define READ_TRANSMIT_POWER_LEVEL_CP_SIZE 3
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       int8_t          level;
+} __attribute__ ((packed)) read_transmit_power_level_rp;
+#define READ_TRANSMIT_POWER_LEVEL_RP_SIZE 4
+
+#define OCF_READ_SYNC_FLOW_ENABLE      0x002E
+
+#define OCF_WRITE_SYNC_FLOW_ENABLE     0x002F
+
+#define OCF_SET_CONTROLLER_TO_HOST_FC  0x0031
+
+#define OCF_HOST_BUFFER_SIZE           0x0033
+typedef struct {
+       uint16_t        acl_mtu;
+       uint8_t         sco_mtu;
+       uint16_t        acl_max_pkt;
+       uint16_t        sco_max_pkt;
+} __attribute__ ((packed)) host_buffer_size_cp;
+#define HOST_BUFFER_SIZE_CP_SIZE 7
+
+#define OCF_HOST_NUM_COMP_PKTS         0x0035
+typedef struct {
+       uint8_t         num_hndl;
+       /* variable length part */
+} __attribute__ ((packed)) host_num_comp_pkts_cp;
+#define HOST_NUM_COMP_PKTS_CP_SIZE 1
+
+#define OCF_READ_LINK_SUPERVISION_TIMEOUT      0x0036
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint16_t        timeout;
+} __attribute__ ((packed)) read_link_supervision_timeout_rp;
+#define READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE 5
+
+#define OCF_WRITE_LINK_SUPERVISION_TIMEOUT     0x0037
+typedef struct {
+       uint16_t        handle;
+       uint16_t        timeout;
+} __attribute__ ((packed)) write_link_supervision_timeout_cp;
+#define WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE 4
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+} __attribute__ ((packed)) write_link_supervision_timeout_rp;
+#define WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE 3
+
+#define OCF_READ_NUM_SUPPORTED_IAC     0x0038
+
+#define MAX_IAC_LAP 0x40
+#define OCF_READ_CURRENT_IAC_LAP       0x0039
+typedef struct {
+       uint8_t         status;
+       uint8_t         num_current_iac;
+       uint8_t         lap[MAX_IAC_LAP][3];
+} __attribute__ ((packed)) read_current_iac_lap_rp;
+#define READ_CURRENT_IAC_LAP_RP_SIZE 2+3*MAX_IAC_LAP
+
+#define OCF_WRITE_CURRENT_IAC_LAP      0x003A
+typedef struct {
+       uint8_t         num_current_iac;
+       uint8_t         lap[MAX_IAC_LAP][3];
+} __attribute__ ((packed)) write_current_iac_lap_cp;
+#define WRITE_CURRENT_IAC_LAP_CP_SIZE 1+3*MAX_IAC_LAP
+
+#define OCF_READ_PAGE_SCAN_PERIOD_MODE 0x003B
+
+#define OCF_WRITE_PAGE_SCAN_PERIOD_MODE        0x003C
+
+#define OCF_READ_PAGE_SCAN_MODE                0x003D
+
+#define OCF_WRITE_PAGE_SCAN_MODE       0x003E
+
+#define OCF_SET_AFH_CLASSIFICATION     0x003F
+typedef struct {
+       uint8_t         map[10];
+} __attribute__ ((packed)) set_afh_classification_cp;
+#define SET_AFH_CLASSIFICATION_CP_SIZE 10
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) set_afh_classification_rp;
+#define SET_AFH_CLASSIFICATION_RP_SIZE 1
+
+#define OCF_READ_INQUIRY_SCAN_TYPE     0x0042
+typedef struct {
+       uint8_t         status;
+       uint8_t         type;
+} __attribute__ ((packed)) read_inquiry_scan_type_rp;
+#define READ_INQUIRY_SCAN_TYPE_RP_SIZE 2
+
+#define OCF_WRITE_INQUIRY_SCAN_TYPE    0x0043
+typedef struct {
+       uint8_t         type;
+} __attribute__ ((packed)) write_inquiry_scan_type_cp;
+#define WRITE_INQUIRY_SCAN_TYPE_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_inquiry_scan_type_rp;
+#define WRITE_INQUIRY_SCAN_TYPE_RP_SIZE 1
+
+#define OCF_READ_INQUIRY_MODE          0x0044
+typedef struct {
+       uint8_t         status;
+       uint8_t         mode;
+} __attribute__ ((packed)) read_inquiry_mode_rp;
+#define READ_INQUIRY_MODE_RP_SIZE 2
+
+#define OCF_WRITE_INQUIRY_MODE         0x0045
+typedef struct {
+       uint8_t         mode;
+} __attribute__ ((packed)) write_inquiry_mode_cp;
+#define WRITE_INQUIRY_MODE_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_inquiry_mode_rp;
+#define WRITE_INQUIRY_MODE_RP_SIZE 1
+
+#define OCF_READ_PAGE_SCAN_TYPE                0x0046
+
+#define OCF_WRITE_PAGE_SCAN_TYPE       0x0047
+       #define PAGE_SCAN_TYPE_STANDARD         0x00
+       #define PAGE_SCAN_TYPE_INTERLACED       0x01
+
+#define OCF_READ_AFH_MODE              0x0048
+typedef struct {
+       uint8_t         status;
+       uint8_t         mode;
+} __attribute__ ((packed)) read_afh_mode_rp;
+#define READ_AFH_MODE_RP_SIZE 2
+
+#define OCF_WRITE_AFH_MODE             0x0049
+typedef struct {
+       uint8_t         mode;
+} __attribute__ ((packed)) write_afh_mode_cp;
+#define WRITE_AFH_MODE_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_afh_mode_rp;
+#define WRITE_AFH_MODE_RP_SIZE 1
+
+#define OCF_READ_EXT_INQUIRY_RESPONSE  0x0051
+typedef struct {
+       uint8_t         status;
+       uint8_t         fec;
+       uint8_t         data[240];
+} __attribute__ ((packed)) read_ext_inquiry_response_rp;
+#define READ_EXT_INQUIRY_RESPONSE_RP_SIZE 242
+
+#define OCF_WRITE_EXT_INQUIRY_RESPONSE 0x0052
+typedef struct {
+       uint8_t         fec;
+       uint8_t         data[240];
+} __attribute__ ((packed)) write_ext_inquiry_response_cp;
+#define WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE 241
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_ext_inquiry_response_rp;
+#define WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE 1
+
+#define OCF_REFRESH_ENCRYPTION_KEY     0x0053
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) refresh_encryption_key_cp;
+#define REFRESH_ENCRYPTION_KEY_CP_SIZE 2
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) refresh_encryption_key_rp;
+#define REFRESH_ENCRYPTION_KEY_RP_SIZE 1
+
+#define OCF_READ_SIMPLE_PAIRING_MODE   0x0055
+typedef struct {
+       uint8_t         status;
+       uint8_t         mode;
+} __attribute__ ((packed)) read_simple_pairing_mode_rp;
+#define READ_SIMPLE_PAIRING_MODE_RP_SIZE 2
+
+#define OCF_WRITE_SIMPLE_PAIRING_MODE  0x0056
+typedef struct {
+       uint8_t         mode;
+} __attribute__ ((packed)) write_simple_pairing_mode_cp;
+#define WRITE_SIMPLE_PAIRING_MODE_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_simple_pairing_mode_rp;
+#define WRITE_SIMPLE_PAIRING_MODE_RP_SIZE 1
+
+#define OCF_READ_LOCAL_OOB_DATA                0x0057
+typedef struct {
+       uint8_t         status;
+       uint8_t         hash[16];
+       uint8_t         randomizer[16];
+} __attribute__ ((packed)) read_local_oob_data_rp;
+#define READ_LOCAL_OOB_DATA_RP_SIZE 33
+
+#define OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL   0x0058
+typedef struct {
+       uint8_t         status;
+       int8_t          level;
+} __attribute__ ((packed)) read_inq_response_tx_power_level_rp;
+#define READ_INQ_RESPONSE_TX_POWER_LEVEL_RP_SIZE 2
+
+#define OCF_READ_INQUIRY_TRANSMIT_POWER_LEVEL  0x0058
+typedef struct {
+       uint8_t         status;
+       int8_t          level;
+} __attribute__ ((packed)) read_inquiry_transmit_power_level_rp;
+#define READ_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE 2
+
+#define OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL 0x0059
+typedef struct {
+       int8_t          level;
+} __attribute__ ((packed)) write_inquiry_transmit_power_level_cp;
+#define WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_inquiry_transmit_power_level_rp;
+#define WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE 1
+
+#define OCF_READ_DEFAULT_ERROR_DATA_REPORTING  0x005A
+typedef struct {
+       uint8_t         status;
+       uint8_t         reporting;
+} __attribute__ ((packed)) read_default_error_data_reporting_rp;
+#define READ_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE 2
+
+#define OCF_WRITE_DEFAULT_ERROR_DATA_REPORTING 0x005B
+typedef struct {
+       uint8_t         reporting;
+} __attribute__ ((packed)) write_default_error_data_reporting_cp;
+#define WRITE_DEFAULT_ERROR_DATA_REPORTING_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_default_error_data_reporting_rp;
+#define WRITE_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE 1
+
+#define OCF_ENHANCED_FLUSH             0x005F
+typedef struct {
+       uint16_t        handle;
+       uint8_t         type;
+} __attribute__ ((packed)) enhanced_flush_cp;
+#define ENHANCED_FLUSH_CP_SIZE 3
+
+#define OCF_SEND_KEYPRESS_NOTIFY       0x0060
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         type;
+} __attribute__ ((packed)) send_keypress_notify_cp;
+#define SEND_KEYPRESS_NOTIFY_CP_SIZE 7
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) send_keypress_notify_rp;
+#define SEND_KEYPRESS_NOTIFY_RP_SIZE 1
+
+#define OCF_READ_LOGICAL_LINK_ACCEPT_TIMEOUT    0x0061
+typedef struct {
+       uint8_t         status;
+       uint16_t        timeout;
+} __attribute__ ((packed)) read_log_link_accept_timeout_rp;
+#define READ_LOGICAL_LINK_ACCEPT_TIMEOUT_RP_SIZE 3
+
+#define OCF_WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT  0x0062
+typedef struct {
+       uint16_t        timeout;
+} __attribute__ ((packed)) write_log_link_accept_timeout_cp;
+#define WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT_CP_SIZE 2
+
+#define OCF_SET_EVENT_MASK_PAGE_2      0x0063
+
+#define OCF_READ_LOCATION_DATA         0x0064
+
+#define OCF_WRITE_LOCATION_DATA        0x0065
+
+#define OCF_READ_FLOW_CONTROL_MODE     0x0066
+
+#define OCF_WRITE_FLOW_CONTROL_MODE    0x0067
+
+#define OCF_READ_ENHANCED_TRANSMIT_POWER_LEVEL 0x0068
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       int8_t          level_gfsk;
+       int8_t          level_dqpsk;
+       int8_t          level_8dpsk;
+} __attribute__ ((packed)) read_enhanced_transmit_power_level_rp;
+#define READ_ENHANCED_TRANSMIT_POWER_LEVEL_RP_SIZE 6
+
+#define OCF_READ_BEST_EFFORT_FLUSH_TIMEOUT     0x0069
+typedef struct {
+       uint8_t         status;
+       uint32_t        timeout;
+} __attribute__ ((packed)) read_best_effort_flush_timeout_rp;
+#define READ_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE 5
+
+#define OCF_WRITE_BEST_EFFORT_FLUSH_TIMEOUT    0x006A
+typedef struct {
+       uint16_t        handle;
+       uint32_t        timeout;
+} __attribute__ ((packed)) write_best_effort_flush_timeout_cp;
+#define WRITE_BEST_EFFORT_FLUSH_TIMEOUT_CP_SIZE 6
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_best_effort_flush_timeout_rp;
+#define WRITE_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE 1
+
+#define OCF_WRITE_LE_HOST_SUPPORTED    0x006D
+typedef struct {
+       uint8_t         le;
+       uint8_t         simul;
+} __attribute__ ((packed)) write_le_host_supported_cp;
+#define WRITE_LE_HOST_SUPPORTED_CP_SIZE 2
+
+/* Informational Parameters */
+#define OGF_INFO_PARAM         0x04
+
+#define OCF_READ_LOCAL_VERSION         0x0001
+typedef struct {
+       uint8_t         status;
+       uint8_t         hci_ver;
+       uint16_t        hci_rev;
+       uint8_t         lmp_ver;
+       uint16_t        manufacturer;
+       uint16_t        lmp_subver;
+} __attribute__ ((packed)) read_local_version_rp;
+#define READ_LOCAL_VERSION_RP_SIZE 9
+
+#define OCF_READ_LOCAL_COMMANDS                0x0002
+typedef struct {
+       uint8_t         status;
+       uint8_t         commands[64];
+} __attribute__ ((packed)) read_local_commands_rp;
+#define READ_LOCAL_COMMANDS_RP_SIZE 65
+
+#define OCF_READ_LOCAL_FEATURES                0x0003
+typedef struct {
+       uint8_t         status;
+       uint8_t         features[8];
+} __attribute__ ((packed)) read_local_features_rp;
+#define READ_LOCAL_FEATURES_RP_SIZE 9
+
+#define OCF_READ_LOCAL_EXT_FEATURES    0x0004
+typedef struct {
+       uint8_t         page_num;
+} __attribute__ ((packed)) read_local_ext_features_cp;
+#define READ_LOCAL_EXT_FEATURES_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+       uint8_t         page_num;
+       uint8_t         max_page_num;
+       uint8_t         features[8];
+} __attribute__ ((packed)) read_local_ext_features_rp;
+#define READ_LOCAL_EXT_FEATURES_RP_SIZE 11
+
+#define OCF_READ_BUFFER_SIZE           0x0005
+typedef struct {
+       uint8_t         status;
+       uint16_t        acl_mtu;
+       uint8_t         sco_mtu;
+       uint16_t        acl_max_pkt;
+       uint16_t        sco_max_pkt;
+} __attribute__ ((packed)) read_buffer_size_rp;
+#define READ_BUFFER_SIZE_RP_SIZE 8
+
+#define OCF_READ_COUNTRY_CODE          0x0007
+
+#define OCF_READ_BD_ADDR               0x0009
+typedef struct {
+       uint8_t         status;
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) read_bd_addr_rp;
+#define READ_BD_ADDR_RP_SIZE 7
+
+/* Status params */
+#define OGF_STATUS_PARAM       0x05
+
+#define OCF_READ_FAILED_CONTACT_COUNTER                0x0001
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         counter;
+} __attribute__ ((packed)) read_failed_contact_counter_rp;
+#define READ_FAILED_CONTACT_COUNTER_RP_SIZE 4
+
+#define OCF_RESET_FAILED_CONTACT_COUNTER       0x0002
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+} __attribute__ ((packed)) reset_failed_contact_counter_rp;
+#define RESET_FAILED_CONTACT_COUNTER_RP_SIZE 4
+
+#define OCF_READ_LINK_QUALITY          0x0003
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         link_quality;
+} __attribute__ ((packed)) read_link_quality_rp;
+#define READ_LINK_QUALITY_RP_SIZE 4
+
+#define OCF_READ_RSSI                  0x0005
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       int8_t          rssi;
+} __attribute__ ((packed)) read_rssi_rp;
+#define READ_RSSI_RP_SIZE 4
+
+#define OCF_READ_AFH_MAP               0x0006
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         mode;
+       uint8_t         map[10];
+} __attribute__ ((packed)) read_afh_map_rp;
+#define READ_AFH_MAP_RP_SIZE 14
+
+#define OCF_READ_CLOCK                 0x0007
+typedef struct {
+       uint16_t        handle;
+       uint8_t         which_clock;
+} __attribute__ ((packed)) read_clock_cp;
+#define READ_CLOCK_CP_SIZE 3
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint32_t        clock;
+       uint16_t        accuracy;
+} __attribute__ ((packed)) read_clock_rp;
+#define READ_CLOCK_RP_SIZE 9
+
+#define OCF_READ_LOCAL_AMP_INFO        0x0009
+typedef struct {
+       uint8_t         status;
+       uint8_t         amp_status;
+       uint32_t        total_bandwidth;
+       uint32_t        max_guaranteed_bandwidth;
+       uint32_t        min_latency;
+       uint32_t        max_pdu_size;
+       uint8_t         controller_type;
+       uint16_t        pal_caps;
+       uint16_t        max_amp_assoc_length;
+       uint32_t        max_flush_timeout;
+       uint32_t        best_effort_flush_timeout;
+} __attribute__ ((packed)) read_local_amp_info_rp;
+#define READ_LOCAL_AMP_INFO_RP_SIZE 31
+
+#define OCF_READ_LOCAL_AMP_ASSOC       0x000A
+typedef struct {
+       uint8_t         handle;
+       uint16_t        length_so_far;
+       uint16_t        assoc_length;
+} __attribute__ ((packed)) read_local_amp_assoc_cp;
+#define READ_LOCAL_AMP_ASSOC_CP_SIZE 5
+typedef struct {
+       uint8_t         status;
+       uint8_t         handle;
+       uint16_t        length;
+       uint8_t         fragment[248];
+} __attribute__ ((packed)) read_local_amp_assoc_rp;
+#define READ_LOCAL_AMP_ASSOC_RP_SIZE 252
+
+#define OCF_WRITE_REMOTE_AMP_ASSOC     0x000B
+typedef struct {
+       uint8_t         handle;
+       uint16_t        length_so_far;
+       uint16_t        assoc_length;
+       uint8_t         fragment[248];
+} __attribute__ ((packed)) write_remote_amp_assoc_cp;
+#define WRITE_REMOTE_AMP_ASSOC_CP_SIZE 253
+typedef struct {
+       uint8_t         status;
+       uint8_t         handle;
+} __attribute__ ((packed)) write_remote_amp_assoc_rp;
+#define WRITE_REMOTE_AMP_ASSOC_RP_SIZE 2
+
+/* Testing commands */
+#define OGF_TESTING_CMD                0x3e
+
+#define OCF_READ_LOOPBACK_MODE                 0x0001
+
+#define OCF_WRITE_LOOPBACK_MODE                        0x0002
+
+#define OCF_ENABLE_DEVICE_UNDER_TEST_MODE      0x0003
+
+#define OCF_WRITE_SIMPLE_PAIRING_DEBUG_MODE    0x0004
+typedef struct {
+       uint8_t         mode;
+} __attribute__ ((packed)) write_simple_pairing_debug_mode_cp;
+#define WRITE_SIMPLE_PAIRING_DEBUG_MODE_CP_SIZE 1
+typedef struct {
+       uint8_t         status;
+} __attribute__ ((packed)) write_simple_pairing_debug_mode_rp;
+#define WRITE_SIMPLE_PAIRING_DEBUG_MODE_RP_SIZE 1
+
+/* LE commands */
+#define OGF_LE_CTL             0x08
+
+#define OCF_LE_SET_EVENT_MASK                  0x0001
+typedef struct {
+       uint8_t         mask[8];
+} __attribute__ ((packed)) le_set_event_mask_cp;
+#define LE_SET_EVENT_MASK_CP_SIZE 8
+
+#define OCF_LE_READ_BUFFER_SIZE                        0x0002
+typedef struct {
+       uint8_t         status;
+       uint16_t        pkt_len;
+       uint8_t         max_pkt;
+} __attribute__ ((packed)) le_read_buffer_size_rp;
+#define LE_READ_BUFFER_SIZE_RP_SIZE 4
+
+#define OCF_LE_READ_LOCAL_SUPPORTED_FEATURES   0x0003
+typedef struct {
+       uint8_t         status;
+       uint8_t         features[8];
+} __attribute__ ((packed)) le_read_local_supported_features_rp;
+#define LE_READ_LOCAL_SUPPORTED_FEATURES_RP_SIZE 9
+
+#define OCF_LE_SET_RANDOM_ADDRESS              0x0005
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) le_set_random_address_cp;
+#define LE_SET_RANDOM_ADDRESS_CP_SIZE 6
+
+#define OCF_LE_SET_ADVERTISING_PARAMETERS      0x0006
+typedef struct {
+       uint16_t        min_interval;
+       uint16_t        max_interval;
+       uint8_t         advtype;
+       uint8_t         own_bdaddr_type;
+       uint8_t         direct_bdaddr_type;
+       bdaddr_t        direct_bdaddr;
+       uint8_t         chan_map;
+       uint8_t         filter;
+} __attribute__ ((packed)) le_set_advertising_parameters_cp;
+#define LE_SET_ADVERTISING_PARAMETERS_CP_SIZE 15
+
+#define OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER       0x0007
+typedef struct {
+       uint8_t         status;
+       uint8_t         level;
+} __attribute__ ((packed)) le_read_advertising_channel_tx_power_rp;
+#define LE_READ_ADVERTISING_CHANNEL_TX_POWER_RP_SIZE 2
+
+#define OCF_LE_SET_ADVERTISING_DATA            0x0008
+typedef struct {
+       uint8_t         length;
+       uint8_t         data[31];
+} __attribute__ ((packed)) le_set_advertising_data_cp;
+#define LE_SET_ADVERTISING_DATA_CP_SIZE 32
+
+#define OCF_LE_SET_SCAN_RESPONSE_DATA          0x0009
+typedef struct {
+       uint8_t         length;
+       uint8_t         data[31];
+} __attribute__ ((packed)) le_set_scan_response_data_cp;
+#define LE_SET_SCAN_RESPONSE_DATA_CP_SIZE 32
+
+#define OCF_LE_SET_ADVERTISE_ENABLE            0x000A
+typedef struct {
+       uint8_t         enable;
+} __attribute__ ((packed)) le_set_advertise_enable_cp;
+#define LE_SET_ADVERTISE_ENABLE_CP_SIZE 1
+
+#define OCF_LE_SET_SCAN_PARAMETERS             0x000B
+typedef struct {
+       uint8_t         type;
+       uint16_t        interval;
+       uint16_t        window;
+       uint8_t         own_bdaddr_type;
+       uint8_t         filter;
+} __attribute__ ((packed)) le_set_scan_parameters_cp;
+#define LE_SET_SCAN_PARAMETERS_CP_SIZE 7
+
+#define OCF_LE_SET_SCAN_ENABLE                 0x000C
+typedef struct {
+       uint8_t         enable;
+       uint8_t         filter_dup;
+} __attribute__ ((packed)) le_set_scan_enable_cp;
+#define LE_SET_SCAN_ENABLE_CP_SIZE 2
+
+#define OCF_LE_CREATE_CONN                     0x000D
+typedef struct {
+       uint16_t        interval;
+       uint16_t        window;
+       uint8_t         initiator_filter;
+       uint8_t         peer_bdaddr_type;
+       bdaddr_t        peer_bdaddr;
+       uint8_t         own_bdaddr_type;
+       uint16_t        min_interval;
+       uint16_t        max_interval;
+       uint16_t        latency;
+       uint16_t        supervision_timeout;
+       uint16_t        min_ce_length;
+       uint16_t        max_ce_length;
+} __attribute__ ((packed)) le_create_connection_cp;
+#define LE_CREATE_CONN_CP_SIZE 25
+
+#define OCF_LE_CREATE_CONN_CANCEL              0x000E
+
+#define OCF_LE_READ_WHITE_LIST_SIZE            0x000F
+typedef struct {
+       uint8_t         status;
+       uint8_t         size;
+} __attribute__ ((packed)) le_read_white_list_size_rp;
+#define LE_READ_WHITE_LIST_SIZE_RP_SIZE 2
+
+#define OCF_LE_CLEAR_WHITE_LIST                        0x0010
+
+#define OCF_LE_ADD_DEVICE_TO_WHITE_LIST                0x0011
+typedef struct {
+       uint8_t         bdaddr_type;
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) le_add_device_to_white_list_cp;
+#define LE_ADD_DEVICE_TO_WHITE_LIST_CP_SIZE 7
+
+#define OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST   0x0012
+typedef struct {
+       uint8_t         bdaddr_type;
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) le_remove_device_from_white_list_cp;
+#define LE_REMOVE_DEVICE_FROM_WHITE_LIST_CP_SIZE 7
+
+#define OCF_LE_CONN_UPDATE                     0x0013
+typedef struct {
+       uint16_t        handle;
+       uint16_t        min_interval;
+       uint16_t        max_interval;
+       uint16_t        latency;
+       uint16_t        supervision_timeout;
+       uint16_t        min_ce_length;
+       uint16_t        max_ce_length;
+} __attribute__ ((packed)) le_connection_update_cp;
+#define LE_CONN_UPDATE_CP_SIZE 14
+
+#define OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION 0x0014
+typedef struct {
+       uint8_t         map[5];
+} __attribute__ ((packed)) le_set_host_channel_classification_cp;
+#define LE_SET_HOST_CHANNEL_CLASSIFICATION_CP_SIZE 5
+
+#define OCF_LE_READ_CHANNEL_MAP                        0x0015
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) le_read_channel_map_cp;
+#define LE_READ_CHANNEL_MAP_CP_SIZE 2
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         map[5];
+} __attribute__ ((packed)) le_read_channel_map_rp;
+#define LE_READ_CHANNEL_MAP_RP_SIZE 8
+
+#define OCF_LE_READ_REMOTE_USED_FEATURES       0x0016
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) le_read_remote_used_features_cp;
+#define LE_READ_REMOTE_USED_FEATURES_CP_SIZE 2
+
+#define OCF_LE_ENCRYPT                         0x0017
+typedef struct {
+       uint8_t         key[16];
+       uint8_t         plaintext[16];
+} __attribute__ ((packed)) le_encrypt_cp;
+#define LE_ENCRYPT_CP_SIZE 32
+typedef struct {
+       uint8_t         status;
+       uint8_t         data[16];
+} __attribute__ ((packed)) le_encrypt_rp;
+#define LE_ENCRYPT_RP_SIZE 17
+
+#define OCF_LE_RAND                            0x0018
+typedef struct {
+       uint8_t         status;
+       uint64_t        random;
+} __attribute__ ((packed)) le_rand_rp;
+#define LE_RAND_RP_SIZE 9
+
+#define OCF_LE_START_ENCRYPTION                        0x0019
+typedef struct {
+       uint16_t        handle;
+       uint64_t        random;
+       uint16_t        diversifier;
+       uint8_t         key[16];
+} __attribute__ ((packed)) le_start_encryption_cp;
+#define LE_START_ENCRYPTION_CP_SIZE 28
+
+#define OCF_LE_LTK_REPLY                       0x001A
+typedef struct {
+       uint16_t        handle;
+       uint8_t         key[16];
+} __attribute__ ((packed)) le_ltk_reply_cp;
+#define LE_LTK_REPLY_CP_SIZE 18
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+} __attribute__ ((packed)) le_ltk_reply_rp;
+#define LE_LTK_REPLY_RP_SIZE 3
+
+#define OCF_LE_LTK_NEG_REPLY                   0x001B
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) le_ltk_neg_reply_cp;
+#define LE_LTK_NEG_REPLY_CP_SIZE 2
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+} __attribute__ ((packed)) le_ltk_neg_reply_rp;
+#define LE_LTK_NEG_REPLY_RP_SIZE 3
+
+#define OCF_LE_READ_SUPPORTED_STATES           0x001C
+typedef struct {
+       uint8_t         status;
+       uint64_t        states;
+} __attribute__ ((packed)) le_read_supported_states_rp;
+#define LE_READ_SUPPORTED_STATES_RP_SIZE 9
+
+#define OCF_LE_RECEIVER_TEST                   0x001D
+typedef struct {
+       uint8_t         frequency;
+} __attribute__ ((packed)) le_receiver_test_cp;
+#define LE_RECEIVER_TEST_CP_SIZE 1
+
+#define OCF_LE_TRANSMITTER_TEST                        0x001E
+typedef struct {
+       uint8_t         frequency;
+       uint8_t         length;
+       uint8_t         payload;
+} __attribute__ ((packed)) le_transmitter_test_cp;
+#define LE_TRANSMITTER_TEST_CP_SIZE 3
+
+#define OCF_LE_TEST_END                                0x001F
+typedef struct {
+       uint8_t         status;
+       uint16_t        num_pkts;
+} __attribute__ ((packed)) le_test_end_rp;
+#define LE_TEST_END_RP_SIZE 3
+
+/* Vendor specific commands */
+#define OGF_VENDOR_CMD         0x3f
+
+/* ---- HCI Events ---- */
+
+#define EVT_INQUIRY_COMPLETE           0x01
+
+#define EVT_INQUIRY_RESULT             0x02
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         pscan_rep_mode;
+       uint8_t         pscan_period_mode;
+       uint8_t         pscan_mode;
+       uint8_t         dev_class[3];
+       uint16_t        clock_offset;
+} __attribute__ ((packed)) inquiry_info;
+#define INQUIRY_INFO_SIZE 14
+
+#define EVT_CONN_COMPLETE              0x03
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       bdaddr_t        bdaddr;
+       uint8_t         link_type;
+       uint8_t         encr_mode;
+} __attribute__ ((packed)) evt_conn_complete;
+#define EVT_CONN_COMPLETE_SIZE 13
+
+#define EVT_CONN_REQUEST               0x04
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         dev_class[3];
+       uint8_t         link_type;
+} __attribute__ ((packed)) evt_conn_request;
+#define EVT_CONN_REQUEST_SIZE 10
+
+#define EVT_DISCONN_COMPLETE           0x05
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         reason;
+} __attribute__ ((packed)) evt_disconn_complete;
+#define EVT_DISCONN_COMPLETE_SIZE 4
+
+#define EVT_AUTH_COMPLETE              0x06
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+} __attribute__ ((packed)) evt_auth_complete;
+#define EVT_AUTH_COMPLETE_SIZE 3
+
+#define EVT_REMOTE_NAME_REQ_COMPLETE   0x07
+typedef struct {
+       uint8_t         status;
+       bdaddr_t        bdaddr;
+       uint8_t         name[248];
+} __attribute__ ((packed)) evt_remote_name_req_complete;
+#define EVT_REMOTE_NAME_REQ_COMPLETE_SIZE 255
+
+#define EVT_ENCRYPT_CHANGE             0x08
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         encrypt;
+} __attribute__ ((packed)) evt_encrypt_change;
+#define EVT_ENCRYPT_CHANGE_SIZE 5
+
+#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE      0x09
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+}  __attribute__ ((packed)) evt_change_conn_link_key_complete;
+#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE 3
+
+#define EVT_MASTER_LINK_KEY_COMPLETE           0x0A
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         key_flag;
+} __attribute__ ((packed)) evt_master_link_key_complete;
+#define EVT_MASTER_LINK_KEY_COMPLETE_SIZE 4
+
+#define EVT_READ_REMOTE_FEATURES_COMPLETE      0x0B
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         features[8];
+} __attribute__ ((packed)) evt_read_remote_features_complete;
+#define EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE 11
+
+#define EVT_READ_REMOTE_VERSION_COMPLETE       0x0C
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         lmp_ver;
+       uint16_t        manufacturer;
+       uint16_t        lmp_subver;
+} __attribute__ ((packed)) evt_read_remote_version_complete;
+#define EVT_READ_REMOTE_VERSION_COMPLETE_SIZE 8
+
+#define EVT_QOS_SETUP_COMPLETE         0x0D
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         flags;                  /* Reserved */
+       hci_qos         qos;
+} __attribute__ ((packed)) evt_qos_setup_complete;
+#define EVT_QOS_SETUP_COMPLETE_SIZE (4 + HCI_QOS_CP_SIZE)
+
+#define EVT_CMD_COMPLETE               0x0E
+typedef struct {
+       uint8_t         ncmd;
+       uint16_t        opcode;
+} __attribute__ ((packed)) evt_cmd_complete;
+#define EVT_CMD_COMPLETE_SIZE 3
+
+#define EVT_CMD_STATUS                         0x0F
+typedef struct {
+       uint8_t         status;
+       uint8_t         ncmd;
+       uint16_t        opcode;
+} __attribute__ ((packed)) evt_cmd_status;
+#define EVT_CMD_STATUS_SIZE 4
+
+#define EVT_HARDWARE_ERROR             0x10
+typedef struct {
+       uint8_t         code;
+} __attribute__ ((packed)) evt_hardware_error;
+#define EVT_HARDWARE_ERROR_SIZE 1
+
+#define EVT_FLUSH_OCCURRED             0x11
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) evt_flush_occured;
+#define EVT_FLUSH_OCCURRED_SIZE 2
+
+#define EVT_ROLE_CHANGE                        0x12
+typedef struct {
+       uint8_t         status;
+       bdaddr_t        bdaddr;
+       uint8_t         role;
+} __attribute__ ((packed)) evt_role_change;
+#define EVT_ROLE_CHANGE_SIZE 8
+
+#define EVT_NUM_COMP_PKTS              0x13
+typedef struct {
+       uint8_t         num_hndl;
+       /* variable length part */
+} __attribute__ ((packed)) evt_num_comp_pkts;
+#define EVT_NUM_COMP_PKTS_SIZE 1
+
+#define EVT_MODE_CHANGE                        0x14
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         mode;
+       uint16_t        interval;
+} __attribute__ ((packed)) evt_mode_change;
+#define EVT_MODE_CHANGE_SIZE 6
+
+#define EVT_RETURN_LINK_KEYS           0x15
+typedef struct {
+       uint8_t         num_keys;
+       /* variable length part */
+} __attribute__ ((packed)) evt_return_link_keys;
+#define EVT_RETURN_LINK_KEYS_SIZE 1
+
+#define EVT_PIN_CODE_REQ               0x16
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) evt_pin_code_req;
+#define EVT_PIN_CODE_REQ_SIZE 6
+
+#define EVT_LINK_KEY_REQ               0x17
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) evt_link_key_req;
+#define EVT_LINK_KEY_REQ_SIZE 6
+
+#define EVT_LINK_KEY_NOTIFY            0x18
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         link_key[16];
+       uint8_t         key_type;
+} __attribute__ ((packed)) evt_link_key_notify;
+#define EVT_LINK_KEY_NOTIFY_SIZE 23
+
+#define EVT_LOOPBACK_COMMAND           0x19
+
+#define EVT_DATA_BUFFER_OVERFLOW       0x1A
+typedef struct {
+       uint8_t         link_type;
+} __attribute__ ((packed)) evt_data_buffer_overflow;
+#define EVT_DATA_BUFFER_OVERFLOW_SIZE 1
+
+#define EVT_MAX_SLOTS_CHANGE           0x1B
+typedef struct {
+       uint16_t        handle;
+       uint8_t         max_slots;
+} __attribute__ ((packed)) evt_max_slots_change;
+#define EVT_MAX_SLOTS_CHANGE_SIZE 3
+
+#define EVT_READ_CLOCK_OFFSET_COMPLETE 0x1C
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint16_t        clock_offset;
+} __attribute__ ((packed)) evt_read_clock_offset_complete;
+#define EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE 5
+
+#define EVT_CONN_PTYPE_CHANGED         0x1D
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint16_t        ptype;
+} __attribute__ ((packed)) evt_conn_ptype_changed;
+#define EVT_CONN_PTYPE_CHANGED_SIZE 5
+
+#define EVT_QOS_VIOLATION              0x1E
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) evt_qos_violation;
+#define EVT_QOS_VIOLATION_SIZE 2
+
+#define EVT_PSCAN_REP_MODE_CHANGE      0x20
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         pscan_rep_mode;
+} __attribute__ ((packed)) evt_pscan_rep_mode_change;
+#define EVT_PSCAN_REP_MODE_CHANGE_SIZE 7
+
+#define EVT_FLOW_SPEC_COMPLETE         0x21
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         flags;
+       uint8_t         direction;
+       hci_qos         qos;
+} __attribute__ ((packed)) evt_flow_spec_complete;
+#define EVT_FLOW_SPEC_COMPLETE_SIZE (5 + HCI_QOS_CP_SIZE)
+
+#define EVT_INQUIRY_RESULT_WITH_RSSI   0x22
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         pscan_rep_mode;
+       uint8_t         pscan_period_mode;
+       uint8_t         dev_class[3];
+       uint16_t        clock_offset;
+       int8_t          rssi;
+} __attribute__ ((packed)) inquiry_info_with_rssi;
+#define INQUIRY_INFO_WITH_RSSI_SIZE 14
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         pscan_rep_mode;
+       uint8_t         pscan_period_mode;
+       uint8_t         pscan_mode;
+       uint8_t         dev_class[3];
+       uint16_t        clock_offset;
+       int8_t          rssi;
+} __attribute__ ((packed)) inquiry_info_with_rssi_and_pscan_mode;
+#define INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE 15
+
+#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE  0x23
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         page_num;
+       uint8_t         max_page_num;
+       uint8_t         features[8];
+} __attribute__ ((packed)) evt_read_remote_ext_features_complete;
+#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE 13
+
+#define EVT_SYNC_CONN_COMPLETE         0x2C
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       bdaddr_t        bdaddr;
+       uint8_t         link_type;
+       uint8_t         trans_interval;
+       uint8_t         retrans_window;
+       uint16_t        rx_pkt_len;
+       uint16_t        tx_pkt_len;
+       uint8_t         air_mode;
+} __attribute__ ((packed)) evt_sync_conn_complete;
+#define EVT_SYNC_CONN_COMPLETE_SIZE 17
+
+#define EVT_SYNC_CONN_CHANGED          0x2D
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         trans_interval;
+       uint8_t         retrans_window;
+       uint16_t        rx_pkt_len;
+       uint16_t        tx_pkt_len;
+} __attribute__ ((packed)) evt_sync_conn_changed;
+#define EVT_SYNC_CONN_CHANGED_SIZE 9
+
+#define EVT_SNIFF_SUBRATING            0x2E
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint16_t        max_tx_latency;
+       uint16_t        max_rx_latency;
+       uint16_t        min_remote_timeout;
+       uint16_t        min_local_timeout;
+} __attribute__ ((packed)) evt_sniff_subrating;
+#define EVT_SNIFF_SUBRATING_SIZE 11
+
+#define EVT_EXTENDED_INQUIRY_RESULT    0x2F
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         pscan_rep_mode;
+       uint8_t         pscan_period_mode;
+       uint8_t         dev_class[3];
+       uint16_t        clock_offset;
+       int8_t          rssi;
+       uint8_t         data[240];
+} __attribute__ ((packed)) extended_inquiry_info;
+#define EXTENDED_INQUIRY_INFO_SIZE 254
+
+#define EVT_ENCRYPTION_KEY_REFRESH_COMPLETE    0x30
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+} __attribute__ ((packed)) evt_encryption_key_refresh_complete;
+#define EVT_ENCRYPTION_KEY_REFRESH_COMPLETE_SIZE 3
+
+#define EVT_IO_CAPABILITY_REQUEST      0x31
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) evt_io_capability_request;
+#define EVT_IO_CAPABILITY_REQUEST_SIZE 6
+
+#define EVT_IO_CAPABILITY_RESPONSE     0x32
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         capability;
+       uint8_t         oob_data;
+       uint8_t         authentication;
+} __attribute__ ((packed)) evt_io_capability_response;
+#define EVT_IO_CAPABILITY_RESPONSE_SIZE 9
+
+#define EVT_USER_CONFIRM_REQUEST       0x33
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint32_t        passkey;
+} __attribute__ ((packed)) evt_user_confirm_request;
+#define EVT_USER_CONFIRM_REQUEST_SIZE 10
+
+#define EVT_USER_PASSKEY_REQUEST       0x34
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) evt_user_passkey_request;
+#define EVT_USER_PASSKEY_REQUEST_SIZE 6
+
+#define EVT_REMOTE_OOB_DATA_REQUEST    0x35
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) evt_remote_oob_data_request;
+#define EVT_REMOTE_OOB_DATA_REQUEST_SIZE 6
+
+#define EVT_SIMPLE_PAIRING_COMPLETE    0x36
+typedef struct {
+       uint8_t         status;
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) evt_simple_pairing_complete;
+#define EVT_SIMPLE_PAIRING_COMPLETE_SIZE 7
+
+#define EVT_LINK_SUPERVISION_TIMEOUT_CHANGED   0x38
+typedef struct {
+       uint16_t        handle;
+       uint16_t        timeout;
+} __attribute__ ((packed)) evt_link_supervision_timeout_changed;
+#define EVT_LINK_SUPERVISION_TIMEOUT_CHANGED_SIZE 4
+
+#define EVT_ENHANCED_FLUSH_COMPLETE    0x39
+typedef struct {
+       uint16_t        handle;
+} __attribute__ ((packed)) evt_enhanced_flush_complete;
+#define EVT_ENHANCED_FLUSH_COMPLETE_SIZE 2
+
+#define EVT_USER_PASSKEY_NOTIFY                0x3B
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint32_t        passkey;
+} __attribute__ ((packed)) evt_user_passkey_notify;
+#define EVT_USER_PASSKEY_NOTIFY_SIZE 10
+
+#define EVT_KEYPRESS_NOTIFY            0x3C
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         type;
+} __attribute__ ((packed)) evt_keypress_notify;
+#define EVT_KEYPRESS_NOTIFY_SIZE 7
+
+#define EVT_REMOTE_HOST_FEATURES_NOTIFY        0x3D
+typedef struct {
+       bdaddr_t        bdaddr;
+       uint8_t         features[8];
+} __attribute__ ((packed)) evt_remote_host_features_notify;
+#define EVT_REMOTE_HOST_FEATURES_NOTIFY_SIZE 14
+
+#define EVT_LE_META_EVENT      0x3E
+typedef struct {
+       uint8_t         subevent;
+       uint8_t         data[0];
+} __attribute__ ((packed)) evt_le_meta_event;
+#define EVT_LE_META_EVENT_SIZE 1
+
+#define EVT_LE_CONN_COMPLETE   0x01
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         role;
+       uint8_t         peer_bdaddr_type;
+       bdaddr_t        peer_bdaddr;
+       uint16_t        interval;
+       uint16_t        latency;
+       uint16_t        supervision_timeout;
+       uint8_t         master_clock_accuracy;
+} __attribute__ ((packed)) evt_le_connection_complete;
+#define EVT_LE_CONN_COMPLETE_SIZE 18
+
+#define EVT_LE_ADVERTISING_REPORT      0x02
+typedef struct {
+       uint8_t         evt_type;
+       uint8_t         bdaddr_type;
+       bdaddr_t        bdaddr;
+       uint8_t         length;
+       uint8_t         data[0];
+} __attribute__ ((packed)) le_advertising_info;
+#define LE_ADVERTISING_INFO_SIZE 9
+
+#define EVT_LE_CONN_UPDATE_COMPLETE    0x03
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint16_t        interval;
+       uint16_t        latency;
+       uint16_t        supervision_timeout;
+} __attribute__ ((packed)) evt_le_connection_update_complete;
+#define EVT_LE_CONN_UPDATE_COMPLETE_SIZE 9
+
+#define EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE      0x04
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+       uint8_t         features[8];
+} __attribute__ ((packed)) evt_le_read_remote_used_features_complete;
+#define EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE_SIZE 11
+
+#define EVT_LE_LTK_REQUEST     0x05
+typedef struct {
+       uint16_t        handle;
+       uint64_t        random;
+       uint16_t        diversifier;
+} __attribute__ ((packed)) evt_le_long_term_key_request;
+#define EVT_LE_LTK_REQUEST_SIZE 12
+
+#define EVT_PHYSICAL_LINK_COMPLETE             0x40
+typedef struct {
+       uint8_t         status;
+       uint8_t         handle;
+} __attribute__ ((packed)) evt_physical_link_complete;
+#define EVT_PHYSICAL_LINK_COMPLETE_SIZE 2
+
+#define EVT_CHANNEL_SELECTED           0x41
+
+#define EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE  0x42
+typedef struct {
+       uint8_t         status;
+       uint8_t         handle;
+       uint8_t         reason;
+} __attribute__ ((packed)) evt_disconn_physical_link_complete;
+#define EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE_SIZE 3
+
+#define EVT_PHYSICAL_LINK_LOSS_EARLY_WARNING   0x43
+typedef struct {
+       uint8_t         handle;
+       uint8_t         reason;
+} __attribute__ ((packed)) evt_physical_link_loss_warning;
+#define EVT_PHYSICAL_LINK_LOSS_WARNING_SIZE 2
+
+#define EVT_PHYSICAL_LINK_RECOVERY             0x44
+typedef struct {
+       uint8_t         handle;
+} __attribute__ ((packed)) evt_physical_link_recovery;
+#define EVT_PHYSICAL_LINK_RECOVERY_SIZE 1
+
+#define EVT_LOGICAL_LINK_COMPLETE              0x45
+typedef struct {
+       uint8_t         status;
+       uint16_t        log_handle;
+       uint8_t         handle;
+       uint8_t         tx_flow_id;
+} __attribute__ ((packed)) evt_logical_link_complete;
+#define EVT_LOGICAL_LINK_COMPLETE_SIZE 5
+
+#define EVT_DISCONNECT_LOGICAL_LINK_COMPLETE   0x46
+
+#define EVT_FLOW_SPEC_MODIFY_COMPLETE          0x47
+typedef struct {
+       uint8_t         status;
+       uint16_t        handle;
+} __attribute__ ((packed)) evt_flow_spec_modify_complete;
+#define EVT_FLOW_SPEC_MODIFY_COMPLETE_SIZE 3
+
+#define EVT_NUMBER_COMPLETED_BLOCKS            0x48
+
+#define EVT_AMP_STATUS_CHANGE                  0x4D
+typedef struct {
+       uint8_t         status;
+       uint8_t         amp_status;
+} __attribute__ ((packed)) evt_amp_status_change;
+#define EVT_AMP_STATUS_CHANGE_SIZE 2
+
+#define EVT_TESTING                    0xFE
+
+#define EVT_VENDOR                     0xFF
+
+/* Internal events generated by BlueZ stack */
+#define EVT_STACK_INTERNAL             0xFD
+typedef struct {
+       uint16_t        type;
+       uint8_t         data[0];
+} __attribute__ ((packed)) evt_stack_internal;
+#define EVT_STACK_INTERNAL_SIZE 2
+
+#define EVT_SI_DEVICE  0x01
+typedef struct {
+       uint16_t        event;
+       uint16_t        dev_id;
+} __attribute__ ((packed)) evt_si_device;
+#define EVT_SI_DEVICE_SIZE 4
+
+/* --------  HCI Packet structures  -------- */
+#define HCI_TYPE_LEN   1
+
+typedef struct {
+       uint16_t        opcode;         /* OCF & OGF */
+       uint8_t         plen;
+} __attribute__ ((packed))     hci_command_hdr;
+#define HCI_COMMAND_HDR_SIZE   3
+
+typedef struct {
+       uint8_t         evt;
+       uint8_t         plen;
+} __attribute__ ((packed))     hci_event_hdr;
+#define HCI_EVENT_HDR_SIZE     2
+
+typedef struct {
+       uint16_t        handle;         /* Handle & Flags(PB, BC) */
+       uint16_t        dlen;
+} __attribute__ ((packed))     hci_acl_hdr;
+#define HCI_ACL_HDR_SIZE       4
+
+typedef struct {
+       uint16_t        handle;
+       uint8_t         dlen;
+} __attribute__ ((packed))     hci_sco_hdr;
+#define HCI_SCO_HDR_SIZE       3
+
+typedef struct {
+       uint16_t        device;
+       uint16_t        type;
+       uint16_t        plen;
+} __attribute__ ((packed))     hci_msg_hdr;
+#define HCI_MSG_HDR_SIZE       6
+
+/* Command opcode pack/unpack */
+#define cmd_opcode_pack(ogf, ocf)      (uint16_t)((ocf & 0x03ff)|(ogf << 10))
+#define cmd_opcode_ogf(op)             (op >> 10)
+#define cmd_opcode_ocf(op)             (op & 0x03ff)
+
+/* ACL handle and flags pack/unpack */
+#define acl_handle_pack(h, f)  (uint16_t)((h & 0x0fff)|(f << 12))
+#define acl_handle(h)          (h & 0x0fff)
+#define acl_flags(h)           (h >> 12)
+
+#endif /* _NO_HCI_DEFS */
+
+/* HCI Socket options */
+#define HCI_DATA_DIR   1
+#define HCI_FILTER     2
+#define HCI_TIME_STAMP 3
+
+/* HCI CMSG flags */
+#define HCI_CMSG_DIR   0x0001
+#define HCI_CMSG_TSTAMP        0x0002
+
+struct sockaddr_hci {
+       sa_family_t     hci_family;
+       unsigned short  hci_dev;
+       unsigned short  hci_channel;
+};
+#define HCI_DEV_NONE   0xffff
+
+#define HCI_CHANNEL_RAW                0
+#define HCI_CHANNEL_CONTROL    1
+
+struct hci_filter {
+       uint32_t type_mask;
+       uint32_t event_mask[2];
+       uint16_t opcode;
+};
+
+#define HCI_FLT_TYPE_BITS      31
+#define HCI_FLT_EVENT_BITS     63
+#define HCI_FLT_OGF_BITS       63
+#define HCI_FLT_OCF_BITS       127
+
+/* Ioctl requests structures */
+struct hci_dev_stats {
+       uint32_t err_rx;
+       uint32_t err_tx;
+       uint32_t cmd_tx;
+       uint32_t evt_rx;
+       uint32_t acl_tx;
+       uint32_t acl_rx;
+       uint32_t sco_tx;
+       uint32_t sco_rx;
+       uint32_t byte_rx;
+       uint32_t byte_tx;
+};
+
+struct hci_dev_info {
+       uint16_t dev_id;
+       char     name[8];
+
+       bdaddr_t bdaddr;
+
+       uint32_t flags;
+       uint8_t  type;
+
+       uint8_t  features[8];
+
+       uint32_t pkt_type;
+       uint32_t link_policy;
+       uint32_t link_mode;
+
+       uint16_t acl_mtu;
+       uint16_t acl_pkts;
+       uint16_t sco_mtu;
+       uint16_t sco_pkts;
+
+       struct   hci_dev_stats stat;
+};
+
+struct hci_conn_info {
+       uint16_t handle;
+       bdaddr_t bdaddr;
+       uint8_t  type;
+       uint8_t  out;
+       uint16_t state;
+       uint32_t link_mode;
+};
+
+struct hci_dev_req {
+       uint16_t dev_id;
+       uint32_t dev_opt;
+};
+
+struct hci_dev_list_req {
+       uint16_t dev_num;
+       struct hci_dev_req dev_req[0];  /* hci_dev_req structures */
+};
+
+struct hci_conn_list_req {
+       uint16_t dev_id;
+       uint16_t conn_num;
+       struct hci_conn_info conn_info[0];
+};
+
+struct hci_conn_info_req {
+       bdaddr_t bdaddr;
+       uint8_t  type;
+       struct hci_conn_info conn_info[0];
+};
+
+struct hci_auth_info_req {
+       bdaddr_t bdaddr;
+       uint8_t  type;
+};
+
+struct hci_inquiry_req {
+       uint16_t dev_id;
+       uint16_t flags;
+       uint8_t  lap[3];
+       uint8_t  length;
+       uint8_t  num_rsp;
+};
+#define IREQ_CACHE_FLUSH 0x0001
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __HCI_H */
diff --git a/lib/hci_lib.h b/lib/hci_lib.h
new file mode 100644 (file)
index 0000000..725eb05
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __HCI_LIB_H
+#define __HCI_LIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct hci_request {
+       uint16_t ogf;
+       uint16_t ocf;
+       int      event;
+       void     *cparam;
+       int      clen;
+       void     *rparam;
+       int      rlen;
+};
+
+struct hci_version {
+       uint16_t manufacturer;
+       uint8_t  hci_ver;
+       uint16_t hci_rev;
+       uint8_t  lmp_ver;
+       uint16_t lmp_subver;
+};
+
+int hci_open_dev(int dev_id);
+int hci_close_dev(int dd);
+int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param);
+int hci_send_req(int dd, struct hci_request *req, int timeout);
+
+int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to);
+int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to);
+
+int hci_inquiry(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info **ii, long flags);
+int hci_devinfo(int dev_id, struct hci_dev_info *di);
+int hci_devba(int dev_id, bdaddr_t *bdaddr);
+int hci_devid(const char *str);
+
+int hci_read_local_name(int dd, int len, char *name, int to);
+int hci_write_local_name(int dd, const char *name, int to);
+int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to);
+int hci_read_remote_name_with_clock_offset(int dd, const bdaddr_t *bdaddr, uint8_t pscan_rep_mode, uint16_t clkoffset, int len, char *name, int to);
+int hci_read_remote_name_cancel(int dd, const bdaddr_t *bdaddr, int to);
+int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver, int to);
+int hci_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to);
+int hci_read_remote_ext_features(int dd, uint16_t handle, uint8_t page, uint8_t *max_page, uint8_t *features, int to);
+int hci_read_clock_offset(int dd, uint16_t handle, uint16_t *clkoffset, int to);
+int hci_read_local_version(int dd, struct hci_version *ver, int to);
+int hci_read_local_commands(int dd, uint8_t *commands, int to);
+int hci_read_local_features(int dd, uint8_t *features, int to);
+int hci_read_local_ext_features(int dd, uint8_t page, uint8_t *max_page, uint8_t *features, int to);
+int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to);
+int hci_read_class_of_dev(int dd, uint8_t *cls, int to);
+int hci_write_class_of_dev(int dd, uint32_t cls, int to);
+int hci_read_voice_setting(int dd, uint16_t *vs, int to);
+int hci_write_voice_setting(int dd, uint16_t vs, int to);
+int hci_read_current_iac_lap(int dd, uint8_t *num_iac, uint8_t *lap, int to);
+int hci_write_current_iac_lap(int dd, uint8_t num_iac, uint8_t *lap, int to);
+int hci_read_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to);
+int hci_write_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t *key, int to);
+int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to);
+int hci_authenticate_link(int dd, uint16_t handle, int to);
+int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to);
+int hci_change_link_key(int dd, uint16_t handle, int to);
+int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to);
+int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, uint16_t min_interval, int to);
+int hci_exit_park_mode(int dd, uint16_t handle, int to);
+int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to);
+int hci_write_inquiry_scan_type(int dd, uint8_t type, int to);
+int hci_read_inquiry_mode(int dd, uint8_t *mode, int to);
+int hci_write_inquiry_mode(int dd, uint8_t mode, int to);
+int hci_read_afh_mode(int dd, uint8_t *mode, int to);
+int hci_write_afh_mode(int dd, uint8_t mode, int to);
+int hci_read_ext_inquiry_response(int dd, uint8_t *fec, uint8_t *data, int to);
+int hci_write_ext_inquiry_response(int dd, uint8_t fec, uint8_t *data, int to);
+int hci_read_simple_pairing_mode(int dd, uint8_t *mode, int to);
+int hci_write_simple_pairing_mode(int dd, uint8_t mode, int to);
+int hci_read_local_oob_data(int dd, uint8_t *hash, uint8_t *randomizer, int to);
+int hci_read_inq_response_tx_power_level(int dd, int8_t *level, int to);
+int hci_read_inquiry_transmit_power_level(int dd, int8_t *level, int to);
+int hci_write_inquiry_transmit_power_level(int dd, int8_t level, int to);
+int hci_read_transmit_power_level(int dd, uint16_t handle, uint8_t type, int8_t *level, int to);
+int hci_read_link_policy(int dd, uint16_t handle, uint16_t *policy, int to);
+int hci_write_link_policy(int dd, uint16_t handle, uint16_t policy, int to);
+int hci_read_link_supervision_timeout(int dd, uint16_t handle, uint16_t *timeout, int to);
+int hci_write_link_supervision_timeout(int dd, uint16_t handle, uint16_t timeout, int to);
+int hci_set_afh_classification(int dd, uint8_t *map, int to);
+int hci_read_link_quality(int dd, uint16_t handle, uint8_t *link_quality, int to);
+int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to);
+int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map, int to);
+int hci_read_clock(int dd, uint16_t handle, uint8_t which, uint32_t *clock, uint16_t *accuracy, int to);
+
+int hci_le_set_scan_enable(int dev_id, uint8_t enable, uint8_t filter_dup, int to);
+int hci_le_set_scan_parameters(int dev_id, uint8_t type, uint16_t interval,
+                                       uint16_t window, uint8_t own_type,
+                                       uint8_t filter, int to);
+int hci_le_set_advertise_enable(int dev_id, uint8_t enable, int to);
+int hci_le_create_conn(int dd, uint16_t interval, uint16_t window,
+               uint8_t initiator_filter, uint8_t peer_bdaddr_type,
+               bdaddr_t peer_bdaddr, uint8_t own_bdaddr_type,
+               uint16_t min_interval,  uint16_t max_interval,
+               uint16_t latency, uint16_t supervision_timeout,
+               uint16_t min_ce_length, uint16_t max_ce_length,
+               uint16_t *handle, int to);
+
+int hci_le_conn_update(int dd, uint16_t handle, uint16_t min_interval,
+                       uint16_t max_interval, uint16_t latency,
+                       uint16_t supervision_timeout, int to);
+int hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to);
+int hci_le_rm_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to);
+int hci_le_read_white_list_size(int dd, uint8_t *size, int to);
+int hci_le_clear_white_list(int dd, int to);
+int hci_for_each_dev(int flag, int(*func)(int dd, int dev_id, long arg), long arg);
+int hci_get_route(bdaddr_t *bdaddr);
+
+char *hci_bustostr(int bus);
+char *hci_typetostr(int type);
+char *hci_dtypetostr(int type);
+char *hci_dflagstostr(uint32_t flags);
+char *hci_ptypetostr(unsigned int ptype);
+int hci_strtoptype(char *str, unsigned int *val);
+char *hci_scoptypetostr(unsigned int ptype);
+int hci_strtoscoptype(char *str, unsigned int *val);
+char *hci_lptostr(unsigned int ptype);
+int hci_strtolp(char *str, unsigned int *val);
+char *hci_lmtostr(unsigned int ptype);
+int hci_strtolm(char *str, unsigned int *val);
+
+char *hci_cmdtostr(unsigned int cmd);
+char *hci_commandstostr(uint8_t *commands, char *pref, int width);
+
+char *hci_vertostr(unsigned int ver);
+int hci_strtover(char *str, unsigned int *ver);
+char *lmp_vertostr(unsigned int ver);
+int lmp_strtover(char *str, unsigned int *ver);
+
+char *lmp_featurestostr(uint8_t *features, char *pref, int width);
+
+static inline void hci_set_bit(int nr, void *addr)
+{
+       *((uint32_t *) addr + (nr >> 5)) |= (1 << (nr & 31));
+}
+
+static inline void hci_clear_bit(int nr, void *addr)
+{
+       *((uint32_t *) addr + (nr >> 5)) &= ~(1 << (nr & 31));
+}
+
+static inline int hci_test_bit(int nr, void *addr)
+{
+       return *((uint32_t *) addr + (nr >> 5)) & (1 << (nr & 31));
+}
+
+/* HCI filter tools */
+static inline void hci_filter_clear(struct hci_filter *f)
+{
+       memset(f, 0, sizeof(*f));
+}
+static inline void hci_filter_set_ptype(int t, struct hci_filter *f)
+{
+       hci_set_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask);
+}
+static inline void hci_filter_clear_ptype(int t, struct hci_filter *f)
+{
+       hci_clear_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask);
+}
+static inline int hci_filter_test_ptype(int t, struct hci_filter *f)
+{
+       return hci_test_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask);
+}
+static inline void hci_filter_all_ptypes(struct hci_filter *f)
+{
+       memset((void *) &f->type_mask, 0xff, sizeof(f->type_mask));
+}
+static inline void hci_filter_set_event(int e, struct hci_filter *f)
+{
+       hci_set_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask);
+}
+static inline void hci_filter_clear_event(int e, struct hci_filter *f)
+{
+       hci_clear_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask);
+}
+static inline int hci_filter_test_event(int e, struct hci_filter *f)
+{
+       return hci_test_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask);
+}
+static inline void hci_filter_all_events(struct hci_filter *f)
+{
+       memset((void *) f->event_mask, 0xff, sizeof(f->event_mask));
+}
+static inline void hci_filter_set_opcode(int opcode, struct hci_filter *f)
+{
+       f->opcode = opcode;
+}
+static inline void hci_filter_clear_opcode(struct hci_filter *f)
+{
+       f->opcode = 0;
+}
+static inline int hci_filter_test_opcode(int opcode, struct hci_filter *f)
+{
+       return (f->opcode == opcode);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __HCI_LIB_H */
diff --git a/lib/hidp.h b/lib/hidp.h
new file mode 100644 (file)
index 0000000..c5e6a78
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __HIDP_H
+#define __HIDP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* HIDP defaults */
+#define HIDP_MINIMUM_MTU 48
+#define HIDP_DEFAULT_MTU 48
+
+/* HIDP ioctl defines */
+#define HIDPCONNADD    _IOW('H', 200, int)
+#define HIDPCONNDEL    _IOW('H', 201, int)
+#define HIDPGETCONNLIST        _IOR('H', 210, int)
+#define HIDPGETCONNINFO        _IOR('H', 211, int)
+
+#define HIDP_VIRTUAL_CABLE_UNPLUG      0
+#define HIDP_BOOT_PROTOCOL_MODE                1
+#define HIDP_BLUETOOTH_VENDOR_ID       9
+
+struct hidp_connadd_req {
+       int ctrl_sock;          /* Connected control socket */
+       int intr_sock;          /* Connected interrupt socket */
+       uint16_t parser;        /* Parser version */
+       uint16_t rd_size;       /* Report descriptor size */
+       uint8_t *rd_data;       /* Report descriptor data */
+       uint8_t  country;
+       uint8_t  subclass;
+       uint16_t vendor;
+       uint16_t product;
+       uint16_t version;
+       uint32_t flags;
+       uint32_t idle_to;
+       char name[128];         /* Device name */
+};
+
+struct hidp_conndel_req {
+       bdaddr_t bdaddr;
+       uint32_t flags;
+};
+
+struct hidp_conninfo {
+       bdaddr_t bdaddr;
+       uint32_t flags;
+       uint16_t state;
+       uint16_t vendor;
+       uint16_t product;
+       uint16_t version;
+       char name[128];
+};
+
+struct hidp_connlist_req {
+       uint32_t cnum;
+       struct hidp_conninfo *ci;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __HIDP_H */
diff --git a/lib/l2cap.h b/lib/l2cap.h
new file mode 100644 (file)
index 0000000..e59cfdd
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __L2CAP_H
+#define __L2CAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/socket.h>
+
+/* L2CAP defaults */
+#define L2CAP_DEFAULT_MTU      672
+#define L2CAP_DEFAULT_FLUSH_TO 0xFFFF
+
+/* L2CAP socket address */
+struct sockaddr_l2 {
+       sa_family_t     l2_family;
+       unsigned short  l2_psm;
+       bdaddr_t        l2_bdaddr;
+       unsigned short  l2_cid;
+};
+
+/* L2CAP socket options */
+#define L2CAP_OPTIONS  0x01
+struct l2cap_options {
+       uint16_t        omtu;
+       uint16_t        imtu;
+       uint16_t        flush_to;
+       uint8_t         mode;
+       uint8_t         fcs;
+       uint8_t         max_tx;
+       uint16_t        txwin_size;
+};
+
+#define L2CAP_CONNINFO 0x02
+struct l2cap_conninfo {
+       uint16_t        hci_handle;
+       uint8_t         dev_class[3];
+};
+
+#define L2CAP_LM       0x03
+#define L2CAP_LM_MASTER                0x0001
+#define L2CAP_LM_AUTH          0x0002
+#define L2CAP_LM_ENCRYPT       0x0004
+#define L2CAP_LM_TRUSTED       0x0008
+#define L2CAP_LM_RELIABLE      0x0010
+#define L2CAP_LM_SECURE                0x0020
+
+/* L2CAP command codes */
+#define L2CAP_COMMAND_REJ      0x01
+#define L2CAP_CONN_REQ         0x02
+#define L2CAP_CONN_RSP         0x03
+#define L2CAP_CONF_REQ         0x04
+#define L2CAP_CONF_RSP         0x05
+#define L2CAP_DISCONN_REQ      0x06
+#define L2CAP_DISCONN_RSP      0x07
+#define L2CAP_ECHO_REQ         0x08
+#define L2CAP_ECHO_RSP         0x09
+#define L2CAP_INFO_REQ         0x0a
+#define L2CAP_INFO_RSP         0x0b
+
+/* L2CAP structures */
+typedef struct {
+       uint16_t        len;
+       uint16_t        cid;
+} __attribute__ ((packed)) l2cap_hdr;
+#define L2CAP_HDR_SIZE 4
+
+typedef struct {
+       uint8_t         code;
+       uint8_t         ident;
+       uint16_t        len;
+} __attribute__ ((packed)) l2cap_cmd_hdr;
+#define L2CAP_CMD_HDR_SIZE 4
+
+typedef struct {
+       uint16_t        reason;
+} __attribute__ ((packed)) l2cap_cmd_rej;
+#define L2CAP_CMD_REJ_SIZE 2
+
+typedef struct {
+       uint16_t        psm;
+       uint16_t        scid;
+} __attribute__ ((packed)) l2cap_conn_req;
+#define L2CAP_CONN_REQ_SIZE 4
+
+typedef struct {
+       uint16_t        dcid;
+       uint16_t        scid;
+       uint16_t        result;
+       uint16_t        status;
+} __attribute__ ((packed)) l2cap_conn_rsp;
+#define L2CAP_CONN_RSP_SIZE 8
+
+/* connect result */
+#define L2CAP_CR_SUCCESS       0x0000
+#define L2CAP_CR_PEND          0x0001
+#define L2CAP_CR_BAD_PSM       0x0002
+#define L2CAP_CR_SEC_BLOCK     0x0003
+#define L2CAP_CR_NO_MEM                0x0004
+
+/* connect status */
+#define L2CAP_CS_NO_INFO       0x0000
+#define L2CAP_CS_AUTHEN_PEND   0x0001
+#define L2CAP_CS_AUTHOR_PEND   0x0002
+
+typedef struct {
+       uint16_t        dcid;
+       uint16_t        flags;
+       uint8_t         data[0];
+} __attribute__ ((packed)) l2cap_conf_req;
+#define L2CAP_CONF_REQ_SIZE 4
+
+typedef struct {
+       uint16_t        scid;
+       uint16_t        flags;
+       uint16_t        result;
+       uint8_t         data[0];
+} __attribute__ ((packed)) l2cap_conf_rsp;
+#define L2CAP_CONF_RSP_SIZE 6
+
+#define L2CAP_CONF_SUCCESS     0x0000
+#define L2CAP_CONF_UNACCEPT    0x0001
+#define L2CAP_CONF_REJECT      0x0002
+#define L2CAP_CONF_UNKNOWN     0x0003
+
+typedef struct {
+       uint8_t         type;
+       uint8_t         len;
+       uint8_t         val[0];
+} __attribute__ ((packed)) l2cap_conf_opt;
+#define L2CAP_CONF_OPT_SIZE 2
+
+#define L2CAP_CONF_MTU         0x01
+#define L2CAP_CONF_FLUSH_TO    0x02
+#define L2CAP_CONF_QOS         0x03
+#define L2CAP_CONF_RFC         0x04
+#define L2CAP_CONF_FCS         0x05
+
+#define L2CAP_CONF_MAX_SIZE    22
+
+#define L2CAP_MODE_BASIC       0x00
+#define L2CAP_MODE_RETRANS     0x01
+#define L2CAP_MODE_FLOWCTL     0x02
+#define L2CAP_MODE_ERTM                0x03
+#define L2CAP_MODE_STREAMING   0x04
+
+typedef struct {
+       uint16_t        dcid;
+       uint16_t        scid;
+} __attribute__ ((packed)) l2cap_disconn_req;
+#define L2CAP_DISCONN_REQ_SIZE 4
+
+typedef struct {
+       uint16_t        dcid;
+       uint16_t        scid;
+} __attribute__ ((packed)) l2cap_disconn_rsp;
+#define L2CAP_DISCONN_RSP_SIZE 4
+
+typedef struct {
+       uint16_t        type;
+} __attribute__ ((packed)) l2cap_info_req;
+#define L2CAP_INFO_REQ_SIZE 2
+
+typedef struct {
+       uint16_t        type;
+       uint16_t        result;
+       uint8_t         data[0];
+} __attribute__ ((packed)) l2cap_info_rsp;
+#define L2CAP_INFO_RSP_SIZE 4
+
+/* info type */
+#define L2CAP_IT_CL_MTU                0x0001
+#define L2CAP_IT_FEAT_MASK     0x0002
+
+/* info result */
+#define L2CAP_IR_SUCCESS       0x0000
+#define L2CAP_IR_NOTSUPP       0x0001
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __L2CAP_H */
diff --git a/lib/mgmt.h b/lib/mgmt.h
new file mode 100644 (file)
index 0000000..bd65328
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __packed
+#define __packed __attribute__((packed))
+#endif
+
+#define MGMT_INDEX_NONE                        0xFFFF
+
+struct mgmt_hdr {
+       uint16_t opcode;
+       uint16_t index;
+       uint16_t len;
+} __packed;
+#define MGMT_HDR_SIZE  6
+
+#define MGMT_OP_READ_VERSION           0x0001
+struct mgmt_rp_read_version {
+       uint8_t version;
+       uint16_t revision;
+} __packed;
+
+#define MGMT_OP_READ_FEATURES          0x0002
+struct mgmt_rp_read_features {
+       uint8_t features[8];
+} __packed;
+
+#define MGMT_OP_READ_INDEX_LIST                0x0003
+struct mgmt_rp_read_index_list {
+       uint16_t num_controllers;
+       uint16_t index[0];
+} __packed;
+
+#define MGMT_OP_READ_INFO              0x0004
+struct mgmt_rp_read_info {
+       uint8_t type;
+       uint8_t powered;
+       uint8_t connectable;
+       uint8_t discoverable;
+       uint8_t pairable;
+       uint8_t sec_mode;
+       bdaddr_t bdaddr;
+       uint8_t dev_class[3];
+       uint8_t features[8];
+       uint16_t manufacturer;
+       uint8_t hci_ver;
+       uint16_t hci_rev;
+       uint8_t name[249];
+} __packed;
+
+struct mgmt_mode {
+       uint8_t val;
+} __packed;
+
+#define MGMT_OP_SET_POWERED            0x0005
+
+#define MGMT_OP_SET_DISCOVERABLE       0x0006
+
+#define MGMT_OP_SET_CONNECTABLE                0x0007
+
+#define MGMT_OP_SET_PAIRABLE           0x0008
+
+#define MGMT_OP_ADD_UUID               0x0009
+struct mgmt_cp_add_uuid {
+       uint8_t uuid[16];
+       uint8_t svc_hint;
+} __packed;
+
+#define MGMT_OP_REMOVE_UUID            0x000A
+struct mgmt_cp_remove_uuid {
+       uint8_t uuid[16];
+} __packed;
+
+#define MGMT_OP_SET_DEV_CLASS          0x000B
+struct mgmt_cp_set_dev_class {
+       uint8_t major;
+       uint8_t minor;
+} __packed;
+
+#define MGMT_OP_SET_SERVICE_CACHE      0x000C
+struct mgmt_cp_set_service_cache {
+       uint8_t enable;
+} __packed;
+
+struct mgmt_key_info {
+       bdaddr_t bdaddr;
+       uint8_t type;
+       uint8_t val[16];
+       uint8_t pin_len;
+} __packed;
+
+#define MGMT_OP_LOAD_KEYS              0x000D
+struct mgmt_cp_load_keys {
+       uint8_t debug_keys;
+       uint16_t key_count;
+       struct mgmt_key_info keys[0];
+} __packed;
+
+#define MGMT_OP_REMOVE_KEY             0x000E
+struct mgmt_cp_remove_key {
+       bdaddr_t bdaddr;
+       uint8_t disconnect;
+} __packed;
+
+#define MGMT_OP_DISCONNECT             0x000F
+struct mgmt_cp_disconnect {
+       bdaddr_t bdaddr;
+} __packed;
+struct mgmt_rp_disconnect {
+       bdaddr_t bdaddr;
+} __packed;
+
+#define MGMT_OP_GET_CONNECTIONS                0x0010
+struct mgmt_rp_get_connections {
+       uint16_t conn_count;
+       bdaddr_t conn[0];
+} __packed;
+
+#define MGMT_OP_PIN_CODE_REPLY         0x0011
+struct mgmt_cp_pin_code_reply {
+       bdaddr_t bdaddr;
+       uint8_t pin_len;
+       uint8_t pin_code[16];
+} __packed;
+
+#define MGMT_OP_PIN_CODE_NEG_REPLY     0x0012
+struct mgmt_cp_pin_code_neg_reply {
+       bdaddr_t bdaddr;
+} __packed;
+
+#define MGMT_OP_SET_IO_CAPABILITY      0x0013
+struct mgmt_cp_set_io_capability {
+       uint8_t io_capability;
+} __packed;
+
+#define MGMT_OP_PAIR_DEVICE            0x0014
+struct mgmt_cp_pair_device {
+       bdaddr_t bdaddr;
+       uint8_t io_cap;
+} __packed;
+struct mgmt_rp_pair_device {
+       bdaddr_t bdaddr;
+       uint8_t status;
+} __packed;
+
+#define MGMT_OP_USER_CONFIRM_REPLY     0x0015
+struct mgmt_cp_user_confirm_reply {
+       bdaddr_t bdaddr;
+} __packed;
+struct mgmt_rp_user_confirm_reply {
+       bdaddr_t bdaddr;
+       uint8_t status;
+} __packed;
+
+#define MGMT_OP_USER_CONFIRM_NEG_REPLY 0x0016
+
+#define MGMT_OP_SET_LOCAL_NAME         0x0017
+struct mgmt_cp_set_local_name {
+       uint8_t name[249];
+} __packed;
+
+#define MGMT_EV_CMD_COMPLETE           0x0001
+struct mgmt_ev_cmd_complete {
+       uint16_t opcode;
+       uint8_t data[0];
+} __packed;
+
+#define MGMT_EV_CMD_STATUS             0x0002
+struct mgmt_ev_cmd_status {
+       uint8_t status;
+       uint16_t opcode;
+} __packed;
+
+#define MGMT_EV_CONTROLLER_ERROR       0x0003
+struct mgmt_ev_controller_error {
+       uint8_t error_code;
+} __packed;
+
+#define MGMT_EV_INDEX_ADDED            0x0004
+
+#define MGMT_EV_INDEX_REMOVED          0x0005
+
+#define MGMT_EV_POWERED                        0x0006
+
+#define MGMT_EV_DISCOVERABLE           0x0007
+
+#define MGMT_EV_CONNECTABLE            0x0008
+
+#define MGMT_EV_PAIRABLE               0x0009
+
+#define MGMT_EV_NEW_KEY                        0x000A
+struct mgmt_ev_new_key {
+       struct mgmt_key_info key;
+       uint8_t old_key_type;
+} __packed;
+
+#define MGMT_EV_DEVICE_CONNECTED       0x000B
+struct mgmt_ev_device_connected {
+       bdaddr_t bdaddr;
+} __packed;
+
+#define MGMT_EV_DEVICE_DISCONNECTED    0x000C
+struct mgmt_ev_device_disconnected {
+       bdaddr_t bdaddr;
+} __packed;
+
+#define MGMT_EV_CONNECT_FAILED         0x000D
+struct mgmt_ev_connect_failed {
+       bdaddr_t bdaddr;
+       uint8_t status;
+} __packed;
+
+#define MGMT_EV_PIN_CODE_REQUEST       0x000E
+struct mgmt_ev_pin_code_request {
+       bdaddr_t bdaddr;
+} __packed;
+
+#define MGMT_EV_USER_CONFIRM_REQUEST   0x000F
+struct mgmt_ev_user_confirm_request {
+       bdaddr_t bdaddr;
+       uint32_t value;
+} __packed;
+
+#define MGMT_EV_AUTH_FAILED            0x0010
+struct mgmt_ev_auth_failed {
+       bdaddr_t bdaddr;
+       uint8_t status;
+} __packed;
+
+#define MGMT_EV_LOCAL_NAME_CHANGED     0x0011
+struct mgmt_ev_local_name_changed {
+       uint8_t name[249];
+} __packed;
diff --git a/lib/rfcomm.h b/lib/rfcomm.h
new file mode 100644 (file)
index 0000000..ad6c0e1
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __RFCOMM_H
+#define __RFCOMM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/socket.h>
+
+/* RFCOMM defaults */
+#define RFCOMM_DEFAULT_MTU     127
+
+#define RFCOMM_PSM 3
+
+/* RFCOMM socket address */
+struct sockaddr_rc {
+       sa_family_t     rc_family;
+       bdaddr_t        rc_bdaddr;
+       uint8_t         rc_channel;
+};
+
+/* RFCOMM socket options */
+#define RFCOMM_CONNINFO        0x02
+struct rfcomm_conninfo {
+       uint16_t        hci_handle;
+       uint8_t         dev_class[3];
+};
+
+#define RFCOMM_LM      0x03
+#define RFCOMM_LM_MASTER       0x0001
+#define RFCOMM_LM_AUTH         0x0002
+#define RFCOMM_LM_ENCRYPT      0x0004
+#define RFCOMM_LM_TRUSTED      0x0008
+#define RFCOMM_LM_RELIABLE     0x0010
+#define RFCOMM_LM_SECURE       0x0020
+
+/* RFCOMM TTY support */
+#define RFCOMM_MAX_DEV 256
+
+#define RFCOMMCREATEDEV                _IOW('R', 200, int)
+#define RFCOMMRELEASEDEV       _IOW('R', 201, int)
+#define RFCOMMGETDEVLIST       _IOR('R', 210, int)
+#define RFCOMMGETDEVINFO       _IOR('R', 211, int)
+
+struct rfcomm_dev_req {
+       int16_t         dev_id;
+       uint32_t        flags;
+       bdaddr_t        src;
+       bdaddr_t        dst;
+       uint8_t channel;
+};
+#define RFCOMM_REUSE_DLC       0
+#define RFCOMM_RELEASE_ONHUP   1
+#define RFCOMM_HANGUP_NOW      2
+#define RFCOMM_TTY_ATTACHED    3
+
+struct rfcomm_dev_info {
+       int16_t         id;
+       uint32_t        flags;
+       uint16_t        state;
+       bdaddr_t        src;
+       bdaddr_t        dst;
+       uint8_t         channel;
+};
+
+struct rfcomm_dev_list_req {
+       uint16_t        dev_num;
+       struct rfcomm_dev_info dev_info[0];
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __RFCOMM_H */
diff --git a/lib/sco.h b/lib/sco.h
new file mode 100644 (file)
index 0000000..75336a5
--- /dev/null
+++ b/lib/sco.h
@@ -0,0 +1,62 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __SCO_H
+#define __SCO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* SCO defaults */
+#define SCO_DEFAULT_MTU                500
+#define SCO_DEFAULT_FLUSH_TO   0xFFFF
+
+#define SCO_CONN_TIMEOUT       (HZ * 40)
+#define SCO_DISCONN_TIMEOUT    (HZ * 2)
+#define SCO_CONN_IDLE_TIMEOUT  (HZ * 60)
+
+/* SCO socket address */
+struct sockaddr_sco {
+       sa_family_t     sco_family;
+       bdaddr_t        sco_bdaddr;
+};
+
+/* set/get sockopt defines */
+#define SCO_OPTIONS    0x01
+struct sco_options {
+       uint16_t        mtu;
+};
+
+#define SCO_CONNINFO   0x02
+struct sco_conninfo {
+       uint16_t        hci_handle;
+       uint8_t         dev_class[3];
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SCO_H */
diff --git a/lib/sdp.c b/lib/sdp.c
new file mode 100644 (file)
index 0000000..e1108e1
--- /dev/null
+++ b/lib/sdp.c
@@ -0,0 +1,4963 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+
+#include "bluetooth.h"
+#include "hci.h"
+#include "hci_lib.h"
+#include "l2cap.h"
+#include "sdp.h"
+#include "sdp_lib.h"
+
+#define SDPINF(fmt, arg...) syslog(LOG_INFO, fmt "\n", ## arg)
+#define SDPERR(fmt, arg...) syslog(LOG_ERR, "%s: " fmt "\n", __func__ , ## arg)
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+#ifdef SDP_DEBUG
+#define SDPDBG(fmt, arg...) syslog(LOG_DEBUG, "%s: " fmt "\n", __func__ , ## arg)
+#else
+#define SDPDBG(fmt...)
+#endif
+
+#define BASE_UUID "00000000-0000-1000-8000-00805F9B34FB"
+
+static uint128_t bluetooth_base_uuid = {
+       .data = {       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+                       0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }
+};
+
+#define SDP_MAX_ATTR_LEN 65535
+
+static sdp_data_t *sdp_copy_seq(sdp_data_t *data);
+static int sdp_attr_add_new_with_length(sdp_record_t *rec,
+       uint16_t attr, uint8_t dtd, const void *value, uint32_t len);
+static int sdp_gen_buffer(sdp_buf_t *buf, sdp_data_t *d);
+
+/* Message structure. */
+struct tupla {
+       int index;
+       char *str;
+};
+
+static struct tupla Protocol[] = {
+       { SDP_UUID,             "SDP"           },
+       { UDP_UUID,             "UDP"           },
+       { RFCOMM_UUID,          "RFCOMM"        },
+       { TCP_UUID,             "TCP"           },
+       { TCS_BIN_UUID,         "TCS-BIN"       },
+       { TCS_AT_UUID,          "TCS-AT"        },
+       { OBEX_UUID,            "OBEX"          },
+       { IP_UUID,              "IP"            },
+       { FTP_UUID,             "FTP"           },
+       { HTTP_UUID,            "HTTP"          },
+       { WSP_UUID,             "WSP"           },
+       { BNEP_UUID,            "BNEP"          },
+       { UPNP_UUID,            "UPNP"          },
+       { HIDP_UUID,            "HIDP"          },
+       { HCRP_CTRL_UUID,       "HCRP-Ctrl"     },
+       { HCRP_DATA_UUID,       "HCRP-Data"     },
+       { HCRP_NOTE_UUID,       "HCRP-Notify"   },
+       { AVCTP_UUID,           "AVCTP"         },
+       { AVDTP_UUID,           "AVDTP"         },
+       { CMTP_UUID,            "CMTP"          },
+       { UDI_UUID,             "UDI"           },
+       { MCAP_CTRL_UUID,       "MCAP-Ctrl"     },
+       { MCAP_DATA_UUID,       "MCAP-Data"     },
+       { L2CAP_UUID,           "L2CAP"         },
+       { ATT_UUID,             "ATT"           },
+       { 0 }
+};
+
+static struct tupla ServiceClass[] = {
+       { SDP_SERVER_SVCLASS_ID,                "SDP Server"                    },
+       { BROWSE_GRP_DESC_SVCLASS_ID,           "Browse Group Descriptor"       },
+       { PUBLIC_BROWSE_GROUP,                  "Public Browse Group"           },
+       { SERIAL_PORT_SVCLASS_ID,               "Serial Port"                   },
+       { LAN_ACCESS_SVCLASS_ID,                "LAN Access Using PPP"          },
+       { DIALUP_NET_SVCLASS_ID,                "Dialup Networking"             },
+       { IRMC_SYNC_SVCLASS_ID,                 "IrMC Sync"                     },
+       { OBEX_OBJPUSH_SVCLASS_ID,              "OBEX Object Push"              },
+       { OBEX_FILETRANS_SVCLASS_ID,            "OBEX File Transfer"            },
+       { IRMC_SYNC_CMD_SVCLASS_ID,             "IrMC Sync Command"             },
+       { HEADSET_SVCLASS_ID,                   "Headset"                       },
+       { CORDLESS_TELEPHONY_SVCLASS_ID,        "Cordless Telephony"            },
+       { AUDIO_SOURCE_SVCLASS_ID,              "Audio Source"                  },
+       { AUDIO_SINK_SVCLASS_ID,                "Audio Sink"                    },
+       { AV_REMOTE_TARGET_SVCLASS_ID,          "AV Remote Target"              },
+       { ADVANCED_AUDIO_SVCLASS_ID,            "Advanced Audio"                },
+       { AV_REMOTE_SVCLASS_ID,                 "AV Remote"                     },
+       { VIDEO_CONF_SVCLASS_ID,                "Video Conferencing"            },
+       { INTERCOM_SVCLASS_ID,                  "Intercom"                      },
+       { FAX_SVCLASS_ID,                       "Fax"                           },
+       { HEADSET_AGW_SVCLASS_ID,               "Headset Audio Gateway"         },
+       { WAP_SVCLASS_ID,                       "WAP"                           },
+       { WAP_CLIENT_SVCLASS_ID,                "WAP Client"                    },
+       { PANU_SVCLASS_ID,                      "PAN User"                      },
+       { NAP_SVCLASS_ID,                       "Network Access Point"          },
+       { GN_SVCLASS_ID,                        "PAN Group Network"             },
+       { DIRECT_PRINTING_SVCLASS_ID,           "Direct Printing"               },
+       { REFERENCE_PRINTING_SVCLASS_ID,        "Reference Printing"            },
+       { IMAGING_SVCLASS_ID,                   "Imaging"                       },
+       { IMAGING_RESPONDER_SVCLASS_ID,         "Imaging Responder"             },
+       { IMAGING_ARCHIVE_SVCLASS_ID,           "Imaging Automatic Archive"     },
+       { IMAGING_REFOBJS_SVCLASS_ID,           "Imaging Referenced Objects"    },
+       { HANDSFREE_SVCLASS_ID,                 "Handsfree"                     },
+       { HANDSFREE_AGW_SVCLASS_ID,             "Handsfree Audio Gateway"       },
+       { DIRECT_PRT_REFOBJS_SVCLASS_ID,        "Direct Printing Ref. Objects"  },
+       { REFLECTED_UI_SVCLASS_ID,              "Reflected UI"                  },
+       { BASIC_PRINTING_SVCLASS_ID,            "Basic Printing"                },
+       { PRINTING_STATUS_SVCLASS_ID,           "Printing Status"               },
+       { HID_SVCLASS_ID,                       "Human Interface Device"        },
+       { HCR_SVCLASS_ID,                       "Hardcopy Cable Replacement"    },
+       { HCR_PRINT_SVCLASS_ID,                 "HCR Print"                     },
+       { HCR_SCAN_SVCLASS_ID,                  "HCR Scan"                      },
+       { CIP_SVCLASS_ID,                       "Common ISDN Access"            },
+       { VIDEO_CONF_GW_SVCLASS_ID,             "Video Conferencing Gateway"    },
+       { UDI_MT_SVCLASS_ID,                    "UDI MT"                        },
+       { UDI_TA_SVCLASS_ID,                    "UDI TA"                        },
+       { AV_SVCLASS_ID,                        "Audio/Video"                   },
+       { SAP_SVCLASS_ID,                       "SIM Access"                    },
+       { PBAP_PCE_SVCLASS_ID,                  "Phonebook Access - PCE"        },
+       { PBAP_PSE_SVCLASS_ID,                  "Phonebook Access - PSE"        },
+       { PBAP_SVCLASS_ID,                      "Phonebook Access"              },
+       { PNP_INFO_SVCLASS_ID,                  "PnP Information"               },
+       { GENERIC_NETWORKING_SVCLASS_ID,        "Generic Networking"            },
+       { GENERIC_FILETRANS_SVCLASS_ID,         "Generic File Transfer"         },
+       { GENERIC_AUDIO_SVCLASS_ID,             "Generic Audio"                 },
+       { GENERIC_TELEPHONY_SVCLASS_ID,         "Generic Telephony"             },
+       { UPNP_SVCLASS_ID,                      "UPnP"                          },
+       { UPNP_IP_SVCLASS_ID,                   "UPnP IP"                       },
+       { UPNP_PAN_SVCLASS_ID,                  "UPnP PAN"                      },
+       { UPNP_LAP_SVCLASS_ID,                  "UPnP LAP"                      },
+       { UPNP_L2CAP_SVCLASS_ID,                "UPnP L2CAP"                    },
+       { VIDEO_SOURCE_SVCLASS_ID,              "Video Source"                  },
+       { VIDEO_SINK_SVCLASS_ID,                "Video Sink"                    },
+       { VIDEO_DISTRIBUTION_SVCLASS_ID,        "Video Distribution"            },
+       { HDP_SVCLASS_ID,                       "HDP"                           },
+       { HDP_SOURCE_SVCLASS_ID,                "HDP Source"                    },
+       { HDP_SINK_SVCLASS_ID,                  "HDP Sink"                      },
+       { APPLE_AGENT_SVCLASS_ID,               "Apple Agent"                   },
+       { GENERIC_ATTRIB_SVCLASS_ID,            "Generic Attribute"             },
+       { 0 }
+};
+
+#define Profile ServiceClass
+
+static char *string_lookup(struct tupla *pt0, int index)
+{
+       struct tupla *pt;
+
+       for (pt = pt0; pt->index; pt++)
+               if (pt->index == index)
+                       return pt->str;
+
+       return "";
+}
+
+static char *string_lookup_uuid(struct tupla *pt0, const uuid_t* uuid)
+{
+       uuid_t tmp_uuid;
+
+       memcpy(&tmp_uuid, uuid, sizeof(tmp_uuid));
+
+       if (sdp_uuid128_to_uuid(&tmp_uuid)) {
+               switch (tmp_uuid.type) {
+               case SDP_UUID16:
+                       return string_lookup(pt0, tmp_uuid.value.uuid16);
+               case SDP_UUID32:
+                       return string_lookup(pt0, tmp_uuid.value.uuid32);
+               }
+       }
+
+       return "";
+}
+
+/*
+ * Prints into a string the Protocol UUID
+ * coping a maximum of n characters.
+ */
+static int uuid2str(struct tupla *message, const uuid_t *uuid, char *str, size_t n)
+{
+       char *str2;
+
+       if (!uuid) {
+               snprintf(str, n, "NULL");
+               return -2;
+       }
+
+       switch (uuid->type) {
+       case SDP_UUID16:
+               str2 = string_lookup(message, uuid->value.uuid16);
+               snprintf(str, n, "%s", str2);
+               break;
+       case SDP_UUID32:
+               str2 = string_lookup(message, uuid->value.uuid32);
+               snprintf(str, n, "%s", str2);
+               break;
+       case SDP_UUID128:
+               str2 = string_lookup_uuid(message, uuid);
+               snprintf(str, n, "%s", str2);
+               break;
+       default:
+               snprintf(str, n, "Type of UUID (%x) unknown.", uuid->type);
+               return -1;
+       }
+
+       return 0;
+}
+
+int sdp_proto_uuid2strn(const uuid_t *uuid, char *str, size_t n)
+{
+       return uuid2str(Protocol, uuid, str, n);
+}
+
+int sdp_svclass_uuid2strn(const uuid_t *uuid, char *str, size_t n)
+{
+       return uuid2str(ServiceClass, uuid, str, n);
+}
+
+int sdp_profile_uuid2strn(const uuid_t *uuid, char *str, size_t n)
+{
+       return uuid2str(Profile, uuid, str, n);
+}
+
+/*
+ * convert the UUID to string, copying a maximum of n characters.
+ */
+int sdp_uuid2strn(const uuid_t *uuid, char *str, size_t n)
+{
+       if (!uuid) {
+               snprintf(str, n, "NULL");
+               return -2;
+       }
+       switch (uuid->type) {
+       case SDP_UUID16:
+               snprintf(str, n, "%.4x", uuid->value.uuid16);
+               break;
+       case SDP_UUID32:
+               snprintf(str, n, "%.8x", uuid->value.uuid32);
+               break;
+       case SDP_UUID128:{
+               unsigned int   data0;
+               unsigned short data1;
+               unsigned short data2;
+               unsigned short data3;
+               unsigned int   data4;
+               unsigned short data5;
+
+               memcpy(&data0, &uuid->value.uuid128.data[0], 4);
+               memcpy(&data1, &uuid->value.uuid128.data[4], 2);
+               memcpy(&data2, &uuid->value.uuid128.data[6], 2);
+               memcpy(&data3, &uuid->value.uuid128.data[8], 2);
+               memcpy(&data4, &uuid->value.uuid128.data[10], 4);
+               memcpy(&data5, &uuid->value.uuid128.data[14], 2);
+
+               snprintf(str, n, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x",
+                               ntohl(data0), ntohs(data1),
+                               ntohs(data2), ntohs(data3),
+                               ntohl(data4), ntohs(data5));
+               }
+               break;
+       default:
+               snprintf(str, n, "Type of UUID (%x) unknown.", uuid->type);
+               return -1;      /* Enum type of UUID not set */
+       }
+       return 0;
+}
+
+#ifdef SDP_DEBUG
+/*
+ * Function prints the UUID in hex as per defined syntax -
+ *
+ * 4bytes-2bytes-2bytes-2bytes-6bytes
+ *
+ * There is some ugly code, including hardcoding, but
+ * that is just the way it is converting 16 and 32 bit
+ * UUIDs to 128 bit as defined in the SDP doc
+ */
+void sdp_uuid_print(const uuid_t *uuid)
+{
+       if (uuid == NULL) {
+               SDPERR("Null passed to print UUID\n");
+               return;
+       }
+       if (uuid->type == SDP_UUID16) {
+               SDPDBG("  uint16_t : 0x%.4x\n", uuid->value.uuid16);
+       } else if (uuid->type == SDP_UUID32) {
+               SDPDBG("  uint32_t : 0x%.8x\n", uuid->value.uuid32);
+       } else if (uuid->type == SDP_UUID128) {
+               unsigned int data0;
+               unsigned short data1;
+               unsigned short data2;
+               unsigned short data3;
+               unsigned int data4;
+               unsigned short data5;
+
+               memcpy(&data0, &uuid->value.uuid128.data[0], 4);
+               memcpy(&data1, &uuid->value.uuid128.data[4], 2);
+               memcpy(&data2, &uuid->value.uuid128.data[6], 2);
+               memcpy(&data3, &uuid->value.uuid128.data[8], 2);
+               memcpy(&data4, &uuid->value.uuid128.data[10], 4);
+               memcpy(&data5, &uuid->value.uuid128.data[14], 2);
+
+               SDPDBG("  uint128_t : 0x%.8x-", ntohl(data0));
+               SDPDBG("%.4x-", ntohs(data1));
+               SDPDBG("%.4x-", ntohs(data2));
+               SDPDBG("%.4x-", ntohs(data3));
+               SDPDBG("%.8x", ntohl(data4));
+               SDPDBG("%.4x\n", ntohs(data5));
+       } else
+               SDPERR("Enum type of UUID not set\n");
+}
+#endif
+
+sdp_data_t *sdp_data_alloc_with_length(uint8_t dtd, const void *value,
+                                                       uint32_t length)
+{
+       sdp_data_t *seq;
+       sdp_data_t *d = malloc(sizeof(sdp_data_t));
+
+       if (!d)
+               return NULL;
+
+       memset(d, 0, sizeof(sdp_data_t));
+       d->dtd = dtd;
+       d->unitSize = sizeof(uint8_t);
+
+       switch (dtd) {
+       case SDP_DATA_NIL:
+               break;
+       case SDP_UINT8:
+               d->val.uint8 = *(uint8_t *) value;
+               d->unitSize += sizeof(uint8_t);
+               break;
+       case SDP_INT8:
+       case SDP_BOOL:
+               d->val.int8 = *(int8_t *) value;
+               d->unitSize += sizeof(int8_t);
+               break;
+       case SDP_UINT16:
+               d->val.uint16 = bt_get_unaligned((uint16_t *) value);
+               d->unitSize += sizeof(uint16_t);
+               break;
+       case SDP_INT16:
+               d->val.int16 = bt_get_unaligned((int16_t *) value);
+               d->unitSize += sizeof(int16_t);
+               break;
+       case SDP_UINT32:
+               d->val.uint32 = bt_get_unaligned((uint32_t *) value);
+               d->unitSize += sizeof(uint32_t);
+               break;
+       case SDP_INT32:
+               d->val.int32 = bt_get_unaligned((int32_t *) value);
+               d->unitSize += sizeof(int32_t);
+               break;
+       case SDP_INT64:
+               d->val.int64 = bt_get_unaligned((int64_t *) value);
+               d->unitSize += sizeof(int64_t);
+               break;
+       case SDP_UINT64:
+               d->val.uint64 = bt_get_unaligned((uint64_t *) value);
+               d->unitSize += sizeof(uint64_t);
+               break;
+       case SDP_UINT128:
+               memcpy(&d->val.uint128.data, value, sizeof(uint128_t));
+               d->unitSize += sizeof(uint128_t);
+               break;
+       case SDP_INT128:
+               memcpy(&d->val.int128.data, value, sizeof(uint128_t));
+               d->unitSize += sizeof(uint128_t);
+               break;
+       case SDP_UUID16:
+               sdp_uuid16_create(&d->val.uuid, bt_get_unaligned((uint16_t *) value));
+               d->unitSize += sizeof(uint16_t);
+               break;
+       case SDP_UUID32:
+               sdp_uuid32_create(&d->val.uuid, bt_get_unaligned((uint32_t *) value));
+               d->unitSize += sizeof(uint32_t);
+               break;
+       case SDP_UUID128:
+               sdp_uuid128_create(&d->val.uuid, value);
+               d->unitSize += sizeof(uint128_t);
+               break;
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+               if (!value) {
+                       free(d);
+                       return NULL;
+               }
+
+               d->unitSize += length;
+               if (length <= USHRT_MAX) {
+                       d->val.str = malloc(length);
+                       if (!d->val.str) {
+                               free(d);
+                               return NULL;
+                       }
+
+                       memcpy(d->val.str, value, length);
+               } else {
+                       SDPERR("Strings of size > USHRT_MAX not supported\n");
+                       free(d);
+                       d = NULL;
+               }
+               break;
+       case SDP_URL_STR32:
+       case SDP_TEXT_STR32:
+               SDPERR("Strings of size > USHRT_MAX not supported\n");
+               break;
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+               if (dtd == SDP_ALT8 || dtd == SDP_SEQ8)
+                       d->unitSize += sizeof(uint8_t);
+               else if (dtd == SDP_ALT16 || dtd == SDP_SEQ16)
+                       d->unitSize += sizeof(uint16_t);
+               else if (dtd == SDP_ALT32 || dtd == SDP_SEQ32)
+                       d->unitSize += sizeof(uint32_t);
+               seq = (sdp_data_t *)value;
+               d->val.dataseq = seq;
+               for (; seq; seq = seq->next)
+                       d->unitSize += seq->unitSize;
+               break;
+       default:
+               free(d);
+               d = NULL;
+       }
+
+       return d;
+}
+
+sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value)
+{
+       uint32_t length;
+
+       switch (dtd) {
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+               if (!value)
+                       return NULL;
+
+               length = strlen((char *) value);
+               break;
+       default:
+               length = 0;
+               break;
+       }
+
+       return sdp_data_alloc_with_length(dtd, value, length);
+}
+
+sdp_data_t *sdp_seq_append(sdp_data_t *seq, sdp_data_t *d)
+{
+       if (seq) {
+               sdp_data_t *p;
+               for (p = seq; p->next; p = p->next);
+               p->next = d;
+       } else
+               seq = d;
+       d->next = NULL;
+       return seq;
+}
+
+sdp_data_t *sdp_seq_alloc_with_length(void **dtds, void **values, int *length,
+                                                               int len)
+{
+       sdp_data_t *curr = NULL, *seq = NULL;
+       int i;
+
+       for (i = 0; i < len; i++) {
+               sdp_data_t *data;
+               int8_t dtd = *(uint8_t *) dtds[i];
+
+               if (dtd >= SDP_SEQ8 && dtd <= SDP_ALT32)
+                       data = (sdp_data_t *) values[i];
+               else
+                       data = sdp_data_alloc_with_length(dtd, values[i], length[i]);
+
+               if (!data)
+                       return NULL;
+
+               if (curr)
+                       curr->next = data;
+               else
+                       seq = data;
+
+               curr = data;
+       }
+
+       return sdp_data_alloc_with_length(SDP_SEQ8, seq, length[i]);
+}
+
+sdp_data_t *sdp_seq_alloc(void **dtds, void **values, int len)
+{
+       sdp_data_t *curr = NULL, *seq = NULL;
+       int i;
+
+       for (i = 0; i < len; i++) {
+               sdp_data_t *data;
+               uint8_t dtd = *(uint8_t *) dtds[i];
+
+               if (dtd >= SDP_SEQ8 && dtd <= SDP_ALT32)
+                       data = (sdp_data_t *) values[i];
+               else
+                       data = sdp_data_alloc(dtd, values[i]);
+
+               if (!data)
+                       return NULL;
+
+               if (curr)
+                       curr->next = data;
+               else
+                       seq = data;
+
+               curr = data;
+       }
+
+       return sdp_data_alloc(SDP_SEQ8, seq);
+}
+
+static void extract_svclass_uuid(sdp_data_t *data, uuid_t *uuid)
+{
+       sdp_data_t *d;
+
+       if (!data || data->dtd < SDP_SEQ8 || data->dtd > SDP_SEQ32)
+               return;
+
+       d = data->val.dataseq;
+       if (!d)
+               return;
+
+       if (d->dtd < SDP_UUID16 || d->dtd > SDP_UUID128)
+               return;
+
+       *uuid = d->val.uuid;
+}
+
+int sdp_attr_add(sdp_record_t *rec, uint16_t attr, sdp_data_t *d)
+{
+       sdp_data_t *p = sdp_data_get(rec, attr);
+
+       if (p)
+               return -1;
+
+       d->attrId = attr;
+       rec->attrlist = sdp_list_insert_sorted(rec->attrlist, d, sdp_attrid_comp_func);
+
+       if (attr == SDP_ATTR_SVCLASS_ID_LIST)
+               extract_svclass_uuid(d, &rec->svclass);
+
+       return 0;
+}
+
+void sdp_attr_remove(sdp_record_t *rec, uint16_t attr)
+{
+       sdp_data_t *d = sdp_data_get(rec, attr);
+
+       if (d)
+               rec->attrlist = sdp_list_remove(rec->attrlist, d);
+
+       if (attr == SDP_ATTR_SVCLASS_ID_LIST)
+               memset(&rec->svclass, 0, sizeof(rec->svclass));
+}
+
+void sdp_set_seq_len(uint8_t *ptr, uint32_t length)
+{
+       uint8_t dtd = *ptr++;
+
+       switch (dtd) {
+       case SDP_SEQ8:
+       case SDP_ALT8:
+       case SDP_TEXT_STR8:
+       case SDP_URL_STR8:
+               *ptr = (uint8_t) length;
+               break;
+       case SDP_SEQ16:
+       case SDP_ALT16:
+       case SDP_TEXT_STR16:
+       case SDP_URL_STR16:
+               bt_put_unaligned(htons(length), (uint16_t *) ptr);
+               break;
+       case SDP_SEQ32:
+       case SDP_ALT32:
+       case SDP_TEXT_STR32:
+       case SDP_URL_STR32:
+               bt_put_unaligned(htonl(length), (uint32_t *) ptr);
+               break;
+       }
+}
+
+static int sdp_get_data_type(sdp_buf_t *buf, uint8_t dtd)
+{
+       int data_type = 0;
+
+       data_type += sizeof(uint8_t);
+
+       switch (dtd) {
+       case SDP_SEQ8:
+       case SDP_TEXT_STR8:
+       case SDP_URL_STR8:
+       case SDP_ALT8:
+               data_type += sizeof(uint8_t);
+               break;
+       case SDP_SEQ16:
+       case SDP_TEXT_STR16:
+       case SDP_URL_STR16:
+       case SDP_ALT16:
+               data_type += sizeof(uint16_t);
+               break;
+       case SDP_SEQ32:
+       case SDP_TEXT_STR32:
+       case SDP_URL_STR32:
+       case SDP_ALT32:
+               data_type += sizeof(uint32_t);
+               break;
+       }
+
+       if (!buf->data)
+               buf->buf_size += data_type;
+
+       return data_type;
+}
+
+static int sdp_set_data_type(sdp_buf_t *buf, uint8_t dtd)
+{
+       int data_type = 0;
+       uint8_t *p = buf->data + buf->data_size;
+
+       *p++ = dtd;
+       data_type = sdp_get_data_type(buf, dtd);
+       buf->data_size += data_type;
+
+       return data_type;
+}
+
+void sdp_set_attrid(sdp_buf_t *buf, uint16_t attr)
+{
+       uint8_t *p = buf->data;
+
+       /* data type for attr */
+       *p++ = SDP_UINT16;
+       buf->data_size = sizeof(uint8_t);
+       bt_put_unaligned(htons(attr), (uint16_t *) p);
+       p += sizeof(uint16_t);
+       buf->data_size += sizeof(uint16_t);
+}
+
+static int get_data_size(sdp_buf_t *buf, sdp_data_t *sdpdata)
+{
+       sdp_data_t *d;
+       int n = 0;
+
+       for (d = sdpdata->val.dataseq; d; d = d->next) {
+               if (buf->data)
+                       n += sdp_gen_pdu(buf, d);
+               else
+                       n += sdp_gen_buffer(buf, d);
+       }
+
+       return n;
+}
+
+static int sdp_get_data_size(sdp_buf_t *buf, sdp_data_t *d)
+{
+       uint32_t data_size = 0;
+       uint8_t dtd = d->dtd;
+
+       switch (dtd) {
+       case SDP_DATA_NIL:
+               break;
+       case SDP_UINT8:
+               data_size = sizeof(uint8_t);
+               break;
+       case SDP_UINT16:
+               data_size = sizeof(uint16_t);
+               break;
+       case SDP_UINT32:
+               data_size = sizeof(uint32_t);
+               break;
+       case SDP_UINT64:
+               data_size = sizeof(uint64_t);
+               break;
+       case SDP_UINT128:
+               data_size = sizeof(uint128_t);
+               break;
+       case SDP_INT8:
+       case SDP_BOOL:
+               data_size = sizeof(int8_t);
+               break;
+       case SDP_INT16:
+               data_size = sizeof(int16_t);
+               break;
+       case SDP_INT32:
+               data_size = sizeof(int32_t);
+               break;
+       case SDP_INT64:
+               data_size = sizeof(int64_t);
+               break;
+       case SDP_INT128:
+               data_size = sizeof(uint128_t);
+               break;
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_TEXT_STR32:
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_URL_STR32:
+               data_size = d->unitSize - sizeof(uint8_t);
+               break;
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+               data_size = get_data_size(buf, d);
+               break;
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+               data_size = get_data_size(buf, d);
+               break;
+       case SDP_UUID16:
+               data_size = sizeof(uint16_t);
+               break;
+       case SDP_UUID32:
+               data_size = sizeof(uint32_t);
+               break;
+       case SDP_UUID128:
+               data_size = sizeof(uint128_t);
+               break;
+       default:
+               break;
+       }
+
+       if (!buf->data)
+               buf->buf_size += data_size;
+
+       return data_size;
+}
+
+static int sdp_gen_buffer(sdp_buf_t *buf, sdp_data_t *d)
+{
+       int orig = buf->buf_size;
+
+       if (buf->buf_size == 0 && d->dtd == 0) {
+               /* create initial sequence */
+               buf->buf_size += sizeof(uint8_t);
+
+               /* reserve space for sequence size */
+               buf->buf_size += sizeof(uint8_t);
+       }
+
+       /* attribute length */
+       buf->buf_size += sizeof(uint8_t) + sizeof(uint16_t);
+
+       sdp_get_data_type(buf, d->dtd);
+       sdp_get_data_size(buf, d);
+
+       if (buf->buf_size > UCHAR_MAX && d->dtd == SDP_SEQ8)
+               buf->buf_size += sizeof(uint8_t);
+
+       return buf->buf_size - orig;
+}
+
+int sdp_gen_pdu(sdp_buf_t *buf, sdp_data_t *d)
+{
+       uint32_t pdu_size = 0, data_size = 0;
+       unsigned char *src = NULL, is_seq = 0, is_alt = 0;
+       uint8_t dtd = d->dtd;
+       uint16_t u16;
+       uint32_t u32;
+       uint64_t u64;
+       uint128_t u128;
+       uint8_t *seqp = buf->data + buf->data_size;
+
+       pdu_size = sdp_set_data_type(buf, dtd);
+       data_size = sdp_get_data_size(buf, d);
+
+       switch (dtd) {
+       case SDP_DATA_NIL:
+               break;
+       case SDP_UINT8:
+               src = &d->val.uint8;
+               break;
+       case SDP_UINT16:
+               u16 = htons(d->val.uint16);
+               src = (unsigned char *) &u16;
+               break;
+       case SDP_UINT32:
+               u32 = htonl(d->val.uint32);
+               src = (unsigned char *) &u32;
+               break;
+       case SDP_UINT64:
+               u64 = hton64(d->val.uint64);
+               src = (unsigned char *) &u64;
+               break;
+       case SDP_UINT128:
+               hton128(&d->val.uint128, &u128);
+               src = (unsigned char *) &u128;
+               break;
+       case SDP_INT8:
+       case SDP_BOOL:
+               src = (unsigned char *) &d->val.int8;
+               break;
+       case SDP_INT16:
+               u16 = htons(d->val.int16);
+               src = (unsigned char *) &u16;
+               break;
+       case SDP_INT32:
+               u32 = htonl(d->val.int32);
+               src = (unsigned char *) &u32;
+               break;
+       case SDP_INT64:
+               u64 = hton64(d->val.int64);
+               src = (unsigned char *) &u64;
+               break;
+       case SDP_INT128:
+               hton128(&d->val.int128, &u128);
+               src = (unsigned char *) &u128;
+               break;
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_TEXT_STR32:
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_URL_STR32:
+               src = (unsigned char *) d->val.str;
+               sdp_set_seq_len(seqp, data_size);
+               break;
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+               is_seq = 1;
+               sdp_set_seq_len(seqp, data_size);
+               break;
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+               is_alt = 1;
+               sdp_set_seq_len(seqp, data_size);
+               break;
+       case SDP_UUID16:
+               u16 = htons(d->val.uuid.value.uuid16);
+               src = (unsigned char *) &u16;
+               break;
+       case SDP_UUID32:
+               u32 = htonl(d->val.uuid.value.uuid32);
+               src = (unsigned char *) &u32;
+               break;
+       case SDP_UUID128:
+               src = (unsigned char *) &d->val.uuid.value.uuid128;
+               break;
+       default:
+               break;
+       }
+
+       if (!is_seq && !is_alt) {
+               if (src && buf && buf->buf_size >= buf->data_size + data_size) {
+                       memcpy(buf->data + buf->data_size, src, data_size);
+                       buf->data_size += data_size;
+               } else if (dtd != SDP_DATA_NIL) {
+                       SDPDBG("Gen PDU : Can't copy from invalid source or dest\n");
+               }
+       }
+
+       pdu_size += data_size;
+
+       return pdu_size;
+}
+
+static void sdp_attr_pdu(void *value, void *udata)
+{
+       sdp_append_to_pdu((sdp_buf_t *)udata, (sdp_data_t *)value);
+}
+
+static void sdp_attr_size(void *value, void *udata)
+{
+       sdp_gen_buffer((sdp_buf_t *)udata, (sdp_data_t *)value);
+}
+
+int sdp_gen_record_pdu(const sdp_record_t *rec, sdp_buf_t *buf)
+{
+       memset(buf, 0, sizeof(sdp_buf_t));
+       sdp_list_foreach(rec->attrlist, sdp_attr_size, buf);
+
+       buf->data = malloc(buf->buf_size);
+       if (!buf->data)
+               return -ENOMEM;
+       buf->data_size = 0;
+       memset(buf->data, 0, buf->buf_size);
+
+       sdp_list_foreach(rec->attrlist, sdp_attr_pdu, buf);
+
+       return 0;
+}
+
+void sdp_attr_replace(sdp_record_t *rec, uint16_t attr, sdp_data_t *d)
+{
+       sdp_data_t *p = sdp_data_get(rec, attr);
+
+       if (p) {
+               rec->attrlist = sdp_list_remove(rec->attrlist, p);
+               sdp_data_free(p);
+       }
+
+       d->attrId = attr;
+       rec->attrlist = sdp_list_insert_sorted(rec->attrlist, d, sdp_attrid_comp_func);
+
+       if (attr == SDP_ATTR_SVCLASS_ID_LIST)
+               extract_svclass_uuid(d, &rec->svclass);
+}
+
+int sdp_attrid_comp_func(const void *key1, const void *key2)
+{
+       const sdp_data_t *d1 = (const sdp_data_t *)key1;
+       const sdp_data_t *d2 = (const sdp_data_t *)key2;
+
+       if (d1 && d2)
+               return d1->attrId - d2->attrId;
+       return 0;
+}
+
+static void data_seq_free(sdp_data_t *seq)
+{
+       sdp_data_t *d = seq->val.dataseq;
+
+       while (d) {
+               sdp_data_t *next = d->next;
+               sdp_data_free(d);
+               d = next;
+       }
+}
+
+void sdp_data_free(sdp_data_t *d)
+{
+       switch (d->dtd) {
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+               data_seq_free(d);
+               break;
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_URL_STR32:
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_TEXT_STR32:
+               free(d->val.str);
+               break;
+       }
+       free(d);
+}
+
+int sdp_uuid_extract(const uint8_t *p, int bufsize, uuid_t *uuid, int *scanned)
+{
+       uint8_t type;
+
+       if (bufsize < (int) sizeof(uint8_t)) {
+               SDPERR("Unexpected end of packet");
+               return -1;
+       }
+
+       type = *(const uint8_t *) p;
+
+       if (!SDP_IS_UUID(type)) {
+               SDPERR("Unknown data type : %d expecting a svc UUID\n", type);
+               return -1;
+       }
+       p += sizeof(uint8_t);
+       *scanned += sizeof(uint8_t);
+       bufsize -= sizeof(uint8_t);
+       if (type == SDP_UUID16) {
+               if (bufsize < (int) sizeof(uint16_t)) {
+                       SDPERR("Not enough room for 16-bit UUID");
+                       return -1;
+               }
+               sdp_uuid16_create(uuid, ntohs(bt_get_unaligned((uint16_t *) p)));
+               *scanned += sizeof(uint16_t);
+               p += sizeof(uint16_t);
+       } else if (type == SDP_UUID32) {
+               if (bufsize < (int) sizeof(uint32_t)) {
+                       SDPERR("Not enough room for 32-bit UUID");
+                       return -1;
+               }
+               sdp_uuid32_create(uuid, ntohl(bt_get_unaligned((uint32_t *) p)));
+               *scanned += sizeof(uint32_t);
+               p += sizeof(uint32_t);
+       } else {
+               if (bufsize < (int) sizeof(uint128_t)) {
+                       SDPERR("Not enough room for 128-bit UUID");
+                       return -1;
+               }
+               sdp_uuid128_create(uuid, p);
+               *scanned += sizeof(uint128_t);
+               p += sizeof(uint128_t);
+       }
+       return 0;
+}
+
+static sdp_data_t *extract_int(const void *p, int bufsize, int *len)
+{
+       sdp_data_t *d;
+
+       if (bufsize < (int) sizeof(uint8_t)) {
+               SDPERR("Unexpected end of packet");
+               return NULL;
+       }
+
+       d = malloc(sizeof(sdp_data_t));
+       if (!d)
+               return NULL;
+
+       SDPDBG("Extracting integer\n");
+       memset(d, 0, sizeof(sdp_data_t));
+       d->dtd = *(uint8_t *) p;
+       p += sizeof(uint8_t);
+       *len += sizeof(uint8_t);
+       bufsize -= sizeof(uint8_t);
+
+       switch (d->dtd) {
+       case SDP_DATA_NIL:
+               break;
+       case SDP_BOOL:
+       case SDP_INT8:
+       case SDP_UINT8:
+               if (bufsize < (int) sizeof(uint8_t)) {
+                       SDPERR("Unexpected end of packet");
+                       free(d);
+                       return NULL;
+               }
+               *len += sizeof(uint8_t);
+               d->val.uint8 = *(uint8_t *) p;
+               break;
+       case SDP_INT16:
+       case SDP_UINT16:
+               if (bufsize < (int) sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       free(d);
+                       return NULL;
+               }
+               *len += sizeof(uint16_t);
+               d->val.uint16 = ntohs(bt_get_unaligned((uint16_t *) p));
+               break;
+       case SDP_INT32:
+       case SDP_UINT32:
+               if (bufsize < (int) sizeof(uint32_t)) {
+                       SDPERR("Unexpected end of packet");
+                       free(d);
+                       return NULL;
+               }
+               *len += sizeof(uint32_t);
+               d->val.uint32 = ntohl(bt_get_unaligned((uint32_t *) p));
+               break;
+       case SDP_INT64:
+       case SDP_UINT64:
+               if (bufsize < (int) sizeof(uint64_t)) {
+                       SDPERR("Unexpected end of packet");
+                       free(d);
+                       return NULL;
+               }
+               *len += sizeof(uint64_t);
+               d->val.uint64 = ntoh64(bt_get_unaligned((uint64_t *) p));
+               break;
+       case SDP_INT128:
+       case SDP_UINT128:
+               if (bufsize < (int) sizeof(uint128_t)) {
+                       SDPERR("Unexpected end of packet");
+                       free(d);
+                       return NULL;
+               }
+               *len += sizeof(uint128_t);
+               ntoh128((uint128_t *) p, &d->val.uint128);
+               break;
+       default:
+               free(d);
+               d = NULL;
+       }
+       return d;
+}
+
+static sdp_data_t *extract_uuid(const uint8_t *p, int bufsize, int *len,
+                                                       sdp_record_t *rec)
+{
+       sdp_data_t *d = malloc(sizeof(sdp_data_t));
+
+       if (!d)
+               return NULL;
+
+       SDPDBG("Extracting UUID");
+       memset(d, 0, sizeof(sdp_data_t));
+       if (sdp_uuid_extract(p, bufsize, &d->val.uuid, len) < 0) {
+               free(d);
+               return NULL;
+       }
+       d->dtd = *p;
+       if (rec)
+               sdp_pattern_add_uuid(rec, &d->val.uuid);
+       return d;
+}
+
+/*
+ * Extract strings from the PDU (could be service description and similar info)
+ */
+static sdp_data_t *extract_str(const void *p, int bufsize, int *len)
+{
+       char *s;
+       int n;
+       sdp_data_t *d;
+
+       if (bufsize < (int) sizeof(uint8_t)) {
+               SDPERR("Unexpected end of packet");
+               return NULL;
+       }
+
+       d = malloc(sizeof(sdp_data_t));
+       if (!d)
+               return NULL;
+
+       memset(d, 0, sizeof(sdp_data_t));
+       d->dtd = *(uint8_t *) p;
+       p += sizeof(uint8_t);
+       *len += sizeof(uint8_t);
+       bufsize -= sizeof(uint8_t);
+
+       switch (d->dtd) {
+       case SDP_TEXT_STR8:
+       case SDP_URL_STR8:
+               if (bufsize < (int) sizeof(uint8_t)) {
+                       SDPERR("Unexpected end of packet");
+                       free(d);
+                       return NULL;
+               }
+               n = *(uint8_t *) p;
+               p += sizeof(uint8_t);
+               *len += sizeof(uint8_t);
+               bufsize -= sizeof(uint8_t);
+               break;
+       case SDP_TEXT_STR16:
+       case SDP_URL_STR16:
+               if (bufsize < (int) sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       free(d);
+                       return NULL;
+               }
+               n = ntohs(bt_get_unaligned((uint16_t *) p));
+               p += sizeof(uint16_t);
+               *len += sizeof(uint16_t) + n;
+               bufsize -= sizeof(uint16_t);
+               break;
+       default:
+               SDPERR("Sizeof text string > UINT16_MAX\n");
+               free(d);
+               return NULL;
+       }
+
+       if (bufsize < n) {
+               SDPERR("String too long to fit in packet");
+               free(d);
+               return NULL;
+       }
+
+       s = malloc(n + 1);
+       if (!s) {
+               SDPERR("Not enough memory for incoming string");
+               free(d);
+               return NULL;
+       }
+       memset(s, 0, n + 1);
+       memcpy(s, p, n);
+
+       *len += n;
+
+       SDPDBG("Len : %d\n", n);
+       SDPDBG("Str : %s\n", s);
+
+       d->val.str = s;
+       d->unitSize = n + sizeof(uint8_t);
+       return d;
+}
+
+/*
+ * Extract the sequence type and its length, and return offset into buf
+ * or 0 on failure.
+ */
+int sdp_extract_seqtype(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size)
+{
+       uint8_t dtd;
+       int scanned = sizeof(uint8_t);
+
+       if (bufsize < (int) sizeof(uint8_t)) {
+               SDPERR("Unexpected end of packet");
+               return 0;
+       }
+
+       dtd = *(uint8_t *) buf;
+       buf += sizeof(uint8_t);
+       bufsize -= sizeof(uint8_t);
+       *dtdp = dtd;
+       switch (dtd) {
+       case SDP_SEQ8:
+       case SDP_ALT8:
+               if (bufsize < (int) sizeof(uint8_t)) {
+                       SDPERR("Unexpected end of packet");
+                       return 0;
+               }
+               *size = *(uint8_t *) buf;
+               scanned += sizeof(uint8_t);
+               break;
+       case SDP_SEQ16:
+       case SDP_ALT16:
+               if (bufsize < (int) sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       return 0;
+               }
+               *size = ntohs(bt_get_unaligned((uint16_t *) buf));
+               scanned += sizeof(uint16_t);
+               break;
+       case SDP_SEQ32:
+       case SDP_ALT32:
+               if (bufsize < (int) sizeof(uint32_t)) {
+                       SDPERR("Unexpected end of packet");
+                       return 0;
+               }
+               *size = ntohl(bt_get_unaligned((uint32_t *) buf));
+               scanned += sizeof(uint32_t);
+               break;
+       default:
+               SDPERR("Unknown sequence type, aborting\n");
+               return 0;
+       }
+       return scanned;
+}
+
+static sdp_data_t *extract_seq(const void *p, int bufsize, int *len,
+                                                       sdp_record_t *rec)
+{
+       int seqlen, n = 0;
+       sdp_data_t *curr, *prev;
+       sdp_data_t *d = malloc(sizeof(sdp_data_t));
+
+       if (!d)
+               return NULL;
+
+       SDPDBG("Extracting SEQ");
+       memset(d, 0, sizeof(sdp_data_t));
+       *len = sdp_extract_seqtype(p, bufsize, &d->dtd, &seqlen);
+       SDPDBG("Sequence Type : 0x%x length : 0x%x\n", d->dtd, seqlen);
+
+       if (*len == 0)
+               return d;
+
+       if (*len > bufsize) {
+               SDPERR("Packet not big enough to hold sequence.");
+               free(d);
+               return NULL;
+       }
+
+       p += *len;
+       bufsize -= *len;
+       prev = NULL;
+       while (n < seqlen) {
+               int attrlen = 0;
+               curr = sdp_extract_attr(p, bufsize, &attrlen, rec);
+               if (curr == NULL)
+                       break;
+
+               if (prev)
+                       prev->next = curr;
+               else
+                       d->val.dataseq = curr;
+               prev = curr;
+               p += attrlen;
+               n += attrlen;
+               bufsize -= attrlen;
+
+               SDPDBG("Extracted: %d SequenceLength: %d", n, seqlen);
+       }
+
+       *len += n;
+       return d;
+}
+
+sdp_data_t *sdp_extract_attr(const uint8_t *p, int bufsize, int *size,
+                                                       sdp_record_t *rec)
+{
+       sdp_data_t *elem;
+       int n = 0;
+       uint8_t dtd;
+
+       if (bufsize < (int) sizeof(uint8_t)) {
+               SDPERR("Unexpected end of packet");
+               return NULL;
+       }
+
+       dtd = *(const uint8_t *)p;
+
+       SDPDBG("extract_attr: dtd=0x%x", dtd);
+       switch (dtd) {
+       case SDP_DATA_NIL:
+       case SDP_BOOL:
+       case SDP_UINT8:
+       case SDP_UINT16:
+       case SDP_UINT32:
+       case SDP_UINT64:
+       case SDP_UINT128:
+       case SDP_INT8:
+       case SDP_INT16:
+       case SDP_INT32:
+       case SDP_INT64:
+       case SDP_INT128:
+               elem = extract_int(p, bufsize, &n);
+               break;
+       case SDP_UUID16:
+       case SDP_UUID32:
+       case SDP_UUID128:
+               elem = extract_uuid(p, bufsize, &n, rec);
+               break;
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_TEXT_STR32:
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_URL_STR32:
+               elem = extract_str(p, bufsize, &n);
+               break;
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+               elem = extract_seq(p, bufsize, &n, rec);
+               break;
+       default:
+               SDPERR("Unknown data descriptor : 0x%x terminating\n", dtd);
+               return NULL;
+       }
+       *size += n;
+       return elem;
+}
+
+#ifdef SDP_DEBUG
+static void attr_print_func(void *value, void *userData)
+{
+       sdp_data_t *d = (sdp_data_t *)value;
+
+       SDPDBG("=====================================\n");
+       SDPDBG("ATTRIBUTE IDENTIFIER : 0x%x\n",  d->attrId);
+       SDPDBG("ATTRIBUTE VALUE PTR : 0x%x\n", (uint32_t)value);
+       if (d)
+               sdp_data_print(d);
+       else
+               SDPDBG("NULL value\n");
+       SDPDBG("=====================================\n");
+}
+
+void sdp_print_service_attr(sdp_list_t *svcAttrList)
+{
+       SDPDBG("Printing service attr list %p\n", svcAttrList);
+       sdp_list_foreach(svcAttrList, attr_print_func, NULL);
+       SDPDBG("Printed service attr list %p\n", svcAttrList);
+}
+#endif
+
+sdp_record_t *sdp_extract_pdu(const uint8_t *buf, int bufsize, int *scanned)
+{
+       int extracted = 0, seqlen = 0;
+       uint8_t dtd;
+       uint16_t attr;
+       sdp_record_t *rec = sdp_record_alloc();
+       const uint8_t *p = buf;
+
+       *scanned = sdp_extract_seqtype(buf, bufsize, &dtd, &seqlen);
+       p += *scanned;
+       bufsize -= *scanned;
+       rec->attrlist = NULL;
+
+       while (extracted < seqlen && bufsize > 0) {
+               int n = sizeof(uint8_t), attrlen = 0;
+               sdp_data_t *data = NULL;
+
+               SDPDBG("Extract PDU, sequenceLength: %d localExtractedLength: %d",
+                                                       seqlen, extracted);
+
+               if (bufsize < n + (int) sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       break;
+               }
+
+               dtd = *(uint8_t *) p;
+               attr = ntohs(bt_get_unaligned((uint16_t *) (p + n)));
+               n += sizeof(uint16_t);
+
+               SDPDBG("DTD of attrId : %d Attr id : 0x%x \n", dtd, attr);
+
+               data = sdp_extract_attr(p + n, bufsize - n, &attrlen, rec);
+
+               SDPDBG("Attr id : 0x%x attrValueLength : %d\n", attr, attrlen);
+
+               n += attrlen;
+               if (data == NULL) {
+                       SDPDBG("Terminating extraction of attributes");
+                       break;
+               }
+
+               if (attr == SDP_ATTR_RECORD_HANDLE)
+                       rec->handle = data->val.uint32;
+
+               if (attr == SDP_ATTR_SVCLASS_ID_LIST)
+                       extract_svclass_uuid(data, &rec->svclass);
+
+               extracted += n;
+               p += n;
+               bufsize -= n;
+               sdp_attr_replace(rec, attr, data);
+
+               SDPDBG("Extract PDU, seqLength: %d localExtractedLength: %d",
+                                                       seqlen, extracted);
+       }
+#ifdef SDP_DEBUG
+       SDPDBG("Successful extracting of Svc Rec attributes\n");
+       sdp_print_service_attr(rec->attrlist);
+#endif
+       *scanned += seqlen;
+       return rec;
+}
+
+static void sdp_copy_pattern(void *value, void *udata)
+{
+       uuid_t *uuid = value;
+       sdp_record_t *rec = udata;
+
+       sdp_pattern_add_uuid(rec, uuid);
+}
+
+static void *sdp_data_value(sdp_data_t *data, uint32_t *len)
+{
+       void *val = NULL;
+
+       switch (data->dtd) {
+       case SDP_DATA_NIL:
+               break;
+       case SDP_UINT8:
+               val = &data->val.uint8;
+               break;
+       case SDP_INT8:
+       case SDP_BOOL:
+               val = &data->val.int8;
+               break;
+       case SDP_UINT16:
+               val = &data->val.uint16;
+               break;
+       case SDP_INT16:
+               val = &data->val.int16;
+               break;
+       case SDP_UINT32:
+               val = &data->val.uint32;
+               break;
+       case SDP_INT32:
+               val = &data->val.int32;
+               break;
+       case SDP_INT64:
+               val = &data->val.int64;
+               break;
+       case SDP_UINT64:
+               val = &data->val.uint64;
+               break;
+       case SDP_UINT128:
+               val = &data->val.uint128;
+               break;
+       case SDP_INT128:
+               val = &data->val.int128;
+               break;
+       case SDP_UUID16:
+               val = &data->val.uuid.value.uuid16;
+               break;
+       case SDP_UUID32:
+               val = &data->val.uuid.value.uuid32;
+               break;
+       case SDP_UUID128:
+               val = &data->val.uuid.value.uuid128;
+               break;
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_URL_STR32:
+       case SDP_TEXT_STR32:
+               val = data->val.str;
+               if (len)
+                       *len = data->unitSize - 1;
+               break;
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+               val = sdp_copy_seq(data->val.dataseq);
+               break;
+       }
+
+       return val;
+}
+
+static sdp_data_t *sdp_copy_seq(sdp_data_t *data)
+{
+       sdp_data_t *tmp, *seq = NULL, *cur = NULL;
+
+       for (tmp = data; tmp; tmp = tmp->next) {
+               sdp_data_t *datatmp;
+               void *value;
+
+               value = sdp_data_value(tmp, NULL);
+               datatmp = sdp_data_alloc_with_length(tmp->dtd, value,
+                                                               tmp->unitSize);
+
+               if (cur)
+                       cur->next = datatmp;
+               else
+                       seq = datatmp;
+
+               cur = datatmp;
+       }
+
+       return seq;
+}
+
+static void sdp_copy_attrlist(void *value, void *udata)
+{
+       sdp_data_t *data = value;
+       sdp_record_t *rec = udata;
+       void *val;
+       uint32_t len = 0;
+
+       val = sdp_data_value(data, &len);
+
+       if (!len)
+               sdp_attr_add_new(rec, data->attrId, data->dtd, val);
+       else
+               sdp_attr_add_new_with_length(rec, data->attrId,
+                                                       data->dtd, val, len);
+}
+
+sdp_record_t *sdp_copy_record(sdp_record_t *rec)
+{
+       sdp_record_t *cpy;
+
+       cpy = sdp_record_alloc();
+
+       cpy->handle = rec->handle;
+
+       sdp_list_foreach(rec->pattern, sdp_copy_pattern, cpy);
+       sdp_list_foreach(rec->attrlist, sdp_copy_attrlist, cpy);
+
+       cpy->svclass = rec->svclass;
+
+       return cpy;
+}
+
+#ifdef SDP_DEBUG
+static void print_dataseq(sdp_data_t *p)
+{
+       sdp_data_t *d;
+
+       for (d = p; d; d = d->next)
+               sdp_data_print(d);
+}
+#endif
+
+void sdp_record_print(const sdp_record_t *rec)
+{
+       sdp_data_t *d = sdp_data_get(rec, SDP_ATTR_SVCNAME_PRIMARY);
+       if (d)
+               printf("Service Name: %.*s\n", d->unitSize, d->val.str);
+       d = sdp_data_get(rec, SDP_ATTR_SVCDESC_PRIMARY);
+       if (d)
+               printf("Service Description: %.*s\n", d->unitSize, d->val.str);
+       d = sdp_data_get(rec, SDP_ATTR_PROVNAME_PRIMARY);
+       if (d)
+               printf("Service Provider: %.*s\n", d->unitSize, d->val.str);
+}
+
+#ifdef SDP_DEBUG
+void sdp_data_print(sdp_data_t *d)
+{
+       switch (d->dtd) {
+       case SDP_DATA_NIL:
+               SDPDBG("NIL\n");
+               break;
+       case SDP_BOOL:
+       case SDP_UINT8:
+       case SDP_UINT16:
+       case SDP_UINT32:
+       case SDP_UINT64:
+       case SDP_UINT128:
+       case SDP_INT8:
+       case SDP_INT16:
+       case SDP_INT32:
+       case SDP_INT64:
+       case SDP_INT128:
+               SDPDBG("Integer : 0x%x\n", d->val.uint32);
+               break;
+       case SDP_UUID16:
+       case SDP_UUID32:
+       case SDP_UUID128:
+               SDPDBG("UUID\n");
+               sdp_uuid_print(&d->val.uuid);
+               break;
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_TEXT_STR32:
+               SDPDBG("Text : %s\n", d->val.str);
+               break;
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_URL_STR32:
+               SDPDBG("URL : %s\n", d->val.str);
+               break;
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+               print_dataseq(d->val.dataseq);
+               break;
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+               SDPDBG("Data Sequence Alternates\n");
+               print_dataseq(d->val.dataseq);
+               break;
+       }
+}
+#endif
+
+sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attrId)
+{
+       if (rec->attrlist) {
+               sdp_data_t sdpTemplate;
+               sdp_list_t *p;
+
+               sdpTemplate.attrId = attrId;
+               p = sdp_list_find(rec->attrlist, &sdpTemplate, sdp_attrid_comp_func);
+               if (p)
+                       return p->data;
+       }
+       return NULL;
+}
+
+static int sdp_send_req(sdp_session_t *session, uint8_t *buf, uint32_t size)
+{
+       uint32_t sent = 0;
+
+       while (sent < size) {
+               int n = send(session->sock, buf + sent, size - sent, 0);
+               if (n < 0)
+                       return -1;
+               sent += n;
+       }
+       return 0;
+}
+
+static int sdp_read_rsp(sdp_session_t *session, uint8_t *buf, uint32_t size)
+{
+       fd_set readFds;
+       struct timeval timeout = { SDP_RESPONSE_TIMEOUT, 0 };
+
+       FD_ZERO(&readFds);
+       FD_SET(session->sock, &readFds);
+       SDPDBG("Waiting for response\n");
+       if (select(session->sock + 1, &readFds, NULL, NULL, &timeout) == 0) {
+               SDPERR("Client timed out\n");
+               errno = ETIMEDOUT;
+               return -1;
+       }
+       return recv(session->sock, buf, size, 0);
+}
+
+/*
+ * generic send request, wait for response method.
+ */
+int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *reqbuf,
+                       uint8_t *rspbuf, uint32_t reqsize, uint32_t *rspsize)
+{
+       int n;
+       sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *) reqbuf;
+       sdp_pdu_hdr_t *rsphdr = (sdp_pdu_hdr_t *) rspbuf;
+
+       SDPDBG("");
+       if (0 > sdp_send_req(session, reqbuf, reqsize)) {
+               SDPERR("Error sending data:%s", strerror(errno));
+               return -1;
+       }
+       n = sdp_read_rsp(session, rspbuf, SDP_RSP_BUFFER_SIZE);
+       if (0 > n)
+               return -1;
+       SDPDBG("Read : %d\n", n);
+       if (n == 0 || reqhdr->tid != rsphdr->tid) {
+               errno = EPROTO;
+               return -1;
+       }
+       *rspsize = n;
+       return 0;
+}
+
+/*
+ * singly-linked lists (after openobex implementation)
+ */
+sdp_list_t *sdp_list_append(sdp_list_t *p, void *d)
+{
+       sdp_list_t *q, *n = malloc(sizeof(sdp_list_t));
+
+       if (!n)
+               return NULL;
+
+       n->data = d;
+       n->next = 0;
+
+       if (!p)
+               return n;
+
+       for (q = p; q->next; q = q->next);
+       q->next = n;
+
+       return p;
+}
+
+sdp_list_t *sdp_list_remove(sdp_list_t *list, void *d)
+{
+       sdp_list_t *p, *q;
+
+       for (q = 0, p = list; p; q = p, p = p->next)
+               if (p->data == d) {
+                       if (q)
+                               q->next = p->next;
+                       else
+                               list = p->next;
+                       free(p);
+                       break;
+               }
+
+       return list;
+}
+
+sdp_list_t *sdp_list_insert_sorted(sdp_list_t *list, void *d,
+                                                       sdp_comp_func_t f)
+{
+       sdp_list_t *q, *p, *n;
+
+       n = malloc(sizeof(sdp_list_t));
+       if (!n)
+               return NULL;
+       n->data = d;
+       for (q = 0, p = list; p; q = p, p = p->next)
+               if (f(p->data, d) >= 0)
+                       break;
+       // insert between q and p; if !q insert at head
+       if (q)
+               q->next = n;
+       else
+               list = n;
+       n->next = p;
+       return list;
+}
+
+/*
+ * Every element of the list points to things which need
+ * to be free()'d. This method frees the list's contents
+ */
+void sdp_list_free(sdp_list_t *list, sdp_free_func_t f)
+{
+       sdp_list_t *next;
+       while (list) {
+               next = list->next;
+               if (f)
+                       f(list->data);
+               free(list);
+               list = next;
+       }
+}
+
+static inline int __find_port(sdp_data_t *seq, int proto)
+{
+       if (!seq || !seq->next)
+               return 0;
+
+       if (SDP_IS_UUID(seq->dtd) && sdp_uuid_to_proto(&seq->val.uuid) == proto) {
+               seq = seq->next;
+               switch (seq->dtd) {
+               case SDP_UINT8:
+                       return seq->val.uint8;
+               case SDP_UINT16:
+                       return seq->val.uint16;
+               }
+       }
+       return 0;
+}
+
+int sdp_get_proto_port(const sdp_list_t *list, int proto)
+{
+       if (proto != L2CAP_UUID && proto != RFCOMM_UUID) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       for (; list; list = list->next) {
+               sdp_list_t *p;
+               for (p = list->data; p; p = p->next) {
+                       sdp_data_t *seq = p->data;
+                       int port = __find_port(seq, proto);
+                       if (port)
+                               return port;
+               }
+       }
+       return 0;
+}
+
+sdp_data_t *sdp_get_proto_desc(sdp_list_t *list, int proto)
+{
+       for (; list; list = list->next) {
+               sdp_list_t *p;
+               for (p = list->data; p; p = p->next) {
+                       sdp_data_t *seq = p->data;
+                       if (SDP_IS_UUID(seq->dtd) &&
+                                       sdp_uuid_to_proto(&seq->val.uuid) == proto)
+                               return seq->next;
+               }
+       }
+       return NULL;
+}
+
+int sdp_get_access_protos(const sdp_record_t *rec, sdp_list_t **pap)
+{
+       sdp_data_t *pdlist, *curr;
+       sdp_list_t *ap = 0;
+
+       pdlist = sdp_data_get(rec, SDP_ATTR_PROTO_DESC_LIST);
+       if (pdlist == NULL) {
+               errno = ENODATA;
+               return -1;
+       }
+       SDPDBG("AP type : 0%x\n", pdlist->dtd);
+
+       for (; pdlist; pdlist = pdlist->next) {
+               sdp_list_t *pds = 0;
+               for (curr = pdlist->val.dataseq; curr; curr = curr->next)
+                       pds = sdp_list_append(pds, curr->val.dataseq);
+               ap = sdp_list_append(ap, pds);
+       }
+       *pap = ap;
+       return 0;
+}
+
+int sdp_get_add_access_protos(const sdp_record_t *rec, sdp_list_t **pap)
+{
+       sdp_data_t *pdlist, *curr;
+       sdp_list_t *ap = 0;
+
+       pdlist = sdp_data_get(rec, SDP_ATTR_ADD_PROTO_DESC_LIST);
+       if (pdlist == NULL) {
+               errno = ENODATA;
+               return -1;
+       }
+       SDPDBG("AP type : 0%x\n", pdlist->dtd);
+
+       pdlist = pdlist->val.dataseq;
+
+       for (; pdlist; pdlist = pdlist->next) {
+               sdp_list_t *pds = 0;
+               for (curr = pdlist->val.dataseq; curr; curr = curr->next)
+                       pds = sdp_list_append(pds, curr->val.dataseq);
+               ap = sdp_list_append(ap, pds);
+       }
+       *pap = ap;
+       return 0;
+}
+
+int sdp_get_uuidseq_attr(const sdp_record_t *rec, uint16_t attr,
+                                                       sdp_list_t **seqp)
+{
+       sdp_data_t *sdpdata = sdp_data_get(rec, attr);
+
+       *seqp = NULL;
+       if (sdpdata && sdpdata->dtd >= SDP_SEQ8 && sdpdata->dtd <= SDP_SEQ32) {
+               sdp_data_t *d;
+               for (d = sdpdata->val.dataseq; d; d = d->next) {
+                       uuid_t *u;
+                       if (d->dtd < SDP_UUID16 || d->dtd > SDP_UUID128) {
+                               errno = EINVAL;
+                               goto fail;
+                       }
+
+                       u = malloc(sizeof(uuid_t));
+                       if (!u)
+                               goto fail;
+
+                       memset(u, 0, sizeof(uuid_t));
+                       *u = d->val.uuid;
+                       *seqp = sdp_list_append(*seqp, u);
+               }
+               return 0;
+       }
+fail:
+       sdp_list_free(*seqp, free);
+       *seqp = NULL;
+       return -1;
+}
+
+int sdp_set_uuidseq_attr(sdp_record_t *rec, uint16_t aid, sdp_list_t *seq)
+{
+       int status = 0, i, len;
+       void **dtds, **values;
+       uint8_t uuid16 = SDP_UUID16;
+       uint8_t uuid32 = SDP_UUID32;
+       uint8_t uuid128 = SDP_UUID128;
+       sdp_list_t *p;
+
+       len = sdp_list_len(seq);
+       if (!seq || len == 0)
+               return -1;
+       dtds = malloc(len * sizeof(void *));
+       if (!dtds)
+               return -1;
+
+       values = malloc(len * sizeof(void *));
+       if (!values) {
+               free(dtds);
+               return -1;
+       }
+
+       for (p = seq, i = 0; i < len; i++, p = p->next) {
+               uuid_t *uuid = p->data;
+               if (uuid)
+                       switch (uuid->type) {
+                       case SDP_UUID16:
+                               dtds[i] = &uuid16;
+                               values[i] = &uuid->value.uuid16;
+                               break;
+                       case SDP_UUID32:
+                               dtds[i] = &uuid32;
+                               values[i] = &uuid->value.uuid32;
+                               break;
+                       case SDP_UUID128:
+                               dtds[i] = &uuid128;
+                               values[i] = &uuid->value.uuid128;
+                               break;
+                       default:
+                               status = -1;
+                               break;
+                       }
+               else {
+                       status = -1;
+                       break;
+               }
+       }
+       if (status == 0) {
+               sdp_data_t *data = sdp_seq_alloc(dtds, values, len);
+               sdp_attr_replace(rec, aid, data);
+               sdp_pattern_add_uuidseq(rec, seq);
+       }
+       free(dtds);
+       free(values);
+       return status;
+}
+
+int sdp_get_lang_attr(const sdp_record_t *rec, sdp_list_t **langSeq)
+{
+       sdp_lang_attr_t *lang;
+       sdp_data_t *sdpdata, *curr_data;
+
+       *langSeq = NULL;
+       sdpdata = sdp_data_get(rec, SDP_ATTR_LANG_BASE_ATTR_ID_LIST);
+       if (sdpdata == NULL) {
+               errno = ENODATA;
+               return -1;
+       }
+       curr_data = sdpdata->val.dataseq;
+       while (curr_data) {
+               sdp_data_t *pCode = curr_data;
+               sdp_data_t *pEncoding = pCode->next;
+               sdp_data_t *pOffset = pEncoding->next;
+               if (pEncoding && pOffset) {
+                       lang = malloc(sizeof(sdp_lang_attr_t));
+                       if (!lang) {
+                               sdp_list_free(*langSeq, free);
+                               *langSeq = NULL;
+                               return -1;
+                       }
+                       lang->code_ISO639 = pCode->val.uint16;
+                       lang->encoding = pEncoding->val.uint16;
+                       lang->base_offset = pOffset->val.uint16;
+                       SDPDBG("code_ISO639 :  0x%02x\n", lang->code_ISO639);
+                       SDPDBG("encoding :     0x%02x\n", lang->encoding);
+                       SDPDBG("base_offfset : 0x%02x\n", lang->base_offset);
+                       *langSeq = sdp_list_append(*langSeq, lang);
+               }
+               curr_data = pOffset->next;
+       }
+       return 0;
+}
+
+int sdp_get_profile_descs(const sdp_record_t *rec, sdp_list_t **profDescSeq)
+{
+       sdp_profile_desc_t *profDesc;
+       sdp_data_t *sdpdata, *seq;
+
+       *profDescSeq = NULL;
+       sdpdata = sdp_data_get(rec, SDP_ATTR_PFILE_DESC_LIST);
+       if (!sdpdata || !sdpdata->val.dataseq) {
+               errno = ENODATA;
+               return -1;
+       }
+       for (seq = sdpdata->val.dataseq; seq && seq->val.dataseq; seq = seq->next) {
+               uuid_t *uuid = NULL;
+               uint16_t version = 0x100;
+
+               if (SDP_IS_UUID(seq->dtd)) {
+                       uuid = &seq->val.uuid;
+               } else {
+                       sdp_data_t *puuid = seq->val.dataseq;
+                       sdp_data_t *pVnum = seq->val.dataseq->next;
+                       if (puuid && pVnum) {
+                               uuid = &puuid->val.uuid;
+                               version = pVnum->val.uint16;
+                       }
+               }
+
+               if (uuid != NULL) {
+                       profDesc = malloc(sizeof(sdp_profile_desc_t));
+                       if (!profDesc) {
+                               sdp_list_free(*profDescSeq, free);
+                               *profDescSeq = NULL;
+                               return -1;
+                       }
+                       profDesc->uuid = *uuid;
+                       profDesc->version = version;
+#ifdef SDP_DEBUG
+                       sdp_uuid_print(&profDesc->uuid);
+                       SDPDBG("Vnum : 0x%04x\n", profDesc->version);
+#endif
+                       *profDescSeq = sdp_list_append(*profDescSeq, profDesc);
+               }
+       }
+       return 0;
+}
+
+int sdp_get_server_ver(const sdp_record_t *rec, sdp_list_t **u16)
+{
+       sdp_data_t *d, *curr;
+
+       *u16 = NULL;
+       d = sdp_data_get(rec, SDP_ATTR_VERSION_NUM_LIST);
+       if (d == NULL) {
+               errno = ENODATA;
+               return -1;
+       }
+       for (curr = d->val.dataseq; curr; curr = curr->next)
+               *u16 = sdp_list_append(*u16, &curr->val.uint16);
+       return 0;
+}
+
+/* flexible extraction of basic attributes - Jean II */
+/* How do we expect caller to extract predefined data sequences? */
+int sdp_get_int_attr(const sdp_record_t *rec, uint16_t attrid, int *value)
+{
+       sdp_data_t *sdpdata = sdp_data_get(rec, attrid);
+
+       if (sdpdata)
+               /* Verify that it is what the caller expects */
+               if (sdpdata->dtd == SDP_BOOL || sdpdata->dtd == SDP_UINT8 ||
+               sdpdata->dtd == SDP_UINT16 || sdpdata->dtd == SDP_UINT32 ||
+               sdpdata->dtd == SDP_INT8 || sdpdata->dtd == SDP_INT16 ||
+               sdpdata->dtd == SDP_INT32) {
+                       *value = sdpdata->val.uint32;
+                       return 0;
+               }
+       errno = EINVAL;
+       return -1;
+}
+
+int sdp_get_string_attr(const sdp_record_t *rec, uint16_t attrid, char *value,
+                                                               int valuelen)
+{
+       sdp_data_t *sdpdata = sdp_data_get(rec, attrid);
+       if (sdpdata)
+               /* Verify that it is what the caller expects */
+               if (sdpdata->dtd == SDP_TEXT_STR8 ||
+                               sdpdata->dtd == SDP_TEXT_STR16 ||
+                               sdpdata->dtd == SDP_TEXT_STR32)
+                       if ((int) strlen(sdpdata->val.str) < valuelen) {
+                               strcpy(value, sdpdata->val.str);
+                               return 0;
+                       }
+       errno = EINVAL;
+       return -1;
+}
+
+#define get_basic_attr(attrID, pAttrValue, fieldName)          \
+       sdp_data_t *data = sdp_data_get(rec, attrID);           \
+       if (data) {                                             \
+               *pAttrValue = data->val.fieldName;              \
+               return 0;                                       \
+       }                                                       \
+       errno = EINVAL;                                         \
+       return -1;
+
+int sdp_get_service_id(const sdp_record_t *rec, uuid_t *uuid)
+{
+       get_basic_attr(SDP_ATTR_SERVICE_ID, uuid, uuid);
+}
+
+int sdp_get_group_id(const sdp_record_t *rec, uuid_t *uuid)
+{
+       get_basic_attr(SDP_ATTR_GROUP_ID, uuid, uuid);
+}
+
+int sdp_get_record_state(const sdp_record_t *rec, uint32_t *svcRecState)
+{
+       get_basic_attr(SDP_ATTR_RECORD_STATE, svcRecState, uint32);
+}
+
+int sdp_get_service_avail(const sdp_record_t *rec, uint8_t *svcAvail)
+{
+       get_basic_attr(SDP_ATTR_SERVICE_AVAILABILITY, svcAvail, uint8);
+}
+
+int sdp_get_service_ttl(const sdp_record_t *rec, uint32_t *svcTTLInfo)
+{
+       get_basic_attr(SDP_ATTR_SVCINFO_TTL, svcTTLInfo, uint32);
+}
+
+int sdp_get_database_state(const sdp_record_t *rec, uint32_t *svcDBState)
+{
+       get_basic_attr(SDP_ATTR_SVCDB_STATE, svcDBState, uint32);
+}
+
+/*
+ * NOTE that none of the setXXX() functions below will
+ * actually update the SDP server, unless the
+ * {register, update}sdp_record_t() function is invoked.
+ */
+
+int sdp_attr_add_new(sdp_record_t *rec, uint16_t attr, uint8_t dtd,
+                                                       const void *value)
+{
+       sdp_data_t *d = sdp_data_alloc(dtd, value);
+       if (d) {
+               sdp_attr_replace(rec, attr, d);
+               return 0;
+       }
+       return -1;
+}
+
+static int sdp_attr_add_new_with_length(sdp_record_t *rec,
+       uint16_t attr, uint8_t dtd, const void *value, uint32_t len)
+{
+       sdp_data_t *d;
+
+       d = sdp_data_alloc_with_length(dtd, value, len);
+       if (!d)
+               return -1;
+
+       sdp_attr_replace(rec, attr, d);
+
+       return 0;
+}
+
+/*
+ * Set the information attributes of the service
+ * pointed to by rec. The attributes are
+ * service name, description and provider name
+ */
+void sdp_set_info_attr(sdp_record_t *rec, const char *name, const char *prov,
+                                                       const char *desc)
+{
+       if (name)
+               sdp_attr_add_new(rec, SDP_ATTR_SVCNAME_PRIMARY,
+                                                       SDP_TEXT_STR8, name);
+       if (prov)
+               sdp_attr_add_new(rec, SDP_ATTR_PROVNAME_PRIMARY,
+                                                       SDP_TEXT_STR8, prov);
+       if (desc)
+               sdp_attr_add_new(rec, SDP_ATTR_SVCDESC_PRIMARY,
+                                                       SDP_TEXT_STR8, desc);
+}
+
+static sdp_data_t *access_proto_to_dataseq(sdp_record_t *rec, sdp_list_t *proto)
+{
+       sdp_data_t *seq = NULL;
+       void *dtds[10], *values[10];
+       void **seqDTDs, **seqs;
+       int i, seqlen;
+       sdp_list_t *p;
+
+       seqlen = sdp_list_len(proto);
+       seqDTDs = malloc(seqlen * sizeof(void *));
+       if (!seqDTDs)
+               return NULL;
+
+       seqs = malloc(seqlen * sizeof(void *));
+       if (!seqs) {
+               free(seqDTDs);
+               return NULL;
+       }
+
+       for (i = 0, p = proto; p; p = p->next, i++) {
+               sdp_list_t *elt = p->data;
+               sdp_data_t *s;
+               uuid_t *uuid = NULL;
+               unsigned int pslen = 0;
+               for (; elt && pslen < ARRAY_SIZE(dtds); elt = elt->next, pslen++) {
+                       sdp_data_t *d = elt->data;
+                       dtds[pslen] = &d->dtd;
+                       switch (d->dtd) {
+                       case SDP_UUID16:
+                               uuid = (uuid_t *) d;
+                               values[pslen] = &uuid->value.uuid16;
+                               break;
+                       case SDP_UUID32:
+                               uuid = (uuid_t *) d;
+                               values[pslen] = &uuid->value.uuid32;
+                               break;
+                       case SDP_UUID128:
+                               uuid = (uuid_t *) d;
+                               values[pslen] = &uuid->value.uuid128;
+                               break;
+                       case SDP_UINT8:
+                               values[pslen] = &d->val.uint8;
+                               break;
+                       case SDP_UINT16:
+                               values[pslen] = &d->val.uint16;
+                               break;
+                       case SDP_SEQ8:
+                       case SDP_SEQ16:
+                       case SDP_SEQ32:
+                               values[pslen] = d;
+                               break;
+                       /* FIXME: more */
+                       }
+               }
+               s = sdp_seq_alloc(dtds, values, pslen);
+               if (s) {
+                       seqDTDs[i] = &s->dtd;
+                       seqs[i] = s;
+                       if (uuid)
+                               sdp_pattern_add_uuid(rec, uuid);
+               }
+       }
+       seq = sdp_seq_alloc(seqDTDs, seqs, seqlen);
+       free(seqDTDs);
+       free(seqs);
+       return seq;
+}
+
+/*
+ * sets the access protocols of the service specified
+ * to the value specified in "access_proto"
+ *
+ * Note that if there are alternate mechanisms by
+ * which the service is accessed, then they should
+ * be specified as sequences
+ *
+ * Using a value of NULL for accessProtocols has
+ * effect of removing this attribute (if previously set)
+ *
+ * This function replaces the existing sdp_access_proto_t
+ * structure (if any) with the new one specified.
+ *
+ * returns 0 if successful or -1 if there is a failure.
+ */
+int sdp_set_access_protos(sdp_record_t *rec, const sdp_list_t *ap)
+{
+       const sdp_list_t *p;
+       sdp_data_t *protos = NULL;
+
+       for (p = ap; p; p = p->next) {
+               sdp_data_t *seq = access_proto_to_dataseq(rec, p->data);
+               protos = sdp_seq_append(protos, seq);
+       }
+
+       sdp_attr_add(rec, SDP_ATTR_PROTO_DESC_LIST, protos);
+
+       return 0;
+}
+
+int sdp_set_add_access_protos(sdp_record_t *rec, const sdp_list_t *ap)
+{
+       const sdp_list_t *p;
+       sdp_data_t *protos = NULL;
+
+       for (p = ap; p; p = p->next) {
+               sdp_data_t *seq = access_proto_to_dataseq(rec, p->data);
+               protos = sdp_seq_append(protos, seq);
+       }
+
+       sdp_attr_add(rec, SDP_ATTR_ADD_PROTO_DESC_LIST,
+                       protos ? sdp_data_alloc(SDP_SEQ8, protos) : NULL);
+
+       return 0;
+}
+
+/*
+ * set the "LanguageBase" attributes of the service record
+ * record to the value specified in "langAttrList".
+ *
+ * "langAttrList" is a linked list of "sdp_lang_attr_t"
+ * objects, one for each language in which user visible
+ * attributes are present in the service record.
+ *
+ * Using a value of NULL for langAttrList has
+ * effect of removing this attribute (if previously set)
+ *
+ * This function replaces the exisiting sdp_lang_attr_t
+ * structure (if any) with the new one specified.
+ *
+ * returns 0 if successful or -1 if there is a failure.
+ */
+int sdp_set_lang_attr(sdp_record_t *rec, const sdp_list_t *seq)
+{
+       uint8_t uint16 = SDP_UINT16;
+       int status = 0, i = 0, seqlen = sdp_list_len(seq);
+       void **dtds, **values;
+       const sdp_list_t *p;
+
+       dtds = malloc(3 * seqlen * sizeof(void *));
+       if (!dtds)
+               return -1;
+
+       values = malloc(3 * seqlen * sizeof(void *));
+       if (!values) {
+               free(dtds);
+               return -1;
+       }
+
+       for (p = seq; p; p = p->next) {
+               sdp_lang_attr_t *lang = p->data;
+               if (!lang) {
+                       status = -1;
+                       break;
+               }
+               dtds[i] = &uint16;
+               values[i] = &lang->code_ISO639;
+               i++;
+               dtds[i] = &uint16;
+               values[i] = &lang->encoding;
+               i++;
+               dtds[i] = &uint16;
+               values[i] = &lang->base_offset;
+               i++;
+       }
+       if (status == 0) {
+               sdp_data_t *seq = sdp_seq_alloc(dtds, values, 3 * seqlen);
+               sdp_attr_add(rec, SDP_ATTR_LANG_BASE_ATTR_ID_LIST, seq);
+       }
+       free(dtds);
+       free(values);
+       return status;
+}
+
+/*
+ * set the "ServiceID" attribute of the service.
+ *
+ * This is the UUID of the service.
+ *
+ * returns 0 if successful or -1 if there is a failure.
+ */
+void sdp_set_service_id(sdp_record_t *rec, uuid_t uuid)
+{
+       switch (uuid.type) {
+       case SDP_UUID16:
+               sdp_attr_add_new(rec, SDP_ATTR_SERVICE_ID, SDP_UUID16,
+                                                       &uuid.value.uuid16);
+               break;
+       case SDP_UUID32:
+               sdp_attr_add_new(rec, SDP_ATTR_SERVICE_ID, SDP_UUID32,
+                                                       &uuid.value.uuid32);
+               break;
+       case SDP_UUID128:
+               sdp_attr_add_new(rec, SDP_ATTR_SERVICE_ID, SDP_UUID128,
+                                                       &uuid.value.uuid128);
+               break;
+       }
+       sdp_pattern_add_uuid(rec, &uuid);
+}
+
+/*
+ * set the GroupID attribute of the service record defining a group.
+ *
+ * This is the UUID of the group.
+ *
+ * returns 0 if successful or -1 if there is a failure.
+ */
+void sdp_set_group_id(sdp_record_t *rec, uuid_t uuid)
+{
+       switch (uuid.type) {
+       case SDP_UUID16:
+               sdp_attr_add_new(rec, SDP_ATTR_GROUP_ID, SDP_UUID16,
+                                                       &uuid.value.uuid16);
+               break;
+       case SDP_UUID32:
+               sdp_attr_add_new(rec, SDP_ATTR_GROUP_ID, SDP_UUID32,
+                                                       &uuid.value.uuid32);
+               break;
+       case SDP_UUID128:
+               sdp_attr_add_new(rec, SDP_ATTR_GROUP_ID, SDP_UUID128,
+                                                       &uuid.value.uuid128);
+               break;
+       }
+       sdp_pattern_add_uuid(rec, &uuid);
+}
+
+/*
+ * set the ProfileDescriptorList attribute of the service record
+ * pointed to by record to the value specified in "profileDesc".
+ *
+ * Each element in the list is an object of type
+ * sdp_profile_desc_t which is a definition of the
+ * Bluetooth profile that this service conforms to.
+ *
+ * Using a value of NULL for profileDesc has
+ * effect of removing this attribute (if previously set)
+ *
+ * This function replaces the exisiting ProfileDescriptorList
+ * structure (if any) with the new one specified.
+ *
+ * returns 0 if successful or -1 if there is a failure.
+ */
+int sdp_set_profile_descs(sdp_record_t *rec, const sdp_list_t *profiles)
+{
+       int status = 0;
+       uint8_t uuid16 = SDP_UUID16;
+       uint8_t uuid32 = SDP_UUID32;
+       uint8_t uuid128 = SDP_UUID128;
+       uint8_t uint16 = SDP_UINT16;
+       int i = 0, seqlen = sdp_list_len(profiles);
+       void **seqDTDs, **seqs;
+       const sdp_list_t *p;
+
+       seqDTDs = malloc(seqlen * sizeof(void *));
+       if (!seqDTDs)
+               return -1;
+
+       seqs = malloc(seqlen * sizeof(void *));
+       if (!seqs) {
+               free(seqDTDs);
+               return -1;
+       }
+
+       for (p = profiles; p; p = p->next) {
+               sdp_data_t *seq;
+               void *dtds[2], *values[2];
+               sdp_profile_desc_t *profile = p->data;
+               if (!profile) {
+                       status = -1;
+                       break;
+               }
+               switch (profile->uuid.type) {
+               case SDP_UUID16:
+                       dtds[0] = &uuid16;
+                       values[0] = &profile->uuid.value.uuid16;
+                       break;
+               case SDP_UUID32:
+                       dtds[0] = &uuid32;
+                       values[0] = &profile->uuid.value.uuid32;
+                       break;
+               case SDP_UUID128:
+                       dtds[0] = &uuid128;
+                       values[0] = &profile->uuid.value.uuid128;
+                       break;
+               default:
+                       status = -1;
+                       break;
+               }
+               dtds[1] = &uint16;
+               values[1] = &profile->version;
+               seq = sdp_seq_alloc(dtds, values, 2);
+               if (seq) {
+                       seqDTDs[i] = &seq->dtd;
+                       seqs[i] = seq;
+                       sdp_pattern_add_uuid(rec, &profile->uuid);
+               }
+               i++;
+       }
+       if (status == 0) {
+               sdp_data_t *pAPSeq = sdp_seq_alloc(seqDTDs, seqs, seqlen);
+               sdp_attr_add(rec, SDP_ATTR_PFILE_DESC_LIST, pAPSeq);
+       }
+       free(seqDTDs);
+       free(seqs);
+       return status;
+}
+
+/*
+ * sets various URL attributes of the service
+ * pointed to by record. The URL include
+ *
+ * client: a URL to the client's
+ *   platform specific (WinCE, PalmOS) executable
+ *   code that can be used to access this service.
+ *
+ * doc: a URL pointing to service documentation
+ *
+ * icon: a URL to an icon that can be used to represent
+ *   this service.
+ *
+ * Note that you need to pass NULL for any URLs
+ * that you don't want to set or remove
+ */
+void sdp_set_url_attr(sdp_record_t *rec, const char *client, const char *doc,
+                                                       const char *icon)
+{
+       sdp_attr_add_new(rec, SDP_ATTR_CLNT_EXEC_URL, SDP_URL_STR8, client);
+       sdp_attr_add_new(rec, SDP_ATTR_DOC_URL, SDP_URL_STR8, doc);
+       sdp_attr_add_new(rec, SDP_ATTR_ICON_URL, SDP_URL_STR8, icon);
+}
+
+uuid_t *sdp_uuid16_create(uuid_t *u, uint16_t val)
+{
+       memset(u, 0, sizeof(uuid_t));
+       u->type = SDP_UUID16;
+       u->value.uuid16 = val;
+       return u;
+}
+
+uuid_t *sdp_uuid32_create(uuid_t *u, uint32_t val)
+{
+       memset(u, 0, sizeof(uuid_t));
+       u->type = SDP_UUID32;
+       u->value.uuid32 = val;
+       return u;
+}
+
+uuid_t *sdp_uuid128_create(uuid_t *u, const void *val)
+{
+       memset(u, 0, sizeof(uuid_t));
+       u->type = SDP_UUID128;
+       memcpy(&u->value.uuid128, val, sizeof(uint128_t));
+       return u;
+}
+
+/*
+ * UUID comparison function
+ * returns 0 if uuidValue1 == uuidValue2 else -1
+ */
+int sdp_uuid_cmp(const void *p1, const void *p2)
+{
+       uuid_t *u1 = sdp_uuid_to_uuid128(p1);
+       uuid_t *u2 = sdp_uuid_to_uuid128(p2);
+       int ret;
+
+       ret = sdp_uuid128_cmp(u1, u2);
+
+       bt_free(u1);
+       bt_free(u2);
+
+       return ret;
+}
+
+/*
+ * UUID comparison function
+ * returns 0 if uuidValue1 == uuidValue2 else -1
+ */
+int sdp_uuid16_cmp(const void *p1, const void *p2)
+{
+       const uuid_t *u1 = p1;
+       const uuid_t *u2 = p2;
+       return memcmp(&u1->value.uuid16, &u2->value.uuid16, sizeof(uint16_t));
+}
+
+/*
+ * UUID comparison function
+ * returns 0 if uuidValue1 == uuidValue2 else -1
+ */
+int sdp_uuid128_cmp(const void *p1, const void *p2)
+{
+       const uuid_t *u1 = p1;
+       const uuid_t *u2 = p2;
+       return memcmp(&u1->value.uuid128, &u2->value.uuid128, sizeof(uint128_t));
+}
+
+/*
+ * 128 to 16 bit and 32 to 16 bit UUID conversion functions
+ * yet to be implemented. Note that the input is in NBO in
+ * both 32 and 128 bit UUIDs and conversion is needed
+ */
+void sdp_uuid16_to_uuid128(uuid_t *uuid128, const uuid_t *uuid16)
+{
+       /*
+        * We have a 16 bit value, which needs to be added to
+        * bytes 3 and 4 (at indices 2 and 3) of the Bluetooth base
+        */
+       unsigned short data1;
+
+       /* allocate a 128bit UUID and init to the Bluetooth base UUID */
+       uuid128->value.uuid128 = bluetooth_base_uuid;
+       uuid128->type = SDP_UUID128;
+
+       /* extract bytes 2 and 3 of 128bit BT base UUID */
+       memcpy(&data1, &bluetooth_base_uuid.data[2], 2);
+
+       /* add the given UUID (16 bits) */
+       data1 += htons(uuid16->value.uuid16);
+
+       /* set bytes 2 and 3 of the 128 bit value */
+       memcpy(&uuid128->value.uuid128.data[2], &data1, 2);
+}
+
+void sdp_uuid32_to_uuid128(uuid_t *uuid128, const uuid_t *uuid32)
+{
+       /*
+        * We have a 32 bit value, which needs to be added to
+        * bytes 1->4 (at indices 0 thru 3) of the Bluetooth base
+        */
+       unsigned int data0;
+
+       /* allocate a 128bit UUID and init to the Bluetooth base UUID */
+       uuid128->value.uuid128 = bluetooth_base_uuid;
+       uuid128->type = SDP_UUID128;
+
+       /* extract first 4 bytes */
+       memcpy(&data0, &bluetooth_base_uuid.data[0], 4);
+
+       /* add the given UUID (32bits) */
+       data0 += htonl(uuid32->value.uuid32);
+
+       /* set the 4 bytes of the 128 bit value */
+       memcpy(&uuid128->value.uuid128.data[0], &data0, 4);
+}
+
+uuid_t *sdp_uuid_to_uuid128(const uuid_t *uuid)
+{
+       uuid_t *uuid128 = bt_malloc(sizeof(uuid_t));
+
+       if (!uuid128)
+               return NULL;
+
+       memset(uuid128, 0, sizeof(uuid_t));
+       switch (uuid->type) {
+       case SDP_UUID128:
+               *uuid128 = *uuid;
+               break;
+       case SDP_UUID32:
+               sdp_uuid32_to_uuid128(uuid128, uuid);
+               break;
+       case SDP_UUID16:
+               sdp_uuid16_to_uuid128(uuid128, uuid);
+               break;
+       }
+       return uuid128;
+}
+
+/*
+ * converts a 128-bit uuid to a 16/32-bit one if possible
+ * returns true if uuid contains a 16/32-bit UUID at exit
+ */
+int sdp_uuid128_to_uuid(uuid_t *uuid)
+{
+       uint128_t *b = &bluetooth_base_uuid;
+       uint128_t *u = &uuid->value.uuid128;
+       uint32_t data;
+       unsigned int i;
+
+       if (uuid->type != SDP_UUID128)
+               return 1;
+
+       for (i = 4; i < sizeof(b->data); i++)
+               if (b->data[i] != u->data[i])
+                       return 0;
+
+       memcpy(&data, u->data, 4);
+       data = htonl(data);
+       if (data <= 0xffff) {
+               uuid->type = SDP_UUID16;
+               uuid->value.uuid16 = (uint16_t) data;
+       } else {
+               uuid->type = SDP_UUID32;
+               uuid->value.uuid32 = data;
+       }
+       return 1;
+}
+
+/*
+ * convert a UUID to the 16-bit short-form
+ */
+int sdp_uuid_to_proto(uuid_t *uuid)
+{
+       uuid_t u = *uuid;
+       if (sdp_uuid128_to_uuid(&u)) {
+               switch (u.type) {
+               case SDP_UUID16:
+                       return u.value.uuid16;
+               case SDP_UUID32:
+                       return u.value.uuid32;
+               }
+       }
+       return 0;
+}
+
+/*
+ * This function appends data to the PDU buffer "dst" from source "src".
+ * The data length is also computed and set.
+ * Should the PDU length exceed 2^8, then sequence type is
+ * set accordingly and the data is memmove()'d.
+ */
+void sdp_append_to_buf(sdp_buf_t *dst, uint8_t *data, uint32_t len)
+{
+       uint8_t *p = dst->data;
+       uint8_t dtd = *p;
+
+       SDPDBG("Append src size: %d\n", len);
+       SDPDBG("Append dst size: %d\n", dst->data_size);
+       SDPDBG("Dst buffer size: %d\n", dst->buf_size);
+       if (dst->data_size == 0 && dtd == 0) {
+               /* create initial sequence */
+               *p = SDP_SEQ8;
+               p += sizeof(uint8_t);
+               dst->data_size += sizeof(uint8_t);
+               /* reserve space for sequence size */
+               p += sizeof(uint8_t);
+               dst->data_size += sizeof(uint8_t);
+       }
+
+       memcpy(dst->data + dst->data_size, data, len);
+       dst->data_size += len;
+
+       dtd = *(uint8_t *) dst->data;
+       if (dst->data_size > UCHAR_MAX && dtd == SDP_SEQ8) {
+               short offset = sizeof(uint8_t) + sizeof(uint8_t);
+               memmove(dst->data + offset + 1, dst->data + offset,
+                                               dst->data_size - offset);
+               p = dst->data;
+               *p = SDP_SEQ16;
+               p += sizeof(uint8_t);
+               dst->data_size += 1;
+       }
+       p = dst->data;
+       dtd = *(uint8_t *) p;
+       p += sizeof(uint8_t);
+       switch (dtd) {
+       case SDP_SEQ8:
+               *(uint8_t *) p = dst->data_size - sizeof(uint8_t) - sizeof(uint8_t);
+               break;
+       case SDP_SEQ16:
+               bt_put_unaligned(htons(dst->data_size - sizeof(uint8_t) - sizeof(uint16_t)), (uint16_t *) p);
+               break;
+       case SDP_SEQ32:
+               bt_put_unaligned(htonl(dst->data_size - sizeof(uint8_t) - sizeof(uint32_t)), (uint32_t *) p);
+               break;
+       }
+}
+
+void sdp_append_to_pdu(sdp_buf_t *pdu, sdp_data_t *d)
+{
+       sdp_buf_t append;
+
+       memset(&append, 0, sizeof(sdp_buf_t));
+       sdp_gen_buffer(&append, d);
+       append.data = malloc(append.buf_size);
+       if (!append.data)
+               return;
+
+       sdp_set_attrid(&append, d->attrId);
+       sdp_gen_pdu(&append, d);
+       sdp_append_to_buf(pdu, append.data, append.data_size);
+       free(append.data);
+}
+
+/*
+ * Registers an sdp record.
+ *
+ * It is incorrect to call this method on a record that
+ * has been already registered with the server.
+ *
+ * Returns zero on success, otherwise -1 (and sets errno).
+ */
+int sdp_device_record_register_binary(sdp_session_t *session, bdaddr_t *device, uint8_t *data, uint32_t size, uint8_t flags, uint32_t *handle)
+{
+       uint8_t *req, *rsp, *p;
+       uint32_t reqsize, rspsize;
+       sdp_pdu_hdr_t *reqhdr, *rsphdr;
+       int status;
+
+       SDPDBG("");
+
+       if (!session->local) {
+               errno = EREMOTE;
+               return -1;
+       }
+       req = malloc(SDP_REQ_BUFFER_SIZE);
+       rsp = malloc(SDP_RSP_BUFFER_SIZE);
+       if (req == NULL || rsp == NULL) {
+               status = -1;
+               errno = ENOMEM;
+               goto end;
+       }
+
+       reqhdr = (sdp_pdu_hdr_t *)req;
+       reqhdr->pdu_id = SDP_SVC_REGISTER_REQ;
+       reqhdr->tid    = htons(sdp_gen_tid(session));
+       reqsize = sizeof(sdp_pdu_hdr_t) + 1;
+       p = req + sizeof(sdp_pdu_hdr_t);
+
+       if (bacmp(device, BDADDR_ANY)) {
+               *p++ = flags | SDP_DEVICE_RECORD;
+               bacpy((bdaddr_t *) p, device);
+               p += sizeof(bdaddr_t);
+               reqsize += sizeof(bdaddr_t);
+       } else
+               *p++ = flags;
+
+       memcpy(p, data, size);
+       reqsize += size;
+       reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t));
+
+       status = sdp_send_req_w4_rsp(session, req, rsp, reqsize, &rspsize);
+       if (status < 0)
+               goto end;
+
+       if (rspsize < sizeof(sdp_pdu_hdr_t)) {
+               SDPERR("Unexpected end of packet");
+               errno = EPROTO;
+               status = -1;
+               goto end;
+       }
+
+       rsphdr = (sdp_pdu_hdr_t *) rsp;
+       p = rsp + sizeof(sdp_pdu_hdr_t);
+
+       if (rsphdr->pdu_id == SDP_ERROR_RSP) {
+               /* Invalid service record */
+               errno = EINVAL;
+               status = -1;
+       } else if (rsphdr->pdu_id != SDP_SVC_REGISTER_RSP) {
+               errno = EPROTO;
+               status = -1;
+       } else {
+               if (rspsize < sizeof(sdp_pdu_hdr_t) + sizeof(uint32_t)) {
+                       SDPERR("Unexpected end of packet");
+                       errno = EPROTO;
+                       status = -1;
+                       goto end;
+               }
+               if (handle)
+                       *handle  = ntohl(bt_get_unaligned((uint32_t *) p));
+       }
+
+end:
+       free(req);
+       free(rsp);
+
+       return status;
+}
+
+int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags)
+{
+       sdp_buf_t pdu;
+       uint32_t handle;
+       int err;
+
+       SDPDBG("");
+
+       if (rec->handle && rec->handle != 0xffffffff) {
+               uint32_t handle = rec->handle;
+               sdp_data_t *data = sdp_data_alloc(SDP_UINT32, &handle);
+               sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data);
+       }
+
+       if (sdp_gen_record_pdu(rec, &pdu) < 0) {
+               errno = ENOMEM;
+               return -1;
+       }
+
+       err = sdp_device_record_register_binary(session, device,
+                               pdu.data, pdu.data_size, flags, &handle);
+
+       free(pdu.data);
+
+       if (err == 0) {
+               sdp_data_t *data = sdp_data_alloc(SDP_UINT32, &handle);
+               rec->handle = handle;
+               sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data);
+       }
+
+       return err;
+}
+
+int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags)
+{
+       return sdp_device_record_register(session, BDADDR_ANY, rec, flags);
+}
+
+/*
+ * unregister a service record
+ */
+int sdp_device_record_unregister_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle)
+{
+       uint8_t *reqbuf, *rspbuf, *p;
+       uint32_t reqsize = 0, rspsize = 0;
+       sdp_pdu_hdr_t *reqhdr, *rsphdr;
+       int status;
+
+       SDPDBG("");
+
+       if (handle == SDP_SERVER_RECORD_HANDLE) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!session->local) {
+               errno = EREMOTE;
+               return -1;
+       }
+
+       reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+       rspbuf = malloc(SDP_RSP_BUFFER_SIZE);
+       if (!reqbuf || !rspbuf) {
+               errno = ENOMEM;
+               status = -1;
+               goto end;
+       }
+       reqhdr = (sdp_pdu_hdr_t *) reqbuf;
+       reqhdr->pdu_id = SDP_SVC_REMOVE_REQ;
+       reqhdr->tid    = htons(sdp_gen_tid(session));
+
+       p = reqbuf + sizeof(sdp_pdu_hdr_t);
+       reqsize = sizeof(sdp_pdu_hdr_t);
+       bt_put_unaligned(htonl(handle), (uint32_t *) p);
+       reqsize += sizeof(uint32_t);
+
+       reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t));
+       status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize);
+       if (status < 0)
+               goto end;
+
+       if (rspsize < sizeof(sdp_pdu_hdr_t) + sizeof(uint16_t)) {
+               SDPERR("Unexpected end of packet");
+               errno = EPROTO;
+               status = -1;
+               goto end;
+       }
+
+       rsphdr = (sdp_pdu_hdr_t *) rspbuf;
+       p = rspbuf + sizeof(sdp_pdu_hdr_t);
+       status = bt_get_unaligned((uint16_t *) p);
+
+       if (rsphdr->pdu_id == SDP_ERROR_RSP) {
+               /* For this case the status always is invalid record handle */
+               errno = EINVAL;
+               status = -1;
+       } else if (rsphdr->pdu_id != SDP_SVC_REMOVE_RSP) {
+               errno = EPROTO;
+               status = -1;
+       }
+end:
+       free(reqbuf);
+       free(rspbuf);
+
+       return status;
+}
+
+int sdp_device_record_unregister(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec)
+{
+       int err;
+
+       err = sdp_device_record_unregister_binary(session, device, rec->handle);
+       if (err == 0)
+               sdp_record_free(rec);
+
+       return err;
+}
+
+int sdp_record_unregister(sdp_session_t *session, sdp_record_t *rec)
+{
+       return sdp_device_record_unregister(session, BDADDR_ANY, rec);
+}
+
+/*
+ * modify an existing service record
+ */
+int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle, uint8_t *data, uint32_t size)
+{
+       return -1;
+}
+
+int sdp_device_record_update(sdp_session_t *session, bdaddr_t *device, const sdp_record_t *rec)
+{
+       uint8_t *reqbuf, *rspbuf, *p;
+       uint32_t reqsize, rspsize;
+       sdp_pdu_hdr_t *reqhdr, *rsphdr;
+       uint32_t handle;
+       sdp_buf_t pdu;
+       int status;
+
+       SDPDBG("");
+
+       handle = rec->handle;
+
+       if (handle == SDP_SERVER_RECORD_HANDLE) {
+               errno = EINVAL;
+               return -1;
+       }
+       if (!session->local) {
+               errno = EREMOTE;
+               return -1;
+       }
+       reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+       rspbuf = malloc(SDP_RSP_BUFFER_SIZE);
+       if (!reqbuf || !rspbuf) {
+               errno = ENOMEM;
+               status = -1;
+               goto end;
+       }
+       reqhdr = (sdp_pdu_hdr_t *) reqbuf;
+       reqhdr->pdu_id = SDP_SVC_UPDATE_REQ;
+       reqhdr->tid    = htons(sdp_gen_tid(session));
+
+       p = reqbuf + sizeof(sdp_pdu_hdr_t);
+       reqsize = sizeof(sdp_pdu_hdr_t);
+
+       bt_put_unaligned(htonl(handle), (uint32_t *) p);
+       reqsize += sizeof(uint32_t);
+       p += sizeof(uint32_t);
+
+       if (sdp_gen_record_pdu(rec, &pdu) < 0) {
+               errno = ENOMEM;
+               status = -1;
+               goto end;
+       }
+       memcpy(p, pdu.data, pdu.data_size);
+       reqsize += pdu.data_size;
+       free(pdu.data);
+
+       reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t));
+       status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize);
+       if (status < 0)
+               goto end;
+
+       if (rspsize < sizeof(sdp_pdu_hdr_t) + sizeof(uint16_t)) {
+               SDPERR("Unexpected end of packet");
+               errno = EPROTO;
+               status = -1;
+               goto end;
+       }
+
+       SDPDBG("Send req status : %d\n", status);
+
+       rsphdr = (sdp_pdu_hdr_t *) rspbuf;
+       p = rspbuf + sizeof(sdp_pdu_hdr_t);
+       status = bt_get_unaligned((uint16_t *) p);
+
+       if (rsphdr->pdu_id == SDP_ERROR_RSP) {
+               /* The status can be invalid sintax or invalid record handle */
+               errno = EINVAL;
+               status = -1;
+       } else if (rsphdr->pdu_id != SDP_SVC_UPDATE_RSP) {
+               errno = EPROTO;
+               status = -1;
+       }
+end:
+       free(reqbuf);
+       free(rspbuf);
+       return status;
+}
+
+int sdp_record_update(sdp_session_t *session, const sdp_record_t *rec)
+{
+       return sdp_device_record_update(session, BDADDR_ANY, rec);
+}
+
+sdp_record_t *sdp_record_alloc()
+{
+       sdp_record_t *rec = malloc(sizeof(sdp_record_t));
+
+       if (!rec)
+               return NULL;
+
+       memset(rec, 0, sizeof(sdp_record_t));
+       rec->handle = 0xffffffff;
+       return rec;
+}
+
+/*
+ * Free the contents of a service record
+ */
+void sdp_record_free(sdp_record_t *rec)
+{
+       sdp_list_free(rec->attrlist, (sdp_free_func_t) sdp_data_free);
+       sdp_list_free(rec->pattern, free);
+       free(rec);
+}
+
+void sdp_pattern_add_uuid(sdp_record_t *rec, uuid_t *uuid)
+{
+       uuid_t *uuid128 = sdp_uuid_to_uuid128(uuid);
+
+       SDPDBG("Elements in target pattern : %d\n", sdp_list_len(rec->pattern));
+       SDPDBG("Trying to add : 0x%lx\n", (unsigned long) uuid128);
+
+       if (sdp_list_find(rec->pattern, uuid128, sdp_uuid128_cmp) == NULL)
+               rec->pattern = sdp_list_insert_sorted(rec->pattern, uuid128, sdp_uuid128_cmp);
+       else
+               bt_free(uuid128);
+
+       SDPDBG("Elements in target pattern : %d\n", sdp_list_len(rec->pattern));
+}
+
+void sdp_pattern_add_uuidseq(sdp_record_t *rec, sdp_list_t *seq)
+{
+       for (; seq; seq = seq->next) {
+               uuid_t *uuid = (uuid_t *)seq->data;
+               sdp_pattern_add_uuid(rec, uuid);
+       }
+}
+
+/*
+ * Extract a sequence of service record handles from a PDU buffer
+ * and add the entries to a sdp_list_t. Note that the service record
+ * handles are not in "data element sequence" form, but just like
+ * an array of service handles
+ */
+static void extract_record_handle_seq(uint8_t *pdu, int bufsize, sdp_list_t **seq, int count, unsigned int *scanned)
+{
+       sdp_list_t *pSeq = *seq;
+       uint8_t *pdata = pdu;
+       int n;
+
+       for (n = 0; n < count; n++) {
+               uint32_t *pSvcRec;
+               if (bufsize < (int) sizeof(uint32_t)) {
+                       SDPERR("Unexpected end of packet");
+                       break;
+               }
+               pSvcRec = malloc(sizeof(uint32_t));
+               if (!pSvcRec)
+                       break;
+               *pSvcRec = ntohl(bt_get_unaligned((uint32_t *) pdata));
+               pSeq = sdp_list_append(pSeq, pSvcRec);
+               pdata += sizeof(uint32_t);
+               *scanned += sizeof(uint32_t);
+               bufsize -= sizeof(uint32_t);
+       }
+       *seq = pSeq;
+}
+/*
+ * Generate the attribute sequence pdu form
+ * from sdp_list_t elements. Return length of attr seq
+ */
+static int gen_dataseq_pdu(uint8_t *dst, const sdp_list_t *seq, uint8_t dtd)
+{
+       sdp_data_t *dataseq;
+       void **types, **values;
+       sdp_buf_t buf;
+       int i, seqlen = sdp_list_len(seq);
+
+       // Fill up the value and the dtd arrays
+       SDPDBG("");
+
+       SDPDBG("Seq length : %d\n", seqlen);
+
+       types = malloc(seqlen * sizeof(void *));
+       if (!types)
+               return -ENOMEM;
+
+       values = malloc(seqlen * sizeof(void *));
+       if (!values) {
+               free(types);
+               return -ENOMEM;
+       }
+
+       for (i = 0; i < seqlen; i++) {
+               void *data = seq->data;
+               types[i] = &dtd;
+               if (SDP_IS_UUID(dtd))
+                       data = &((uuid_t *)data)->value;
+               values[i] = data;
+               seq = seq->next;
+       }
+
+       dataseq = sdp_seq_alloc(types, values, seqlen);
+       if (!dataseq) {
+               free(types);
+               free(values);
+               return -ENOMEM;
+       }
+
+       memset(&buf, 0, sizeof(sdp_buf_t));
+       sdp_gen_buffer(&buf, dataseq);
+       buf.data = malloc(buf.buf_size);
+
+       if (!buf.data) {
+               sdp_data_free(dataseq);
+               free(types);
+               free(values);
+               return -ENOMEM;
+       }
+
+       SDPDBG("Data Seq : 0x%p\n", seq);
+       seqlen = sdp_gen_pdu(&buf, dataseq);
+       SDPDBG("Copying : %d\n", buf.data_size);
+       memcpy(dst, buf.data, buf.data_size);
+
+       sdp_data_free(dataseq);
+
+       free(types);
+       free(values);
+       free(buf.data);
+       return seqlen;
+}
+
+static int gen_searchseq_pdu(uint8_t *dst, const sdp_list_t *seq)
+{
+       uuid_t *uuid = seq->data;
+       return gen_dataseq_pdu(dst, seq, uuid->type);
+}
+
+static int gen_attridseq_pdu(uint8_t *dst, const sdp_list_t *seq, uint8_t dataType)
+{
+       return gen_dataseq_pdu(dst, seq, dataType);
+}
+
+typedef struct {
+       uint8_t length;
+       unsigned char data[16];
+} __attribute__ ((packed)) sdp_cstate_t;
+
+static int copy_cstate(uint8_t *pdata, int pdata_len, const sdp_cstate_t *cstate)
+{
+       if (cstate) {
+               uint8_t len = cstate->length;
+               if (len >= pdata_len) {
+                       SDPERR("Continuation state size exceeds internal buffer");
+                       len = pdata_len - 1;
+               }
+               *pdata++ = len;
+               memcpy(pdata, cstate->data, len);
+               return len + 1;
+       }
+       *pdata = 0;
+       return 1;
+}
+
+/*
+ * This is a service search request.
+ *
+ * INPUT :
+ *
+ *   sdp_list_t *search
+ *     Singly linked list containing elements of the search
+ *     pattern. Each entry in the list is a UUID (DataTypeSDP_UUID16)
+ *     of the service to be searched
+ *
+ *   uint16_t max_rec_num
+ *      A 16 bit integer which tells the service, the maximum
+ *      entries that the client can handle in the response. The
+ *      server is obliged not to return > max_rec_num entries
+ *
+ * OUTPUT :
+ *
+ *   int return value
+ *     0:
+ *       The request completed successfully. This does not
+ *       mean the requested services were found
+ *     -1:
+ *       On any failure and sets errno
+ *
+ *   sdp_list_t **rsp_list
+ *     This variable is set on a successful return if there are
+ *     non-zero service handles. It is a singly linked list of
+ *     service record handles (uint16_t)
+ */
+int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search,
+                       uint16_t max_rec_num, sdp_list_t **rsp)
+{
+       int status = 0;
+       uint32_t reqsize = 0, _reqsize;
+       uint32_t rspsize = 0, rsplen;
+       int seqlen = 0;
+       int total_rec_count, rec_count;
+       unsigned scanned, pdata_len;
+       uint8_t *pdata, *_pdata;
+       uint8_t *reqbuf, *rspbuf;
+       sdp_pdu_hdr_t *reqhdr, *rsphdr;
+       sdp_cstate_t *cstate = NULL;
+
+       reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+       rspbuf = malloc(SDP_RSP_BUFFER_SIZE);
+       if (!reqbuf || !rspbuf) {
+               errno = ENOMEM;
+               status = -1;
+               goto end;
+       }
+       reqhdr = (sdp_pdu_hdr_t *) reqbuf;
+       reqhdr->pdu_id = SDP_SVC_SEARCH_REQ;
+       pdata = reqbuf + sizeof(sdp_pdu_hdr_t);
+       reqsize = sizeof(sdp_pdu_hdr_t);
+
+       // add service class IDs for search
+       seqlen = gen_searchseq_pdu(pdata, search);
+
+       SDPDBG("Data seq added : %d\n", seqlen);
+
+       // set the length and increment the pointer
+       reqsize += seqlen;
+       pdata += seqlen;
+
+       // specify the maximum svc rec count that client expects
+       bt_put_unaligned(htons(max_rec_num), (uint16_t *) pdata);
+       reqsize += sizeof(uint16_t);
+       pdata += sizeof(uint16_t);
+
+       _reqsize = reqsize;
+       _pdata   = pdata;
+       *rsp = NULL;
+
+       do {
+               // Add continuation state or NULL (first time)
+               reqsize = _reqsize + copy_cstate(_pdata,
+                                       SDP_REQ_BUFFER_SIZE - _reqsize, cstate);
+
+               // Set the request header's param length
+               reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t));
+
+               reqhdr->tid  = htons(sdp_gen_tid(session));
+               /*
+                * Send the request, wait for response and if
+                * no error, set the appropriate values and return
+                */
+               status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize);
+               if (status < 0)
+                       goto end;
+
+               if (rspsize < sizeof(sdp_pdu_hdr_t)) {
+                       SDPERR("Unexpected end of packet");
+                       status = -1;
+                       goto end;
+               }
+
+               rsphdr = (sdp_pdu_hdr_t *) rspbuf;
+               rsplen = ntohs(rsphdr->plen);
+
+               if (rsphdr->pdu_id == SDP_ERROR_RSP) {
+                       SDPDBG("Status : 0x%x\n", rsphdr->pdu_id);
+                       status = -1;
+                       goto end;
+               }
+               scanned = 0;
+               pdata = rspbuf + sizeof(sdp_pdu_hdr_t);
+               pdata_len = rspsize - sizeof(sdp_pdu_hdr_t);
+
+               if (pdata_len < sizeof(uint16_t) + sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       status = -1;
+                       goto end;
+               }
+
+               // net service record match count
+               total_rec_count = ntohs(bt_get_unaligned((uint16_t *) pdata));
+               pdata += sizeof(uint16_t);
+               scanned += sizeof(uint16_t);
+               pdata_len -= sizeof(uint16_t);
+               rec_count = ntohs(bt_get_unaligned((uint16_t *) pdata));
+               pdata += sizeof(uint16_t);
+               scanned += sizeof(uint16_t);
+               pdata_len -= sizeof(uint16_t);
+
+               SDPDBG("Total svc count: %d\n", total_rec_count);
+               SDPDBG("Current svc count: %d\n", rec_count);
+               SDPDBG("ResponseLength: %d\n", rsplen);
+
+               if (!rec_count) {
+                       status = -1;
+                       goto end;
+               }
+               extract_record_handle_seq(pdata, pdata_len, rsp, rec_count, &scanned);
+               SDPDBG("BytesScanned : %d\n", scanned);
+
+               if (rsplen > scanned) {
+                       uint8_t cstate_len;
+
+                       if (rspsize < sizeof(sdp_pdu_hdr_t) + scanned + sizeof(uint8_t)) {
+                               SDPERR("Unexpected end of packet: continuation state data missing");
+                               status = -1;
+                               goto end;
+                       }
+
+                       pdata = rspbuf + sizeof(sdp_pdu_hdr_t) + scanned;
+                       cstate_len = *(uint8_t *) pdata;
+                       if (cstate_len > 0) {
+                               cstate = (sdp_cstate_t *)pdata;
+                               SDPDBG("Cont state length: %d\n", cstate_len);
+                       } else
+                               cstate = NULL;
+               }
+       } while (cstate);
+
+end:
+       free(reqbuf);
+       free(rspbuf);
+
+       return status;
+}
+
+/*
+ * This is a service attribute request.
+ *
+ * INPUT :
+ *
+ *   uint32_t handle
+ *     The handle of the service for which the attribute(s) are
+ *     requested
+ *
+ *   sdp_attrreq_type_t reqtype
+ *     Attribute identifiers are 16 bit unsigned integers specified
+ *     in one of 2 ways described below :
+ *     SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers
+ *        They are the actual attribute identifiers in ascending order
+ *
+ *     SDP_ATTR_REQ_RANGE - 32bit identifier range
+ *        The high-order 16bits is the start of range
+ *        the low-order 16bits are the end of range
+ *        0x0000 to 0xFFFF gets all attributes
+ *
+ *   sdp_list_t *attrid
+ *     Singly linked list containing attribute identifiers desired.
+ *     Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL)
+ *     or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE)
+ *
+ * OUTPUT :
+ *   return sdp_record_t *
+ *     0:
+ *       On any error and sets errno
+ *     !0:
+ *      The service record
+ */
+sdp_record_t *sdp_service_attr_req(sdp_session_t *session, uint32_t handle,
+                       sdp_attrreq_type_t reqtype, const sdp_list_t *attrids)
+{
+       uint32_t reqsize = 0, _reqsize;
+       uint32_t rspsize = 0, rsp_count;
+       int attr_list_len = 0;
+       int seqlen = 0;
+       unsigned int pdata_len;
+       uint8_t *pdata, *_pdata;
+       uint8_t *reqbuf, *rspbuf;
+       sdp_pdu_hdr_t *reqhdr, *rsphdr;
+       sdp_cstate_t *cstate = NULL;
+       uint8_t cstate_len = 0;
+       sdp_buf_t rsp_concat_buf;
+       sdp_record_t *rec = 0;
+
+       if (reqtype != SDP_ATTR_REQ_INDIVIDUAL && reqtype != SDP_ATTR_REQ_RANGE) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       memset(&rsp_concat_buf, 0, sizeof(sdp_buf_t));
+
+       reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+       rspbuf = malloc(SDP_RSP_BUFFER_SIZE);
+       if (!reqbuf || !rspbuf) {
+               errno = ENOMEM;
+               goto end;
+       }
+       reqhdr = (sdp_pdu_hdr_t *) reqbuf;
+       reqhdr->pdu_id = SDP_SVC_ATTR_REQ;
+
+       pdata = reqbuf + sizeof(sdp_pdu_hdr_t);
+       reqsize = sizeof(sdp_pdu_hdr_t);
+
+       // add the service record handle
+       bt_put_unaligned(htonl(handle), (uint32_t *) pdata);
+       reqsize += sizeof(uint32_t);
+       pdata += sizeof(uint32_t);
+
+       // specify the response limit
+       bt_put_unaligned(htons(65535), (uint16_t *) pdata);
+       reqsize += sizeof(uint16_t);
+       pdata += sizeof(uint16_t);
+
+       // get attr seq PDU form
+       seqlen = gen_attridseq_pdu(pdata, attrids,
+               reqtype == SDP_ATTR_REQ_INDIVIDUAL? SDP_UINT16 : SDP_UINT32);
+       if (seqlen == -1) {
+               errno = EINVAL;
+               goto end;
+       }
+       pdata += seqlen;
+       reqsize += seqlen;
+       SDPDBG("Attr list length : %d\n", seqlen);
+
+       // save before Continuation State
+       _pdata = pdata;
+       _reqsize = reqsize;
+
+       do {
+               int status;
+
+               // add NULL continuation state
+               reqsize = _reqsize + copy_cstate(_pdata,
+                                       SDP_REQ_BUFFER_SIZE - _reqsize, cstate);
+
+               // set the request header's param length
+               reqhdr->tid  = htons(sdp_gen_tid(session));
+               reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t));
+
+               status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize);
+               if (status < 0)
+                       goto end;
+
+               if (rspsize < sizeof(sdp_pdu_hdr_t)) {
+                       SDPERR("Unexpected end of packet");
+                       goto end;
+               }
+
+               rsphdr = (sdp_pdu_hdr_t *) rspbuf;
+               if (rsphdr->pdu_id == SDP_ERROR_RSP) {
+                       SDPDBG("PDU ID : 0x%x\n", rsphdr->pdu_id);
+                       goto end;
+               }
+               pdata = rspbuf + sizeof(sdp_pdu_hdr_t);
+               pdata_len = rspsize - sizeof(sdp_pdu_hdr_t);
+
+               if (pdata_len < sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       goto end;
+               }
+
+               rsp_count = ntohs(bt_get_unaligned((uint16_t *) pdata));
+               attr_list_len += rsp_count;
+               pdata += sizeof(uint16_t);
+               pdata_len -= sizeof(uint16_t);
+
+               // if continuation state set need to re-issue request before parsing
+               if (pdata_len < rsp_count + sizeof(uint8_t)) {
+                       SDPERR("Unexpected end of packet: continuation state data missing");
+                       goto end;
+               }
+               cstate_len = *(uint8_t *) (pdata + rsp_count);
+
+               SDPDBG("Response id : %d\n", rsphdr->pdu_id);
+               SDPDBG("Attrlist byte count : %d\n", rsp_count);
+               SDPDBG("sdp_cstate_t length : %d\n", cstate_len);
+
+               /*
+                * a split response: concatenate intermediate responses
+                * and the last one (which has cstate_len == 0)
+                */
+               if (cstate_len > 0 || rsp_concat_buf.data_size != 0) {
+                       uint8_t *targetPtr = NULL;
+
+                       cstate = cstate_len > 0 ? (sdp_cstate_t *) (pdata + rsp_count) : 0;
+
+                       // build concatenated response buffer
+                       rsp_concat_buf.data = realloc(rsp_concat_buf.data, rsp_concat_buf.data_size + rsp_count);
+                       rsp_concat_buf.buf_size = rsp_concat_buf.data_size + rsp_count;
+                       targetPtr = rsp_concat_buf.data + rsp_concat_buf.data_size;
+                       memcpy(targetPtr, pdata, rsp_count);
+                       rsp_concat_buf.data_size += rsp_count;
+               }
+       } while (cstate);
+
+       if (attr_list_len > 0) {
+               int scanned = 0;
+               if (rsp_concat_buf.data_size != 0) {
+                       pdata = rsp_concat_buf.data;
+                       pdata_len = rsp_concat_buf.data_size;
+               }
+               rec = sdp_extract_pdu(pdata, pdata_len, &scanned);
+       }
+
+end:
+       free(reqbuf);
+       free(rsp_concat_buf.data);
+       free(rspbuf);
+       return rec;
+}
+
+/*
+ * SDP transaction structure for asynchronous search
+ */
+struct sdp_transaction {
+       sdp_callback_t *cb;     /* called when the transaction finishes */
+       void *udata;            /* client user data */
+       uint8_t *reqbuf;        /* pointer to request PDU */
+       sdp_buf_t rsp_concat_buf;
+       uint32_t reqsize;       /* without cstate */
+       int err;                /* ZERO if success or the errno if failed */
+};
+
+/*
+ * Creates a new sdp session for asynchronous search
+ * INPUT:
+ *  int sk
+ *     non-blocking L2CAP socket
+ *
+ * RETURN:
+ *  sdp_session_t *
+ *  NULL - On memory allocation failure
+ */
+sdp_session_t *sdp_create(int sk, uint32_t flags)
+{
+       sdp_session_t *session;
+       struct sdp_transaction *t;
+
+       session = malloc(sizeof(sdp_session_t));
+       if (!session) {
+               errno = ENOMEM;
+               return NULL;
+       }
+       memset(session, 0, sizeof(*session));
+
+       session->flags = flags;
+       session->sock = sk;
+
+       t = malloc(sizeof(struct sdp_transaction));
+       if (!t) {
+               errno = ENOMEM;
+               free(session);
+               return NULL;
+       }
+       memset(t, 0, sizeof(*t));
+
+       session->priv = t;
+
+       return session;
+}
+
+/*
+ * Sets the callback function/user data used to notify the application
+ * that the asynchronous transaction finished. This function must be
+ * called before request an asynchronous search.
+ *
+ * INPUT:
+ *  sdp_session_t *session
+ *     Current sdp session to be handled
+ *  sdp_callback_t *cb
+ *      callback to be called when the transaction finishes
+ *  void *udata
+ *      user data passed to callback
+ * RETURN:
+ *      0 - Success
+ *     -1 - Failure
+ */
+int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata)
+{
+       struct sdp_transaction *t;
+
+       if (!session || !session->priv)
+               return -1;
+
+       t = session->priv;
+       t->cb = func;
+       t->udata = udata;
+
+       return 0;
+}
+
+/*
+ * This function starts an asynchronous service search request.
+ * The incomming and outgoing data are stored in the transaction structure
+ * buffers. When there is incomming data the sdp_process function must be
+ * called to get the data and handle the continuation state.
+ *
+ * INPUT :
+ *  sdp_session_t *session
+ *     Current sdp session to be handled
+ *
+ *   sdp_list_t *search
+ *     Singly linked list containing elements of the search
+ *     pattern. Each entry in the list is a UUID (DataTypeSDP_UUID16)
+ *     of the service to be searched
+ *
+ *   uint16_t max_rec_num
+ *      A 16 bit integer which tells the service, the maximum
+ *      entries that the client can handle in the response. The
+ *      server is obliged not to return > max_rec_num entries
+ *
+ * OUTPUT :
+ *
+ *   int return value
+ *     0  - if the request has been sent properly
+ *     -1 - On any failure and sets errno
+ */
+
+int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num)
+{
+       struct sdp_transaction *t;
+       sdp_pdu_hdr_t *reqhdr;
+       uint8_t *pdata;
+       int cstate_len, seqlen = 0;
+
+       if (!session || !session->priv)
+               return -1;
+
+       t = session->priv;
+
+       /* clean possible allocated buffer */
+       free(t->rsp_concat_buf.data);
+       memset(&t->rsp_concat_buf, 0, sizeof(sdp_buf_t));
+
+       if (!t->reqbuf) {
+               t->reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+               if (!t->reqbuf) {
+                       t->err = ENOMEM;
+                       goto end;
+               }
+       }
+       memset(t->reqbuf, 0, SDP_REQ_BUFFER_SIZE);
+
+       reqhdr = (sdp_pdu_hdr_t *) t->reqbuf;
+       reqhdr->tid = htons(sdp_gen_tid(session));
+       reqhdr->pdu_id = SDP_SVC_SEARCH_REQ;
+
+       // generate PDU
+       pdata = t->reqbuf + sizeof(sdp_pdu_hdr_t);
+       t->reqsize = sizeof(sdp_pdu_hdr_t);
+
+       // add service class IDs for search
+       seqlen = gen_searchseq_pdu(pdata, search);
+
+       SDPDBG("Data seq added : %d\n", seqlen);
+
+       // now set the length and increment the pointer
+       t->reqsize += seqlen;
+       pdata += seqlen;
+
+       bt_put_unaligned(htons(max_rec_num), (uint16_t *) pdata);
+       t->reqsize += sizeof(uint16_t);
+       pdata += sizeof(uint16_t);
+
+       // set the request header's param length
+       cstate_len = copy_cstate(pdata, SDP_REQ_BUFFER_SIZE - t->reqsize, NULL);
+       reqhdr->plen = htons((t->reqsize + cstate_len) - sizeof(sdp_pdu_hdr_t));
+
+       if (sdp_send_req(session, t->reqbuf, t->reqsize + cstate_len) < 0) {
+               SDPERR("Error sendind data:%s", strerror(errno));
+               t->err = errno;
+               goto end;
+       }
+
+       return 0;
+end:
+
+       free(t->reqbuf);
+       t->reqbuf = NULL;
+
+       return -1;
+}
+
+/*
+ * This function starts an asynchronous service attribute request.
+ * The incomming and outgoing data are stored in the transaction structure
+ * buffers. When there is incomming data the sdp_process function must be
+ * called to get the data and handle the continuation state.
+ *
+ * INPUT :
+ *  sdp_session_t *session
+ *     Current sdp session to be handled
+ *
+ *   uint32_t handle
+ *     The handle of the service for which the attribute(s) are
+ *     requested
+ *
+ *   sdp_attrreq_type_t reqtype
+ *     Attribute identifiers are 16 bit unsigned integers specified
+ *     in one of 2 ways described below :
+ *     SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers
+ *        They are the actual attribute identifiers in ascending order
+ *
+ *     SDP_ATTR_REQ_RANGE - 32bit identifier range
+ *        The high-order 16bits is the start of range
+ *        the low-order 16bits are the end of range
+ *        0x0000 to 0xFFFF gets all attributes
+ *
+ *   sdp_list_t *attrid_list
+ *     Singly linked list containing attribute identifiers desired.
+ *     Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL)
+ *     or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE)
+ *
+ * OUTPUT :
+ *   int return value
+ *      0 - if the request has been sent properly
+ *     -1 - On any failure and sets errno
+ */
+
+int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list)
+{
+       struct sdp_transaction *t;
+       sdp_pdu_hdr_t *reqhdr;
+       uint8_t *pdata;
+       int cstate_len, seqlen = 0;
+
+       if (!session || !session->priv)
+               return -1;
+
+       t = session->priv;
+
+       /* clean possible allocated buffer */
+       free(t->rsp_concat_buf.data);
+       memset(&t->rsp_concat_buf, 0, sizeof(sdp_buf_t));
+
+       if (!t->reqbuf) {
+               t->reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+               if (!t->reqbuf) {
+                       t->err = ENOMEM;
+                       goto end;
+               }
+       }
+       memset(t->reqbuf, 0, SDP_REQ_BUFFER_SIZE);
+
+       reqhdr = (sdp_pdu_hdr_t *) t->reqbuf;
+       reqhdr->tid = htons(sdp_gen_tid(session));
+       reqhdr->pdu_id = SDP_SVC_ATTR_REQ;
+
+       // generate PDU
+       pdata = t->reqbuf + sizeof(sdp_pdu_hdr_t);
+       t->reqsize = sizeof(sdp_pdu_hdr_t);
+
+       // add the service record handle
+       bt_put_unaligned(htonl(handle), (uint32_t *) pdata);
+       t->reqsize += sizeof(uint32_t);
+       pdata += sizeof(uint32_t);
+
+       // specify the response limit
+       bt_put_unaligned(htons(65535), (uint16_t *) pdata);
+       t->reqsize += sizeof(uint16_t);
+       pdata += sizeof(uint16_t);
+
+       // get attr seq PDU form
+       seqlen = gen_attridseq_pdu(pdata, attrid_list,
+                       reqtype == SDP_ATTR_REQ_INDIVIDUAL? SDP_UINT16 : SDP_UINT32);
+       if (seqlen == -1) {
+               t->err = EINVAL;
+               goto end;
+       }
+
+       // now set the length and increment the pointer
+       t->reqsize += seqlen;
+       pdata += seqlen;
+       SDPDBG("Attr list length : %d\n", seqlen);
+
+       // set the request header's param length
+       cstate_len = copy_cstate(pdata, SDP_REQ_BUFFER_SIZE - t->reqsize, NULL);
+       reqhdr->plen = htons((t->reqsize + cstate_len) - sizeof(sdp_pdu_hdr_t));
+
+       if (sdp_send_req(session, t->reqbuf, t->reqsize + cstate_len) < 0) {
+               SDPERR("Error sendind data:%s", strerror(errno));
+               t->err = errno;
+               goto end;
+       }
+
+       return 0;
+end:
+
+       free(t->reqbuf);
+       t->reqbuf = NULL;
+
+       return -1;
+}
+
+/*
+ * This function starts an asynchronous service search attributes.
+ * It is a service search request combined with attribute request. The incomming
+ * and outgoing data are stored in the transaction structure buffers. When there
+ * is incomming data the sdp_process function must be called to get the data
+ * and handle the continuation state.
+ *
+ * INPUT:
+ *  sdp_session_t *session
+ *     Current sdp session to be handled
+ *
+ *   sdp_list_t *search
+ *     Singly linked list containing elements of the search
+ *     pattern. Each entry in the list is a UUID(DataTypeSDP_UUID16)
+ *     of the service to be searched
+ *
+ *   AttributeSpecification attrSpec
+ *     Attribute identifiers are 16 bit unsigned integers specified
+ *     in one of 2 ways described below :
+ *     SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers
+ *        They are the actual attribute identifiers in ascending order
+ *
+ *     SDP_ATTR_REQ_RANGE - 32bit identifier range
+ *        The high-order 16bits is the start of range
+ *        the low-order 16bits are the end of range
+ *        0x0000 to 0xFFFF gets all attributes
+ *
+ *   sdp_list_t *attrid_list
+ *     Singly linked list containing attribute identifiers desired.
+ *     Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL)
+ *     or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE)
+ *
+
+ * RETURN:
+ *      0 - if the request has been sent properly
+ *     -1 - On any failure
+ */
+int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list)
+{
+       struct sdp_transaction *t;
+       sdp_pdu_hdr_t *reqhdr;
+       uint8_t *pdata;
+       int cstate_len, seqlen = 0;
+
+       if (!session || !session->priv)
+               return -1;
+
+       t = session->priv;
+
+       /* clean possible allocated buffer */
+       free(t->rsp_concat_buf.data);
+       memset(&t->rsp_concat_buf, 0, sizeof(sdp_buf_t));
+
+       if (!t->reqbuf) {
+               t->reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+               if (!t->reqbuf) {
+                       t->err = ENOMEM;
+                       goto end;
+               }
+       }
+       memset(t->reqbuf, 0, SDP_REQ_BUFFER_SIZE);
+
+       reqhdr = (sdp_pdu_hdr_t *) t->reqbuf;
+       reqhdr->tid = htons(sdp_gen_tid(session));
+       reqhdr->pdu_id = SDP_SVC_SEARCH_ATTR_REQ;
+
+       // generate PDU
+       pdata = t->reqbuf + sizeof(sdp_pdu_hdr_t);
+       t->reqsize = sizeof(sdp_pdu_hdr_t);
+
+       // add service class IDs for search
+       seqlen = gen_searchseq_pdu(pdata, search);
+
+       SDPDBG("Data seq added : %d\n", seqlen);
+
+       // now set the length and increment the pointer
+       t->reqsize += seqlen;
+       pdata += seqlen;
+
+       bt_put_unaligned(htons(SDP_MAX_ATTR_LEN), (uint16_t *) pdata);
+       t->reqsize += sizeof(uint16_t);
+       pdata += sizeof(uint16_t);
+
+       SDPDBG("Max attr byte count : %d\n", SDP_MAX_ATTR_LEN);
+
+       // get attr seq PDU form
+       seqlen = gen_attridseq_pdu(pdata, attrid_list,
+                       reqtype == SDP_ATTR_REQ_INDIVIDUAL ? SDP_UINT16 : SDP_UINT32);
+       if (seqlen == -1) {
+               t->err = EINVAL;
+               goto end;
+       }
+
+       pdata += seqlen;
+       SDPDBG("Attr list length : %d\n", seqlen);
+       t->reqsize += seqlen;
+
+       // set the request header's param length
+       cstate_len = copy_cstate(pdata, SDP_REQ_BUFFER_SIZE - t->reqsize, NULL);
+       reqhdr->plen = htons((t->reqsize + cstate_len) - sizeof(sdp_pdu_hdr_t));
+
+       if (sdp_send_req(session, t->reqbuf, t->reqsize + cstate_len) < 0) {
+               SDPERR("Error sendind data:%s", strerror(errno));
+               t->err = errno;
+               goto end;
+       }
+
+       return 0;
+end:
+
+       free(t->reqbuf);
+       t->reqbuf = NULL;
+
+       return -1;
+}
+
+/*
+ * Function used to get the error reason after sdp_callback_t function has been called
+ * and the status is 0xffff or if sdp_service_{search, attr, search_attr}_async returns -1.
+ * It indicates that an error NOT related to SDP_ErrorResponse happened. Get errno directly
+ * is not safe because multiple transactions can be triggered.
+ * This function must be used with asynchronous sdp functions only.
+ *
+ * INPUT:
+ *  sdp_session_t *session
+ *     Current sdp session to be handled
+ * RETURN:
+ *      0 = No error in the current transaction
+ *     -1 - if the session is invalid
+ *     positive value - the errno value
+ *
+ */
+int sdp_get_error(sdp_session_t *session)
+{
+       struct sdp_transaction *t;
+
+       if (!session || !session->priv) {
+               SDPERR("Invalid session");
+               return -1;
+       }
+
+       t = session->priv;
+
+       return t->err;
+}
+
+/*
+ * Receive the incomming SDP PDU. This function must be called when there is data
+ * available to be read. On continuation state, the original request (with a new
+ * transaction ID) and the continuation state data will be appended in the initial PDU.
+ * If an error happens or the transaction finishes the callback function will be called.
+ *
+ * INPUT:
+ *  sdp_session_t *session
+ *     Current sdp session to be handled
+ * RETURN:
+ *     0  - if the transaction is on continuation state
+ *     -1 - On any failure or the transaction finished
+ */
+int sdp_process(sdp_session_t *session)
+{
+       struct sdp_transaction *t;
+       sdp_pdu_hdr_t *reqhdr, *rsphdr;
+       sdp_cstate_t *pcstate;
+       uint8_t *pdata, *rspbuf, *targetPtr;
+       int rsp_count, err = -1;
+       size_t size = 0;
+       int n, plen;
+       uint16_t status = 0xffff;
+       uint8_t pdu_id = 0x00;
+
+       if (!session || !session->priv) {
+               SDPERR("Invalid session");
+               return -1;
+       }
+
+       rspbuf = malloc(SDP_RSP_BUFFER_SIZE);
+       if (!rspbuf) {
+               SDPERR("Response buffer alloc failure:%s (%d)",
+                               strerror(errno), errno);
+               return -1;
+       }
+
+       memset(rspbuf, 0, SDP_RSP_BUFFER_SIZE);
+
+       t = session->priv;
+       reqhdr = (sdp_pdu_hdr_t *)t->reqbuf;
+       rsphdr = (sdp_pdu_hdr_t *)rspbuf;
+
+       pdata = rspbuf + sizeof(sdp_pdu_hdr_t);
+
+       n = sdp_read_rsp(session, rspbuf, SDP_RSP_BUFFER_SIZE);
+       if (n < 0) {
+               SDPERR("Read response:%s (%d)", strerror(errno), errno);
+               t->err = errno;
+               goto end;
+       }
+
+       if (n == 0 || reqhdr->tid != rsphdr->tid ||
+               (n != (ntohs(rsphdr->plen) + (int) sizeof(sdp_pdu_hdr_t)))) {
+               t->err = EPROTO;
+               SDPERR("Protocol error.");
+               goto end;
+       }
+
+       pdu_id = rsphdr->pdu_id;
+       switch (rsphdr->pdu_id) {
+       uint8_t *ssr_pdata;
+       uint16_t tsrc, csrc;
+       case SDP_SVC_SEARCH_RSP:
+               /*
+                * TSRC: Total Service Record Count (2 bytes)
+                * CSRC: Current Service Record Count (2 bytes)
+                */
+               ssr_pdata = pdata;
+               tsrc = ntohs(bt_get_unaligned((uint16_t *) ssr_pdata));
+               ssr_pdata += sizeof(uint16_t);
+               csrc = ntohs(bt_get_unaligned((uint16_t *) ssr_pdata));
+
+               /* csrc should never be larger than tsrc */
+               if (csrc > tsrc) {
+                       t->err = EPROTO;
+                       SDPERR("Protocol error: wrong current service record count value.");
+                       goto end;
+               }
+
+               SDPDBG("Total svc count: %d\n", tsrc);
+               SDPDBG("Current svc count: %d\n", csrc);
+
+               /* parameter length without continuation state */
+               plen = sizeof(tsrc) + sizeof(csrc) + csrc * 4;
+
+               if (t->rsp_concat_buf.data_size == 0) {
+                       /* first fragment */
+                       rsp_count = sizeof(tsrc) + sizeof(csrc) + csrc * 4;
+               } else {
+                       /* point to the first csrc */
+                       uint16_t *pcsrc = (uint16_t *) (t->rsp_concat_buf.data + 2);
+
+                       /* FIXME: update the interface later. csrc doesn't need be passed to clients */
+
+                       pdata += sizeof(uint16_t); /* point to csrc */
+
+                       /* the first csrc contains the sum of partial csrc responses */
+                       *pcsrc += bt_get_unaligned((uint16_t *) pdata);
+
+                       pdata += sizeof(uint16_t); /* point to the first handle */
+                       rsp_count = csrc * 4;
+               }
+               status = 0x0000;
+               break;
+       case SDP_SVC_ATTR_RSP:
+       case SDP_SVC_SEARCH_ATTR_RSP:
+               rsp_count = ntohs(bt_get_unaligned((uint16_t *) pdata));
+               SDPDBG("Attrlist byte count : %d\n", rsp_count);
+
+               /*
+                * Number of bytes in the AttributeLists parameter(without
+                * continuation state) + AttributeListsByteCount field size.
+                */
+               plen = sizeof(uint16_t) + rsp_count;
+
+               pdata += sizeof(uint16_t); // points to attribute list
+               status = 0x0000;
+               break;
+       case SDP_ERROR_RSP:
+               status = ntohs(bt_get_unaligned((uint16_t *) pdata));
+               size = ntohs(rsphdr->plen);
+
+               /* error code + error info */
+               plen = size;
+               goto end;
+       default:
+               t->err = EPROTO;
+               SDPERR("Illegal PDU ID: 0x%x", rsphdr->pdu_id);
+               goto end;
+       }
+
+       pcstate = (sdp_cstate_t *) (pdata + rsp_count);
+
+       SDPDBG("Cstate length : %d\n", pcstate->length);
+
+       /*
+        * Check out of bound. Continuation state must have at least
+        * 1 byte: ZERO to indicate that it is not a partial response.
+        */
+       if ((n - (int) sizeof(sdp_pdu_hdr_t))  != (plen + pcstate->length + 1)) {
+               t->err = EPROTO;
+               SDPERR("Protocol error: wrong PDU size.");
+               status = 0xffff;
+               goto end;
+       }
+
+       /*
+        * This is a split response, need to concatenate intermediate
+        * responses and the last one which will have cstate length == 0
+        */
+       t->rsp_concat_buf.data = realloc(t->rsp_concat_buf.data, t->rsp_concat_buf.data_size + rsp_count);
+       targetPtr = t->rsp_concat_buf.data + t->rsp_concat_buf.data_size;
+       t->rsp_concat_buf.buf_size = t->rsp_concat_buf.data_size + rsp_count;
+       memcpy(targetPtr, pdata, rsp_count);
+       t->rsp_concat_buf.data_size += rsp_count;
+
+       if (pcstate->length > 0) {
+               int reqsize, cstate_len;
+
+               reqhdr->tid = htons(sdp_gen_tid(session));
+
+               // add continuation state
+               cstate_len = copy_cstate(t->reqbuf + t->reqsize,
+                               SDP_REQ_BUFFER_SIZE - t->reqsize, pcstate);
+
+               reqsize = t->reqsize + cstate_len;
+
+               // set the request header's param length
+               reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t));
+
+               if (sdp_send_req(session, t->reqbuf, reqsize) < 0) {
+                       SDPERR("Error sendind data:%s(%d)", strerror(errno), errno);
+                       status = 0xffff;
+                       t->err = errno;
+                       goto end;
+               }
+               err = 0;
+       }
+
+end:
+       if (err) {
+               if (t->rsp_concat_buf.data_size != 0) {
+                       pdata = t->rsp_concat_buf.data;
+                       size = t->rsp_concat_buf.data_size;
+               }
+               if (t->cb)
+                       t->cb(pdu_id, status, pdata, size, t->udata);
+       }
+
+       free(rspbuf);
+
+       return err;
+}
+
+/*
+ * This is a service search request combined with the service
+ * attribute request. First a service class match is done and
+ * for matching service, requested attributes are extracted
+ *
+ * INPUT :
+ *
+ *   sdp_list_t *search
+ *     Singly linked list containing elements of the search
+ *     pattern. Each entry in the list is a UUID(DataTypeSDP_UUID16)
+ *     of the service to be searched
+ *
+ *   AttributeSpecification attrSpec
+ *     Attribute identifiers are 16 bit unsigned integers specified
+ *     in one of 2 ways described below :
+ *     SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers
+ *        They are the actual attribute identifiers in ascending order
+ *
+ *     SDP_ATTR_REQ_RANGE - 32bit identifier range
+ *        The high-order 16bits is the start of range
+ *        the low-order 16bits are the end of range
+ *        0x0000 to 0xFFFF gets all attributes
+ *
+ *   sdp_list_t *attrids
+ *     Singly linked list containing attribute identifiers desired.
+ *     Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL)
+ *     or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE)
+ *
+ * OUTPUT :
+ *   int return value
+ *     0:
+ *       The request completed successfully. This does not
+ *       mean the requested services were found
+ *     -1:
+ *       On any error and sets errno
+ *
+ *   sdp_list_t **rsp
+ *     This variable is set on a successful return to point to
+ *     service(s) found. Each element of this list is of type
+ *     sdp_record_t* (of the services which matched the search list)
+ */
+int sdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrids, sdp_list_t **rsp)
+{
+       int status = 0;
+       uint32_t reqsize = 0, _reqsize;
+       uint32_t rspsize = 0;
+       int seqlen = 0, attr_list_len = 0;
+       int rsp_count = 0, cstate_len = 0;
+       unsigned int pdata_len;
+       uint8_t *pdata, *_pdata;
+       uint8_t *reqbuf, *rspbuf;
+       sdp_pdu_hdr_t *reqhdr, *rsphdr;
+       uint8_t dataType;
+       sdp_list_t *rec_list = NULL;
+       sdp_buf_t rsp_concat_buf;
+       sdp_cstate_t *cstate = NULL;
+
+       if (reqtype != SDP_ATTR_REQ_INDIVIDUAL && reqtype != SDP_ATTR_REQ_RANGE) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       memset(&rsp_concat_buf, 0, sizeof(sdp_buf_t));
+
+       reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+       rspbuf = malloc(SDP_RSP_BUFFER_SIZE);
+       if (!reqbuf || !rspbuf) {
+               errno = ENOMEM;
+               status = -1;
+               goto end;
+       }
+
+       reqhdr = (sdp_pdu_hdr_t *) reqbuf;
+       reqhdr->pdu_id = SDP_SVC_SEARCH_ATTR_REQ;
+
+       // generate PDU
+       pdata = reqbuf + sizeof(sdp_pdu_hdr_t);
+       reqsize = sizeof(sdp_pdu_hdr_t);
+
+       // add service class IDs for search
+       seqlen = gen_searchseq_pdu(pdata, search);
+
+       SDPDBG("Data seq added : %d\n", seqlen);
+
+       /* now set the length and increment the pointer */
+       reqsize += seqlen;
+       pdata += seqlen;
+
+       bt_put_unaligned(htons(SDP_MAX_ATTR_LEN), (uint16_t *) pdata);
+       reqsize += sizeof(uint16_t);
+       pdata += sizeof(uint16_t);
+
+       SDPDBG("Max attr byte count : %d\n", SDP_MAX_ATTR_LEN);
+
+       /* get attr seq PDU form */
+       seqlen = gen_attridseq_pdu(pdata, attrids,
+               reqtype == SDP_ATTR_REQ_INDIVIDUAL ? SDP_UINT16 : SDP_UINT32);
+       if (seqlen == -1) {
+               status = EINVAL;
+               goto end;
+       }
+       pdata += seqlen;
+       SDPDBG("Attr list length : %d\n", seqlen);
+       reqsize += seqlen;
+       *rsp = 0;
+
+       /* save before Continuation State */
+       _pdata = pdata;
+       _reqsize = reqsize;
+
+       do {
+               reqhdr->tid = htons(sdp_gen_tid(session));
+
+               /* add continuation state (can be null) */
+               reqsize = _reqsize + copy_cstate(_pdata,
+                                       SDP_REQ_BUFFER_SIZE - _reqsize, cstate);
+
+               /* set the request header's param length */
+               reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t));
+               rsphdr = (sdp_pdu_hdr_t *) rspbuf;
+               status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize);
+               if (rspsize < sizeof(sdp_pdu_hdr_t)) {
+                       SDPERR("Unexpected end of packet");
+                       status = -1;
+                       goto end;
+               }
+
+               if (status < 0) {
+                       SDPDBG("Status : 0x%x\n", rsphdr->pdu_id);
+                       goto end;
+               }
+
+               if (rsphdr->pdu_id == SDP_ERROR_RSP) {
+                       status = -1;
+                       goto end;
+               }
+
+               pdata = rspbuf + sizeof(sdp_pdu_hdr_t);
+               pdata_len = rspsize - sizeof(sdp_pdu_hdr_t);
+
+               if (pdata_len < sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       status = -1;
+                       goto end;
+               }
+
+               rsp_count = ntohs(bt_get_unaligned((uint16_t *) pdata));
+               attr_list_len += rsp_count;
+               pdata += sizeof(uint16_t);      // pdata points to attribute list
+               pdata_len -= sizeof(uint16_t);
+
+               if (pdata_len < rsp_count + sizeof(uint8_t)) {
+                       SDPERR("Unexpected end of packet: continuation state data missing");
+                       status = -1;
+                       goto end;
+               }
+
+               cstate_len = *(uint8_t *) (pdata + rsp_count);
+
+               SDPDBG("Attrlist byte count : %d\n", attr_list_len);
+               SDPDBG("Response byte count : %d\n", rsp_count);
+               SDPDBG("Cstate length : %d\n", cstate_len);
+               /*
+                * This is a split response, need to concatenate intermediate
+                * responses and the last one which will have cstate_len == 0
+                */
+               if (cstate_len > 0 || rsp_concat_buf.data_size != 0) {
+                       uint8_t *targetPtr = NULL;
+
+                       cstate = cstate_len > 0 ? (sdp_cstate_t *) (pdata + rsp_count) : 0;
+
+                       /* build concatenated response buffer */
+                       rsp_concat_buf.data = realloc(rsp_concat_buf.data, rsp_concat_buf.data_size + rsp_count);
+                       targetPtr = rsp_concat_buf.data + rsp_concat_buf.data_size;
+                       rsp_concat_buf.buf_size = rsp_concat_buf.data_size + rsp_count;
+                       memcpy(targetPtr, pdata, rsp_count);
+                       rsp_concat_buf.data_size += rsp_count;
+               }
+       } while (cstate);
+
+       if (attr_list_len > 0) {
+               int scanned = 0;
+
+               if (rsp_concat_buf.data_size != 0) {
+                       pdata = rsp_concat_buf.data;
+                       pdata_len = rsp_concat_buf.data_size;
+               }
+
+               /*
+                * Response is a sequence of sequence(s) for one or
+                * more data element sequence(s) representing services
+                * for which attributes are returned
+                */
+               scanned = sdp_extract_seqtype(pdata, pdata_len, &dataType, &seqlen);
+
+               SDPDBG("Bytes scanned : %d\n", scanned);
+               SDPDBG("Seq length : %d\n", seqlen);
+
+               if (scanned && seqlen) {
+                       pdata += scanned;
+                       pdata_len -= scanned;
+                       do {
+                               int recsize = 0;
+                               sdp_record_t *rec = sdp_extract_pdu(pdata, pdata_len, &recsize);
+                               if (rec == NULL) {
+                                       SDPERR("SVC REC is null\n");
+                                       status = -1;
+                                       goto end;
+                               }
+                               if (!recsize) {
+                                       sdp_record_free(rec);
+                                       break;
+                               }
+                               scanned += recsize;
+                               pdata += recsize;
+                               pdata_len -= recsize;
+
+                               SDPDBG("Loc seq length : %d\n", recsize);
+                               SDPDBG("Svc Rec Handle : 0x%x\n", rec->handle);
+                               SDPDBG("Bytes scanned : %d\n", scanned);
+                               SDPDBG("Attrlist byte count : %d\n", attr_list_len);
+                               rec_list = sdp_list_append(rec_list, rec);
+                       } while (scanned < attr_list_len && pdata_len > 0);
+
+                       SDPDBG("Successful scan of service attr lists\n");
+                       *rsp = rec_list;
+               }
+       }
+end:
+       free(rsp_concat_buf.data);
+       free(reqbuf);
+       free(rspbuf);
+       return status;
+}
+
+/*
+ * Find devices in the piconet.
+ */
+int sdp_general_inquiry(inquiry_info *ii, int num_dev, int duration, uint8_t *found)
+{
+       int n = hci_inquiry(-1, 10, num_dev, NULL, &ii, 0);
+       if (n < 0) {
+               SDPERR("Inquiry failed:%s", strerror(errno));
+               return -1;
+       }
+       *found = n;
+       return 0;
+}
+
+int sdp_close(sdp_session_t *session)
+{
+       struct sdp_transaction *t;
+       int ret;
+
+       if (!session)
+               return -1;
+
+       ret = close(session->sock);
+
+       t = session->priv;
+
+       if (t) {
+               free(t->reqbuf);
+
+               free(t->rsp_concat_buf.data);
+
+               free(t);
+       }
+       free(session);
+       return ret;
+}
+
+static inline int sdp_is_local(const bdaddr_t *device)
+{
+       return memcmp(device, BDADDR_LOCAL, sizeof(bdaddr_t)) == 0;
+}
+
+static int sdp_connect_local(sdp_session_t *session)
+{
+       struct sockaddr_un sa;
+
+       session->sock = socket(PF_UNIX, SOCK_STREAM, 0);
+       if (session->sock < 0)
+               return -1;
+       session->local = 1;
+
+       sa.sun_family = AF_UNIX;
+       strcpy(sa.sun_path, SDP_UNIX_PATH);
+
+       return connect(session->sock, (struct sockaddr *) &sa, sizeof(sa));
+}
+
+static int sdp_connect_l2cap(const bdaddr_t *src,
+               const bdaddr_t *dst, sdp_session_t *session)
+{
+       uint32_t flags = session->flags;
+       struct sockaddr_l2 sa;
+       int sk;
+
+       session->sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+       if (session->sock < 0)
+               return -1;
+       session->local = 0;
+
+       sk = session->sock;
+
+       if (flags & SDP_NON_BLOCKING) {
+               long arg = fcntl(sk, F_GETFL, 0);
+               fcntl(sk, F_SETFL, arg | O_NONBLOCK);
+       }
+
+       memset(&sa, 0, sizeof(sa));
+
+       sa.l2_family = AF_BLUETOOTH;
+       sa.l2_psm = 0;
+
+       if (bacmp(src, BDADDR_ANY)) {
+               sa.l2_bdaddr = *src;
+               if (bind(sk, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+                       return -1;
+       }
+
+       if (flags & SDP_WAIT_ON_CLOSE) {
+               struct linger l = { .l_onoff = 1, .l_linger = 1 };
+               setsockopt(sk, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+       }
+
+       sa.l2_psm = htobs(SDP_PSM);
+       sa.l2_bdaddr = *dst;
+
+       do {
+               int ret = connect(sk, (struct sockaddr *) &sa, sizeof(sa));
+               if (!ret)
+                       return 0;
+               if (ret < 0 && (flags & SDP_NON_BLOCKING) &&
+                               (errno == EAGAIN || errno == EINPROGRESS))
+                       return 0;
+       } while (errno == EBUSY && (flags & SDP_RETRY_IF_BUSY));
+
+       return -1;
+}
+
+sdp_session_t *sdp_connect(const bdaddr_t *src,
+               const bdaddr_t *dst, uint32_t flags)
+{
+       sdp_session_t *session;
+       int err;
+
+       if ((flags & SDP_RETRY_IF_BUSY) && (flags & SDP_NON_BLOCKING)) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       session = sdp_create(-1, flags);
+       if (!session)
+               return NULL;
+
+       if (sdp_is_local(dst)) {
+               if (sdp_connect_local(session) < 0)
+                       goto fail;
+       } else {
+               if (sdp_connect_l2cap(src, dst, session) < 0)
+                       goto fail;
+       }
+
+       return session;
+
+fail:
+       err = errno;
+       if (session->sock >= 0)
+               close(session->sock);
+       free(session->priv);
+       free(session);
+       errno = err;
+
+       return NULL;
+}
+
+int sdp_get_socket(const sdp_session_t *session)
+{
+       return session->sock;
+}
+
+uint16_t sdp_gen_tid(sdp_session_t *session)
+{
+       return session->tid++;
+}
+#ifdef __TIZEN_PATCH__
+sdp_data_t *sdp_extract_attr_safe(const uint8_t *p, int bufsize, int *size, sdp_record_t *rec)
+{
+       sdp_data_t *elem;
+       int n = 0;
+       uint8_t dtd;
+
+       if (bufsize < sizeof(uint8_t)) {
+               SDPERR("Unexpected end of packet");
+               return NULL;
+       }
+
+       dtd = *(const uint8_t *)p;
+
+       SDPDBG("extract_attr: dtd=0x%x", dtd);
+       switch (dtd) {
+       case SDP_DATA_NIL:
+       case SDP_BOOL:
+       case SDP_UINT8:
+       case SDP_UINT16:
+       case SDP_UINT32:
+       case SDP_UINT64:
+       case SDP_UINT128:
+       case SDP_INT8:
+       case SDP_INT16:
+       case SDP_INT32:
+       case SDP_INT64:
+       case SDP_INT128:
+               elem = extract_int(p, bufsize, &n);
+               break;
+       case SDP_UUID16:
+       case SDP_UUID32:
+       case SDP_UUID128:
+               elem = extract_uuid(p, bufsize, &n, rec);
+               break;
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_TEXT_STR32:
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_URL_STR32:
+               elem = extract_str(p, bufsize, &n);
+               break;
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+               elem = extract_seq(p, bufsize, &n, rec);
+               break;
+       default:
+               SDPERR("Unknown data descriptor : 0x%x terminating\n", dtd);
+               return NULL;
+       }
+       *size += n;
+       return elem;
+}
+
+
+int sdp_extract_seqtype_safe(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size)
+{
+       uint8_t dtd;
+       int scanned = sizeof(uint8_t);
+
+       if (bufsize < sizeof(uint8_t)) {
+               SDPERR("Unexpected end of packet");
+               return 0;
+       }
+
+       dtd = *(uint8_t *) buf;
+       buf += sizeof(uint8_t);
+       bufsize -= sizeof(uint8_t);
+       *dtdp = dtd;
+       switch (dtd) {
+       case SDP_SEQ8:
+       case SDP_ALT8:
+               if (bufsize < sizeof(uint8_t)) {
+                       SDPERR("Unexpected end of packet");
+                       return 0;
+               }
+               *size = *(uint8_t *) buf;
+               scanned += sizeof(uint8_t);
+               break;
+       case SDP_SEQ16:
+       case SDP_ALT16:
+               if (bufsize < sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       return 0;
+               }
+               *size = ntohs(bt_get_unaligned((uint16_t *) buf));
+               scanned += sizeof(uint16_t);
+               break;
+       case SDP_SEQ32:
+       case SDP_ALT32:
+               if (bufsize < sizeof(uint32_t)) {
+                       SDPERR("Unexpected end of packet");
+                       return 0;
+               }
+               *size = ntohl(bt_get_unaligned((uint32_t *) buf));
+               scanned += sizeof(uint32_t);
+               break;
+       default:
+               SDPERR("Unknown sequence type, aborting\n");
+               return 0;
+       }
+       return scanned;
+}
+
+
+sdp_record_t *sdp_extract_pdu_safe(const uint8_t *buf, int bufsize, int *scanned)
+{
+       int extracted = 0, seqlen = 0;
+       uint8_t dtd;
+       uint16_t attr;
+       sdp_record_t *rec = sdp_record_alloc();
+       const uint8_t *p = buf;
+
+       *scanned = sdp_extract_seqtype_safe(buf, bufsize, &dtd, &seqlen);
+       p += *scanned;
+       bufsize -= *scanned;
+       rec->attrlist = NULL;
+
+       while (extracted < seqlen && bufsize > 0) {
+               int n = sizeof(uint8_t), attrlen = 0;
+               sdp_data_t *data = NULL;
+
+               SDPDBG("Extract PDU, sequenceLength: %d localExtractedLength: %d",
+                                                       seqlen, extracted);
+
+               if (bufsize < n + sizeof(uint16_t)) {
+                       SDPERR("Unexpected end of packet");
+                       break;
+               }
+
+               dtd = *(uint8_t *) p;
+               attr = ntohs(bt_get_unaligned((uint16_t *) (p + n)));
+               n += sizeof(uint16_t);
+
+               SDPDBG("DTD of attrId : %d Attr id : 0x%x \n", dtd, attr);
+
+               data = sdp_extract_attr_safe(p + n, bufsize - n, &attrlen, rec);
+
+               SDPDBG("Attr id : 0x%x attrValueLength : %d\n", attr, attrlen);
+
+               n += attrlen;
+               if (data == NULL) {
+                       SDPDBG("Terminating extraction of attributes");
+                       break;
+               }
+
+               if (attr == SDP_ATTR_RECORD_HANDLE)
+                       rec->handle = data->val.uint32;
+
+               if (attr == SDP_ATTR_SVCLASS_ID_LIST)
+                       extract_svclass_uuid(data, &rec->svclass);
+
+               extracted += n;
+               p += n;
+               bufsize -= n;
+               sdp_attr_replace(rec, attr, data);
+
+               SDPDBG("Extract PDU, seqLength: %d localExtractedLength: %d",
+                                                       seqlen, extracted);
+       }
+#ifdef SDP_DEBUG
+       SDPDBG("Successful extracting of Svc Rec attributes\n");
+       sdp_print_service_attr(rec->attrlist);
+#endif
+       *scanned += seqlen;
+       return rec;
+}
+#endif
+/*
+ * Set the supported features
+ */
+int sdp_set_supp_feat(sdp_record_t *rec, const sdp_list_t *sf)
+{
+       const sdp_list_t *p, *r;
+       sdp_data_t *feat, *seq_feat;
+       int seqlen, i;
+       void **seqDTDs, **seqVals;
+
+       seqlen = sdp_list_len(sf);
+       seqDTDs = malloc(seqlen * sizeof(void *));
+       if (!seqDTDs)
+               return -1;
+       seqVals = malloc(seqlen * sizeof(void *));
+       if (!seqVals) {
+               free(seqDTDs);
+               return -1;
+       }
+
+       for (p = sf, i = 0; p; p = p->next, i++) {
+               int plen, j;
+               void **dtds, **vals;
+               int *lengths;
+
+               plen = sdp_list_len(p->data);
+               dtds = malloc(plen * sizeof(void *));
+               if (!dtds)
+                       goto fail;
+               vals = malloc(plen * sizeof(void *));
+               if (!vals) {
+                       free(dtds);
+                       goto fail;
+               }
+               lengths = malloc(plen * sizeof(int *));
+               if (!lengths) {
+                       free(dtds);
+                       free(vals);
+                       goto fail;
+               }
+               for (r = p->data, j = 0; r; r = r->next, j++) {
+                       sdp_data_t *data = (sdp_data_t*)r->data;
+                       dtds[j] = &data->dtd;
+                       switch (data->dtd) {
+                       case SDP_URL_STR8:
+                       case SDP_URL_STR16:
+                       case SDP_TEXT_STR8:
+                       case SDP_TEXT_STR16:
+                               vals[j] = data->val.str;
+                               lengths[j] = data->unitSize - sizeof(uint8_t);
+                               break;
+                       case SDP_ALT8:
+                       case SDP_ALT16:
+                       case SDP_ALT32:
+                       case SDP_SEQ8:
+                       case SDP_SEQ16:
+                       case SDP_SEQ32:
+                               vals[j] = data->val.dataseq;
+                               lengths[j] = 0;
+                               break;
+                       default:
+                               vals[j] = &data->val;
+                               lengths[j] = 0;
+                               break;
+                       }
+               }
+               feat = sdp_seq_alloc_with_length(dtds, vals, lengths, plen);
+               free(dtds);
+               free(vals);
+               free(lengths);
+               if (!feat)
+                       goto fail;
+               seqDTDs[i] = &feat->dtd;
+               seqVals[i] = feat;
+       }
+       seq_feat = sdp_seq_alloc(seqDTDs, seqVals, seqlen);
+       if (!seq_feat)
+               goto fail;
+       sdp_attr_replace(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST, seq_feat);
+
+       free(seqVals);
+       free(seqDTDs);
+       return 0;
+
+fail:
+       free(seqVals);
+       free(seqDTDs);
+       return -1;
+}
+
+/*
+ * Get the supported features
+ * If an error occurred -1 is returned and errno is set
+ */
+int sdp_get_supp_feat(const sdp_record_t *rec, sdp_list_t **seqp)
+{
+       sdp_data_t *sdpdata, *d;
+       sdp_list_t *tseq;
+       tseq = NULL;
+
+       sdpdata = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST);
+
+       if (!sdpdata || sdpdata->dtd < SDP_SEQ8 || sdpdata->dtd > SDP_SEQ32)
+               return sdp_get_uuidseq_attr(rec,
+                                       SDP_ATTR_SUPPORTED_FEATURES_LIST, seqp);
+
+       for (d = sdpdata->val.dataseq; d; d = d->next) {
+               sdp_data_t *dd;
+               sdp_list_t *subseq;
+
+               if (d->dtd < SDP_SEQ8 || d->dtd > SDP_SEQ32)
+                       goto fail;
+
+               subseq = NULL;
+
+               for (dd = d->val.dataseq; dd; dd = dd->next) {
+                       sdp_data_t *data;
+                       void *val;
+                       int length;
+
+                       switch (dd->dtd) {
+                       case SDP_URL_STR8:
+                       case SDP_URL_STR16:
+                       case SDP_TEXT_STR8:
+                       case SDP_TEXT_STR16:
+                               val = dd->val.str;
+                               length = dd->unitSize - sizeof(uint8_t);
+                               break;
+                       case SDP_UINT8:
+                       case SDP_UINT16:
+                               val = &dd->val;
+                               length = 0;
+                               break;
+                       default:
+                               goto fail;
+                       }
+
+                       data = sdp_data_alloc_with_length(dd->dtd, val, length);
+                       if (data)
+                               subseq = sdp_list_append(subseq, data);
+               }
+               tseq = sdp_list_append(tseq, subseq);
+       }
+       *seqp = tseq;
+       return 0;
+
+fail:
+       while (tseq) {
+               sdp_list_t * next;
+
+               next = tseq->next;
+               sdp_list_free(tseq, free);
+               tseq = next;
+       }
+       errno = EINVAL;
+       return -1;
+}
+
diff --git a/lib/sdp.h b/lib/sdp.h
new file mode 100644 (file)
index 0000000..b5b64a7
--- /dev/null
+++ b/lib/sdp.h
@@ -0,0 +1,519 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ *  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_H
+#define __SDP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <bluetooth/bluetooth.h>
+
+#define SDP_UNIX_PATH "/var/run/sdp"
+#define SDP_RESPONSE_TIMEOUT   20
+#define SDP_REQ_BUFFER_SIZE    2048
+#define SDP_RSP_BUFFER_SIZE    65535
+#define SDP_PDU_CHUNK_SIZE     1024
+
+#define BLUEZ_SDP_DEBUG(format, args...)    printf("%s():%d " format, /*__FILE__,*/ __FUNCTION__, __LINE__, ##args) /*__SYAM__*/
+/*
+ * All definitions are based on Bluetooth Assigned Numbers
+ * of the Bluetooth Specification
+ */
+#define SDP_PSM                0x0001
+
+/*
+ * Protocol UUIDs
+ */
+#define SDP_UUID       0x0001
+#define UDP_UUID       0x0002
+#define RFCOMM_UUID    0x0003
+#define TCP_UUID       0x0004
+#define TCS_BIN_UUID   0x0005
+#define TCS_AT_UUID    0x0006
+#define ATT_UUID       0x0007
+#define OBEX_UUID      0x0008
+#define IP_UUID                0x0009
+#define FTP_UUID       0x000a
+#define HTTP_UUID      0x000c
+#define WSP_UUID       0x000e
+#define BNEP_UUID      0x000f
+#define UPNP_UUID      0x0010
+#define HIDP_UUID      0x0011
+#define HCRP_CTRL_UUID 0x0012
+#define HCRP_DATA_UUID 0x0014
+#define HCRP_NOTE_UUID 0x0016
+#define AVCTP_UUID     0x0017
+#define AVDTP_UUID     0x0019
+#define CMTP_UUID      0x001b
+#define UDI_UUID       0x001d
+#define MCAP_CTRL_UUID 0x001e
+#define MCAP_DATA_UUID 0x001f
+#define L2CAP_UUID     0x0100
+
+/*
+ * Service class identifiers of standard services and service groups
+ */
+#define SDP_SERVER_SVCLASS_ID          0x1000
+#define BROWSE_GRP_DESC_SVCLASS_ID     0x1001
+#define PUBLIC_BROWSE_GROUP            0x1002
+#define SERIAL_PORT_SVCLASS_ID         0x1101
+#define LAN_ACCESS_SVCLASS_ID          0x1102
+#define DIALUP_NET_SVCLASS_ID          0x1103
+#define IRMC_SYNC_SVCLASS_ID           0x1104
+#define OBEX_OBJPUSH_SVCLASS_ID                0x1105
+#define OBEX_FILETRANS_SVCLASS_ID      0x1106
+#define IRMC_SYNC_CMD_SVCLASS_ID       0x1107
+#define HEADSET_SVCLASS_ID             0x1108
+#define CORDLESS_TELEPHONY_SVCLASS_ID  0x1109
+#define AUDIO_SOURCE_SVCLASS_ID                0x110a
+#define AUDIO_SINK_SVCLASS_ID          0x110b
+#define AV_REMOTE_TARGET_SVCLASS_ID    0x110c
+#define ADVANCED_AUDIO_SVCLASS_ID      0x110d
+#define AV_REMOTE_SVCLASS_ID           0x110e
+#define VIDEO_CONF_SVCLASS_ID          0x110f
+#define INTERCOM_SVCLASS_ID            0x1110
+#define FAX_SVCLASS_ID                 0x1111
+#define HEADSET_AGW_SVCLASS_ID         0x1112
+#define WAP_SVCLASS_ID                 0x1113
+#define WAP_CLIENT_SVCLASS_ID          0x1114
+#define PANU_SVCLASS_ID                        0x1115
+#define NAP_SVCLASS_ID                 0x1116
+#define GN_SVCLASS_ID                  0x1117
+#define DIRECT_PRINTING_SVCLASS_ID     0x1118
+#define REFERENCE_PRINTING_SVCLASS_ID  0x1119
+#define IMAGING_SVCLASS_ID             0x111a
+#define IMAGING_RESPONDER_SVCLASS_ID   0x111b
+#define IMAGING_ARCHIVE_SVCLASS_ID     0x111c
+#define IMAGING_REFOBJS_SVCLASS_ID     0x111d
+#define HANDSFREE_SVCLASS_ID           0x111e
+#define HANDSFREE_AGW_SVCLASS_ID       0x111f
+#define DIRECT_PRT_REFOBJS_SVCLASS_ID  0x1120
+#define REFLECTED_UI_SVCLASS_ID                0x1121
+#define BASIC_PRINTING_SVCLASS_ID      0x1122
+#define PRINTING_STATUS_SVCLASS_ID     0x1123
+#define HID_SVCLASS_ID                 0x1124
+#define HCR_SVCLASS_ID                 0x1125
+#define HCR_PRINT_SVCLASS_ID           0x1126
+#define HCR_SCAN_SVCLASS_ID            0x1127
+#define CIP_SVCLASS_ID                 0x1128
+#define VIDEO_CONF_GW_SVCLASS_ID       0x1129
+#define UDI_MT_SVCLASS_ID              0x112a
+#define UDI_TA_SVCLASS_ID              0x112b
+#define AV_SVCLASS_ID                  0x112c
+#define SAP_SVCLASS_ID                 0x112d
+#define PBAP_PCE_SVCLASS_ID            0x112e
+#define PBAP_PSE_SVCLASS_ID            0x112f
+#define PBAP_SVCLASS_ID                        0x1130
+#define PNP_INFO_SVCLASS_ID            0x1200
+#define GENERIC_NETWORKING_SVCLASS_ID  0x1201
+#define GENERIC_FILETRANS_SVCLASS_ID   0x1202
+#define GENERIC_AUDIO_SVCLASS_ID       0x1203
+#define GENERIC_TELEPHONY_SVCLASS_ID   0x1204
+#define UPNP_SVCLASS_ID                        0x1205
+#define UPNP_IP_SVCLASS_ID             0x1206
+#define UPNP_PAN_SVCLASS_ID            0x1300
+#define UPNP_LAP_SVCLASS_ID            0x1301
+#define UPNP_L2CAP_SVCLASS_ID          0x1302
+#define VIDEO_SOURCE_SVCLASS_ID                0x1303
+#define VIDEO_SINK_SVCLASS_ID          0x1304
+#define VIDEO_DISTRIBUTION_SVCLASS_ID  0x1305
+#define HDP_SVCLASS_ID                 0x1400
+#define HDP_SOURCE_SVCLASS_ID          0x1401
+#define HDP_SINK_SVCLASS_ID            0x1402
+#define APPLE_AGENT_SVCLASS_ID         0x2112
+#define GENERIC_ATTRIB_SVCLASS_ID      0x1801
+
+/*
+ * Standard profile descriptor identifiers; note these
+ * may be identical to some of the service classes defined above
+ */
+#define SDP_SERVER_PROFILE_ID          SDP_SERVER_SVCLASS_ID
+#define BROWSE_GRP_DESC_PROFILE_ID     BROWSE_GRP_DESC_SVCLASS_ID
+#define SERIAL_PORT_PROFILE_ID         SERIAL_PORT_SVCLASS_ID
+#define LAN_ACCESS_PROFILE_ID          LAN_ACCESS_SVCLASS_ID
+#define DIALUP_NET_PROFILE_ID          DIALUP_NET_SVCLASS_ID
+#define IRMC_SYNC_PROFILE_ID           IRMC_SYNC_SVCLASS_ID
+#define OBEX_OBJPUSH_PROFILE_ID                OBEX_OBJPUSH_SVCLASS_ID
+#define OBEX_FILETRANS_PROFILE_ID      OBEX_FILETRANS_SVCLASS_ID
+#define IRMC_SYNC_CMD_PROFILE_ID       IRMC_SYNC_CMD_SVCLASS_ID
+#define HEADSET_PROFILE_ID             HEADSET_SVCLASS_ID
+#define CORDLESS_TELEPHONY_PROFILE_ID  CORDLESS_TELEPHONY_SVCLASS_ID
+#define AUDIO_SOURCE_PROFILE_ID                AUDIO_SOURCE_SVCLASS_ID
+#define AUDIO_SINK_PROFILE_ID          AUDIO_SINK_SVCLASS_ID
+#define AV_REMOTE_TARGET_PROFILE_ID    AV_REMOTE_TARGET_SVCLASS_ID
+#define ADVANCED_AUDIO_PROFILE_ID      ADVANCED_AUDIO_SVCLASS_ID
+#define AV_REMOTE_PROFILE_ID           AV_REMOTE_SVCLASS_ID
+#define VIDEO_CONF_PROFILE_ID          VIDEO_CONF_SVCLASS_ID
+#define INTERCOM_PROFILE_ID            INTERCOM_SVCLASS_ID
+#define FAX_PROFILE_ID                 FAX_SVCLASS_ID
+#define HEADSET_AGW_PROFILE_ID         HEADSET_AGW_SVCLASS_ID
+#define WAP_PROFILE_ID                 WAP_SVCLASS_ID
+#define WAP_CLIENT_PROFILE_ID          WAP_CLIENT_SVCLASS_ID
+#define PANU_PROFILE_ID                        PANU_SVCLASS_ID
+#define NAP_PROFILE_ID                 NAP_SVCLASS_ID
+#define GN_PROFILE_ID                  GN_SVCLASS_ID
+#define DIRECT_PRINTING_PROFILE_ID     DIRECT_PRINTING_SVCLASS_ID
+#define REFERENCE_PRINTING_PROFILE_ID  REFERENCE_PRINTING_SVCLASS_ID
+#define IMAGING_PROFILE_ID             IMAGING_SVCLASS_ID
+#define IMAGING_RESPONDER_PROFILE_ID   IMAGING_RESPONDER_SVCLASS_ID
+#define IMAGING_ARCHIVE_PROFILE_ID     IMAGING_ARCHIVE_SVCLASS_ID
+#define IMAGING_REFOBJS_PROFILE_ID     IMAGING_REFOBJS_SVCLASS_ID
+#define HANDSFREE_PROFILE_ID           HANDSFREE_SVCLASS_ID
+#define HANDSFREE_AGW_PROFILE_ID       HANDSFREE_AGW_SVCLASS_ID
+#define DIRECT_PRT_REFOBJS_PROFILE_ID  DIRECT_PRT_REFOBJS_SVCLASS_ID
+#define REFLECTED_UI_PROFILE_ID                REFLECTED_UI_SVCLASS_ID
+#define BASIC_PRINTING_PROFILE_ID      BASIC_PRINTING_SVCLASS_ID
+#define PRINTING_STATUS_PROFILE_ID     PRINTING_STATUS_SVCLASS_ID
+#define HID_PROFILE_ID                 HID_SVCLASS_ID
+#define HCR_PROFILE_ID                 HCR_SCAN_SVCLASS_ID
+#define HCR_PRINT_PROFILE_ID           HCR_PRINT_SVCLASS_ID
+#define HCR_SCAN_PROFILE_ID            HCR_SCAN_SVCLASS_ID
+#define CIP_PROFILE_ID                 CIP_SVCLASS_ID
+#define VIDEO_CONF_GW_PROFILE_ID       VIDEO_CONF_GW_SVCLASS_ID
+#define UDI_MT_PROFILE_ID              UDI_MT_SVCLASS_ID
+#define UDI_TA_PROFILE_ID              UDI_TA_SVCLASS_ID
+#define AV_PROFILE_ID                  AV_SVCLASS_ID
+#define SAP_PROFILE_ID                 SAP_SVCLASS_ID
+#define PBAP_PCE_PROFILE_ID            PBAP_PCE_SVCLASS_ID
+#define PBAP_PSE_PROFILE_ID            PBAP_PSE_SVCLASS_ID
+#define PBAP_PROFILE_ID                        PBAP_SVCLASS_ID
+#define PNP_INFO_PROFILE_ID            PNP_INFO_SVCLASS_ID
+#define GENERIC_NETWORKING_PROFILE_ID  GENERIC_NETWORKING_SVCLASS_ID
+#define GENERIC_FILETRANS_PROFILE_ID   GENERIC_FILETRANS_SVCLASS_ID
+#define GENERIC_AUDIO_PROFILE_ID       GENERIC_AUDIO_SVCLASS_ID
+#define GENERIC_TELEPHONY_PROFILE_ID   GENERIC_TELEPHONY_SVCLASS_ID
+#define UPNP_PROFILE_ID                        UPNP_SVCLASS_ID
+#define UPNP_IP_PROFILE_ID             UPNP_IP_SVCLASS_ID
+#define UPNP_PAN_PROFILE_ID            UPNP_PAN_SVCLASS_ID
+#define UPNP_LAP_PROFILE_ID            UPNP_LAP_SVCLASS_ID
+#define UPNP_L2CAP_PROFILE_ID          UPNP_L2CAP_SVCLASS_ID
+#define VIDEO_SOURCE_PROFILE_ID                VIDEO_SOURCE_SVCLASS_ID
+#define VIDEO_SINK_PROFILE_ID          VIDEO_SINK_SVCLASS_ID
+#define VIDEO_DISTRIBUTION_PROFILE_ID  VIDEO_DISTRIBUTION_SVCLASS_ID
+#define HDP_PROFILE_ID                 HDP_SVCLASS_ID
+#define HDP_SOURCE_PROFILE_ID          HDP_SOURCE_SVCLASS_ID
+#define HDP_SINK_PROFILE_ID            HDP_SINK_SVCLASS_ID
+#define APPLE_AGENT_PROFILE_ID         APPLE_AGENT_SVCLASS_ID
+#define GENERIC_ACCESS_PROFILE_ID      0x1800
+#define GENERIC_ATTRIB_PROFILE_ID      GENERIC_ATTRIB_SVCLASS_ID
+
+/*
+ * Compatibility macros for the old MDP acronym
+ */
+#define MDP_SVCLASS_ID                 HDP_SVCLASS_ID
+#define MDP_SOURCE_SVCLASS_ID          HDP_SOURCE_SVCLASS_ID
+#define MDP_SINK_SVCLASS_ID            HDP_SINK_SVCLASS_ID
+#define MDP_PROFILE_ID                 HDP_PROFILE_ID
+#define MDP_SOURCE_PROFILE_ID          HDP_SOURCE_PROFILE_ID
+#define MDP_SINK_PROFILE_ID            HDP_SINK_PROFILE_ID
+
+/*
+ * Attribute identifier codes
+ */
+#define SDP_SERVER_RECORD_HANDLE               0x0000
+
+/*
+ * Possible values for attribute-id are listed below.
+ * See SDP Spec, section "Service Attribute Definitions" for more details.
+ */
+#define SDP_ATTR_RECORD_HANDLE                 0x0000
+#define SDP_ATTR_SVCLASS_ID_LIST               0x0001
+#define SDP_ATTR_RECORD_STATE                  0x0002
+#define SDP_ATTR_SERVICE_ID                    0x0003
+#define SDP_ATTR_PROTO_DESC_LIST               0x0004
+#define SDP_ATTR_BROWSE_GRP_LIST               0x0005
+#define SDP_ATTR_LANG_BASE_ATTR_ID_LIST                0x0006
+#define SDP_ATTR_SVCINFO_TTL                   0x0007
+#define SDP_ATTR_SERVICE_AVAILABILITY          0x0008
+#define SDP_ATTR_PFILE_DESC_LIST               0x0009
+#define SDP_ATTR_DOC_URL                       0x000a
+#define SDP_ATTR_CLNT_EXEC_URL                 0x000b
+#define SDP_ATTR_ICON_URL                      0x000c
+#define SDP_ATTR_ADD_PROTO_DESC_LIST           0x000d
+
+#define SDP_ATTR_GROUP_ID                      0x0200
+#define SDP_ATTR_IP_SUBNET                     0x0200
+#define SDP_ATTR_VERSION_NUM_LIST              0x0200
+#define SDP_ATTR_SUPPORTED_FEATURES_LIST       0x0200
+#define SDP_ATTR_SVCDB_STATE                   0x0201
+
+#define SDP_ATTR_SERVICE_VERSION               0x0300
+#define SDP_ATTR_EXTERNAL_NETWORK              0x0301
+#define SDP_ATTR_SUPPORTED_DATA_STORES_LIST    0x0301
+#define SDP_ATTR_DATA_EXCHANGE_SPEC            0x0301
+#define SDP_ATTR_FAX_CLASS1_SUPPORT            0x0302
+#define SDP_ATTR_REMOTE_AUDIO_VOLUME_CONTROL   0x0302
+#define SDP_ATTR_MCAP_SUPPORTED_PROCEDURES     0x0302
+#define SDP_ATTR_FAX_CLASS20_SUPPORT           0x0303
+#define SDP_ATTR_SUPPORTED_FORMATS_LIST                0x0303
+#define SDP_ATTR_FAX_CLASS2_SUPPORT            0x0304
+#define SDP_ATTR_AUDIO_FEEDBACK_SUPPORT                0x0305
+#define SDP_ATTR_NETWORK_ADDRESS               0x0306
+#define SDP_ATTR_WAP_GATEWAY                   0x0307
+#define SDP_ATTR_HOMEPAGE_URL                  0x0308
+#define SDP_ATTR_WAP_STACK_TYPE                        0x0309
+#define SDP_ATTR_SECURITY_DESC                 0x030a
+#define SDP_ATTR_NET_ACCESS_TYPE               0x030b
+#define SDP_ATTR_MAX_NET_ACCESSRATE            0x030c
+#define SDP_ATTR_IP4_SUBNET                    0x030d
+#define SDP_ATTR_IP6_SUBNET                    0x030e
+#define SDP_ATTR_SUPPORTED_CAPABILITIES                0x0310
+#define SDP_ATTR_SUPPORTED_FEATURES            0x0311
+#define SDP_ATTR_SUPPORTED_FUNCTIONS           0x0312
+#define SDP_ATTR_TOTAL_IMAGING_DATA_CAPACITY   0x0313
+#define SDP_ATTR_SUPPORTED_REPOSITORIES                0x0314
+
+#define SDP_ATTR_SPECIFICATION_ID              0x0200
+#define SDP_ATTR_VENDOR_ID                     0x0201
+#define SDP_ATTR_PRODUCT_ID                    0x0202
+#define SDP_ATTR_VERSION                       0x0203
+#define SDP_ATTR_PRIMARY_RECORD                        0x0204
+#define SDP_ATTR_VENDOR_ID_SOURCE              0x0205
+
+#define SDP_ATTR_HID_DEVICE_RELEASE_NUMBER     0x0200
+#define SDP_ATTR_HID_PARSER_VERSION            0x0201
+#define SDP_ATTR_HID_DEVICE_SUBCLASS           0x0202
+#define SDP_ATTR_HID_COUNTRY_CODE              0x0203
+#define SDP_ATTR_HID_VIRTUAL_CABLE             0x0204
+#define SDP_ATTR_HID_RECONNECT_INITIATE                0x0205
+#define SDP_ATTR_HID_DESCRIPTOR_LIST           0x0206
+#define SDP_ATTR_HID_LANG_ID_BASE_LIST         0x0207
+#define SDP_ATTR_HID_SDP_DISABLE               0x0208
+#define SDP_ATTR_HID_BATTERY_POWER             0x0209
+#define SDP_ATTR_HID_REMOTE_WAKEUP             0x020a
+#define SDP_ATTR_HID_PROFILE_VERSION           0x020b
+#define SDP_ATTR_HID_SUPERVISION_TIMEOUT       0x020c
+#define SDP_ATTR_HID_NORMALLY_CONNECTABLE      0x020d
+#define SDP_ATTR_HID_BOOT_DEVICE               0x020e
+
+/*
+ * These identifiers are based on the SDP spec stating that
+ * "base attribute id of the primary (universal) language must be 0x0100"
+ *
+ * Other languages should have their own offset; e.g.:
+ * #define XXXLangBase yyyy
+ * #define AttrServiceName_XXX 0x0000+XXXLangBase
+ */
+#define SDP_PRIMARY_LANG_BASE          0x0100
+
+#define SDP_ATTR_SVCNAME_PRIMARY       0x0000 + SDP_PRIMARY_LANG_BASE
+#define SDP_ATTR_SVCDESC_PRIMARY       0x0001 + SDP_PRIMARY_LANG_BASE
+#define SDP_ATTR_PROVNAME_PRIMARY      0x0002 + SDP_PRIMARY_LANG_BASE
+
+/*
+ * The Data representation in SDP PDUs (pps 339, 340 of BT SDP Spec)
+ * These are the exact data type+size descriptor values
+ * that go into the PDU buffer.
+ *
+ * The datatype (leading 5bits) + size descriptor (last 3 bits)
+ * is 8 bits. The size descriptor is critical to extract the
+ * right number of bytes for the data value from the PDU.
+ *
+ * For most basic types, the datatype+size descriptor is
+ * straightforward. However for constructed types and strings,
+ * the size of the data is in the next "n" bytes following the
+ * 8 bits (datatype+size) descriptor. Exactly what the "n" is
+ * specified in the 3 bits of the data size descriptor.
+ *
+ * TextString and URLString can be of size 2^{8, 16, 32} bytes
+ * DataSequence and DataSequenceAlternates can be of size 2^{8, 16, 32}
+ * The size are computed post-facto in the API and are not known apriori
+ */
+#define SDP_DATA_NIL           0x00
+#define SDP_UINT8              0x08
+#define SDP_UINT16             0x09
+#define SDP_UINT32             0x0A
+#define SDP_UINT64             0x0B
+#define SDP_UINT128            0x0C
+#define SDP_INT8               0x10
+#define SDP_INT16              0x11
+#define SDP_INT32              0x12
+#define SDP_INT64              0x13
+#define SDP_INT128             0x14
+#define SDP_UUID_UNSPEC                0x18
+#define SDP_UUID16             0x19
+#define SDP_UUID32             0x1A
+#define SDP_UUID128            0x1C
+#define SDP_TEXT_STR_UNSPEC    0x20
+#define SDP_TEXT_STR8          0x25
+#define SDP_TEXT_STR16         0x26
+#define SDP_TEXT_STR32         0x27
+#define SDP_BOOL               0x28
+#define SDP_SEQ_UNSPEC         0x30
+#define SDP_SEQ8               0x35
+#define SDP_SEQ16              0x36
+#define SDP_SEQ32              0x37
+#define SDP_ALT_UNSPEC         0x38
+#define SDP_ALT8               0x3D
+#define SDP_ALT16              0x3E
+#define SDP_ALT32              0x3F
+#define SDP_URL_STR_UNSPEC     0x40
+#define SDP_URL_STR8           0x45
+#define SDP_URL_STR16          0x46
+#define SDP_URL_STR32          0x47
+
+/*
+ * The PDU identifiers of SDP packets between client and server
+ */
+#define SDP_ERROR_RSP          0x01
+#define SDP_SVC_SEARCH_REQ     0x02
+#define SDP_SVC_SEARCH_RSP     0x03
+#define SDP_SVC_ATTR_REQ       0x04
+#define SDP_SVC_ATTR_RSP       0x05
+#define SDP_SVC_SEARCH_ATTR_REQ        0x06
+#define SDP_SVC_SEARCH_ATTR_RSP        0x07
+
+/*
+ * Some additions to support service registration.
+ * These are outside the scope of the Bluetooth specification
+ */
+#define SDP_SVC_REGISTER_REQ   0x75
+#define SDP_SVC_REGISTER_RSP   0x76
+#define SDP_SVC_UPDATE_REQ     0x77
+#define SDP_SVC_UPDATE_RSP     0x78
+#define SDP_SVC_REMOVE_REQ     0x79
+#define SDP_SVC_REMOVE_RSP     0x80
+
+/*
+ * SDP Error codes
+ */
+#define SDP_INVALID_VERSION            0x0001
+#define SDP_INVALID_RECORD_HANDLE      0x0002
+#define SDP_INVALID_SYNTAX             0x0003
+#define SDP_INVALID_PDU_SIZE           0x0004
+#define SDP_INVALID_CSTATE             0x0005
+
+/*
+ * SDP PDU
+ */
+typedef struct {
+       uint8_t  pdu_id;
+       uint16_t tid;
+       uint16_t plen;
+} __attribute__ ((packed)) sdp_pdu_hdr_t;
+
+/*
+ * Common definitions for attributes in the SDP.
+ * Should the type of any of these change, you need only make a change here.
+ */
+
+typedef struct {
+       uint8_t type;
+       union {
+               uint16_t  uuid16;
+               uint32_t  uuid32;
+               uint128_t uuid128;
+       } value;
+} uuid_t;
+
+#define SDP_IS_UUID(x) ((x) == SDP_UUID16 || (x) == SDP_UUID32 || (x) ==SDP_UUID128)
+
+typedef struct _sdp_list sdp_list_t;
+struct _sdp_list {
+       sdp_list_t *next;
+       void *data;
+};
+
+/*
+ * User-visible strings can be in many languages
+ * in addition to the universal language.
+ *
+ * Language meta-data includes language code in ISO639
+ * followed by the encoding format. The third field in this
+ * structure is the attribute offset for the language.
+ * User-visible strings in the specified language can be
+ * obtained at this offset.
+ */
+typedef struct {
+       uint16_t code_ISO639;
+       uint16_t encoding;
+       uint16_t base_offset;
+} sdp_lang_attr_t;
+
+/*
+ * Profile descriptor is the Bluetooth profile metadata. If a
+ * service conforms to a well-known profile, then its profile
+ * identifier (UUID) is an attribute of the service. In addition,
+ * if the profile has a version number it is specified here.
+ */
+typedef struct {
+       uuid_t uuid;
+       uint16_t version;
+} sdp_profile_desc_t;
+
+typedef struct {
+       uint8_t major;
+       uint8_t minor;
+} sdp_version_t;
+
+typedef struct {
+       uint8_t *data;
+       uint32_t data_size;
+       uint32_t buf_size;
+} sdp_buf_t;
+
+typedef struct {
+       uint32_t handle;
+
+       /* Search pattern: a sequence of all UUIDs seen in this record */
+       sdp_list_t *pattern;
+       sdp_list_t *attrlist;
+
+       /* Main service class for Extended Inquiry Response */
+       uuid_t svclass;
+} sdp_record_t;
+
+typedef struct sdp_data_struct sdp_data_t;
+struct sdp_data_struct {
+       uint8_t dtd;
+       uint16_t attrId;
+       union {
+               int8_t    int8;
+               int16_t   int16;
+               int32_t   int32;
+               int64_t   int64;
+               uint128_t int128;
+               uint8_t   uint8;
+               uint16_t  uint16;
+               uint32_t  uint32;
+               uint64_t  uint64;
+               uint128_t uint128;
+               uuid_t    uuid;
+               char     *str;
+               sdp_data_t *dataseq;
+       } val;
+       sdp_data_t *next;
+       int unitSize;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SDP_H */
diff --git a/lib/sdp_lib.h b/lib/sdp_lib.h
new file mode 100644 (file)
index 0000000..e506ac1
--- /dev/null
@@ -0,0 +1,631 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ *  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_LIB_H
+#define __SDP_LIB_H
+
+#include <sys/socket.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * SDP lists
+ */
+typedef void(*sdp_list_func_t)(void *, void *);
+typedef void(*sdp_free_func_t)(void *);
+typedef int (*sdp_comp_func_t)(const void *, const void *);
+
+sdp_list_t *sdp_list_append(sdp_list_t *list, void *d);
+sdp_list_t *sdp_list_remove(sdp_list_t *list, void *d);
+sdp_list_t *sdp_list_insert_sorted(sdp_list_t *list, void *data, sdp_comp_func_t f);
+void        sdp_list_free(sdp_list_t *list, sdp_free_func_t f);
+
+static inline int sdp_list_len(const sdp_list_t *list)
+{
+       int n = 0;
+       for (; list; list = list->next)
+               n++;
+       return n;
+}
+
+static inline sdp_list_t *sdp_list_find(sdp_list_t *list, void *u, sdp_comp_func_t f)
+{
+       for (; list; list = list->next)
+               if (f(list->data, u) == 0)
+                       return list;
+       return NULL;
+}
+
+static inline void sdp_list_foreach(sdp_list_t *list, sdp_list_func_t f, void *u)
+{
+       for (; list; list = list->next)
+               f(list->data, u);
+}
+
+/*
+ * Values of the flags parameter to sdp_record_register
+ */
+#define SDP_RECORD_PERSIST     0x01
+#define SDP_DEVICE_RECORD      0x02
+
+/*
+ * Values of the flags parameter to sdp_connect
+ */
+#define SDP_RETRY_IF_BUSY      0x01
+#define SDP_WAIT_ON_CLOSE      0x02
+#define SDP_NON_BLOCKING       0x04
+
+/*
+ * a session with an SDP server
+ */
+typedef struct {
+       int sock;
+       int state;
+       int local;
+       int flags;
+       uint16_t tid;   // Current transaction ID
+       void *priv;
+} sdp_session_t;
+
+typedef enum {
+       /*
+        *  Attributes are specified as individual elements
+        */
+       SDP_ATTR_REQ_INDIVIDUAL = 1,
+       /*
+        *  Attributes are specified as a range
+        */
+       SDP_ATTR_REQ_RANGE
+} sdp_attrreq_type_t;
+
+/*
+ *     When the pdu_id(type) is a sdp error response, check the status value
+ *     to figure out the error reason. For status values 0x0001-0x0006 check
+ *     Bluetooth SPEC. If the status is 0xffff, call sdp_get_error function
+ *     to get the real reason:
+ *         - wrong transaction ID(EPROTO)
+ *         - wrong PDU id or(EPROTO)
+ *         - I/O error
+ */
+typedef void sdp_callback_t(uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *udata);
+
+/*
+ * create an L2CAP connection to a Bluetooth device
+ *
+ * INPUT:
+ *
+ *  bdaddr_t *src:
+ *     Address of the local device to use to make the connection
+ *     (or BDADDR_ANY)
+ *
+ *  bdaddr_t *dst:
+ *    Address of the SDP server device
+ */
+sdp_session_t *sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags);
+int sdp_close(sdp_session_t *session);
+int sdp_get_socket(const sdp_session_t *session);
+
+/*
+ * SDP transaction: functions for asynchronous search.
+ */
+sdp_session_t *sdp_create(int sk, uint32_t flags);
+int sdp_get_error(sdp_session_t *session);
+int sdp_process(sdp_session_t *session);
+int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata);
+
+int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num);
+int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list);
+int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list);
+
+uint16_t sdp_gen_tid(sdp_session_t *session);
+
+/*
+ * find all devices in the piconet
+ */
+int sdp_general_inquiry(inquiry_info *ii, int dev_num, int duration, uint8_t *found);
+
+/* flexible extraction of basic attributes - Jean II */
+int sdp_get_int_attr(const sdp_record_t *rec, uint16_t attr, int *value);
+int sdp_get_string_attr(const sdp_record_t *rec, uint16_t attr, char *value, int valuelen);
+
+/*
+ * Basic sdp data functions
+ */
+sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value);
+sdp_data_t *sdp_data_alloc_with_length(uint8_t dtd, const void *value, uint32_t length);
+void sdp_data_free(sdp_data_t *data);
+sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attr_id);
+
+sdp_data_t *sdp_seq_alloc(void **dtds, void **values, int len);
+sdp_data_t *sdp_seq_alloc_with_length(void **dtds, void **values, int *length, int len);
+sdp_data_t *sdp_seq_append(sdp_data_t *seq, sdp_data_t *data);
+
+int sdp_attr_add(sdp_record_t *rec, uint16_t attr, sdp_data_t *data);
+void sdp_attr_remove(sdp_record_t *rec, uint16_t attr);
+void sdp_attr_replace(sdp_record_t *rec, uint16_t attr, sdp_data_t *data);
+int sdp_set_uuidseq_attr(sdp_record_t *rec, uint16_t attr, sdp_list_t *seq);
+int sdp_get_uuidseq_attr(const sdp_record_t *rec, uint16_t attr, sdp_list_t **seqp);
+
+/*
+ * NOTE that none of the functions below will update the SDP server,
+ * unless the {register, update}sdp_record_t() function is invoked.
+ * All functions which return an integer value, return 0 on success
+ * or -1 on failure.
+ */
+
+/*
+ * Create an attribute and add it to the service record's attribute list.
+ * This consists of the data type descriptor of the attribute,
+ * the value of the attribute and the attribute identifier.
+ */
+int sdp_attr_add_new(sdp_record_t *rec, uint16_t attr, uint8_t dtd, const void *p);
+
+/*
+ * Set the information attributes of the service record.
+ * The set of attributes comprises service name, description
+ * and provider name
+ */
+void sdp_set_info_attr(sdp_record_t *rec, const char *name, const char *prov, const char *desc);
+
+/*
+ * Set the ServiceClassID attribute to the sequence specified by seq.
+ * Note that the identifiers need to be in sorted order from the most
+ * specific to the most generic service class that this service
+ * conforms to.
+ */
+static inline int sdp_set_service_classes(sdp_record_t *rec, sdp_list_t *seq)
+{
+       return sdp_set_uuidseq_attr(rec, SDP_ATTR_SVCLASS_ID_LIST, seq);
+}
+
+/*
+ * Get the service classes to which the service conforms.
+ *
+ * When set, the list contains elements of ServiceClassIdentifer(uint16_t)
+ * ordered from most specific to most generic
+ */
+static inline int sdp_get_service_classes(const sdp_record_t *rec, sdp_list_t **seqp)
+{
+       return sdp_get_uuidseq_attr(rec, SDP_ATTR_SVCLASS_ID_LIST, seqp);
+}
+
+/*
+ * Set the BrowseGroupList attribute to the list specified by seq.
+ *
+ * A service can belong to one or more service groups
+ * and the list comprises such group identifiers (UUIDs)
+ */
+static inline int sdp_set_browse_groups(sdp_record_t *rec, sdp_list_t *seq)
+{
+       return sdp_set_uuidseq_attr(rec, SDP_ATTR_BROWSE_GRP_LIST, seq);
+}
+
+/*
+ * Set the access protocols of the record to those specified in proto
+ */
+int sdp_set_access_protos(sdp_record_t *rec, const sdp_list_t *proto);
+
+/*
+ * Set the additional access protocols of the record to those specified in proto
+ */
+int sdp_set_add_access_protos(sdp_record_t *rec, const sdp_list_t *proto);
+
+/*
+ * Get protocol port (i.e. PSM for L2CAP, Channel for RFCOMM)
+ */
+int sdp_get_proto_port(const sdp_list_t *list, int proto);
+
+/*
+ * Get protocol descriptor.
+ */
+sdp_data_t *sdp_get_proto_desc(sdp_list_t *list, int proto);
+
+/*
+ * Set the LanguageBase attributes to the values specified in list
+ * (a linked list of sdp_lang_attr_t objects, one for each language in
+ * which user-visible attributes are present).
+ */
+int sdp_set_lang_attr(sdp_record_t *rec, const sdp_list_t *list);
+
+/*
+ * Set the ServiceInfoTimeToLive attribute of the service.
+ * This is the number of seconds that this record is guaranteed
+ * not to change after being obtained by a client.
+ */
+static inline int sdp_set_service_ttl(sdp_record_t *rec, uint32_t ttl)
+{
+       return sdp_attr_add_new(rec, SDP_ATTR_SVCINFO_TTL, SDP_UINT32, &ttl);
+}
+
+/*
+ * Set the ServiceRecordState attribute of a service. This is
+ * guaranteed to change if there is any kind of modification to
+ * the record.
+ */
+static inline int sdp_set_record_state(sdp_record_t *rec, uint32_t state)
+{
+       return sdp_attr_add_new(rec, SDP_ATTR_RECORD_STATE, SDP_UINT32, &state);
+}
+
+/*
+ * Set the ServiceID attribute of a service.
+ */
+void sdp_set_service_id(sdp_record_t *rec, uuid_t uuid);
+
+/*
+ * Set the GroupID attribute of a service
+ */
+void sdp_set_group_id(sdp_record_t *rec, uuid_t grouuuid);
+
+/*
+ * Set the ServiceAvailability attribute of a service.
+ *
+ * Note that this represents the relative availability
+ * of the service: 0x00 means completely unavailable;
+ * 0xFF means maximum availability.
+ */
+static inline int sdp_set_service_avail(sdp_record_t *rec, uint8_t avail)
+{
+       return sdp_attr_add_new(rec, SDP_ATTR_SERVICE_AVAILABILITY, SDP_UINT8, &avail);
+}
+
+/*
+ * Set the profile descriptor list attribute of a record.
+ *
+ * Each element in the list is an object of type
+ * sdp_profile_desc_t which is a definition of the
+ * Bluetooth profile that this service conforms to.
+ */
+int sdp_set_profile_descs(sdp_record_t *rec, const sdp_list_t *desc);
+
+/*
+ * Set URL attributes of a record.
+ *
+ * ClientExecutableURL: a URL to a client's platform specific (WinCE,
+ * PalmOS) executable code that can be used to access this service.
+ *
+ * DocumentationURL: a URL pointing to service documentation
+ *
+ * IconURL: a URL to an icon that can be used to represent this service.
+ *
+ * Note: pass NULL for any URLs that you don't want to set or remove
+ */
+void sdp_set_url_attr(sdp_record_t *rec, const char *clientExecURL, const char *docURL, const char *iconURL);
+
+/*
+ * a service search request.
+ *
+ *  INPUT :
+ *
+ *    sdp_list_t *search
+ *      list containing elements of the search
+ *      pattern. Each entry in the list is a UUID
+ *      of the service to be searched
+ *
+ *    uint16_t max_rec_num
+ *       An integer specifying the maximum number of
+ *       entries that the client can handle in the response.
+ *
+ *  OUTPUT :
+ *
+ *    int return value
+ *      0
+ *        The request completed successfully. This does not
+ *        mean the requested services were found
+ *      -1
+ *        The request completed unsuccessfully
+ *
+ *    sdp_list_t *rsp_list
+ *      This variable is set on a successful return if there are
+ *      non-zero service handles. It is a singly linked list of
+ *      service record handles (uint16_t)
+ */
+int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num, sdp_list_t **rsp_list);
+
+/*
+ *  a service attribute request.
+ *
+ *  INPUT :
+ *
+ *    uint32_t handle
+ *      The handle of the service for which the attribute(s) are
+ *      requested
+ *
+ *    sdp_attrreq_type_t reqtype
+ *      Attribute identifiers are 16 bit unsigned integers specified
+ *      in one of 2 ways described below :
+ *      SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers
+ *         They are the actual attribute identifiers in ascending order
+ *
+ *      SDP_ATTR_REQ_RANGE - 32bit identifier range
+ *         The high-order 16bits is the start of range
+ *         the low-order 16bits are the end of range
+ *         0x0000 to 0xFFFF gets all attributes
+ *
+ *    sdp_list_t *attrid_list
+ *      Singly linked list containing attribute identifiers desired.
+ *      Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL)
+ *      or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE)
+ *
+ *  OUTPUT :
+ *    int return value
+ *      0
+ *        The request completed successfully. This does not
+ *        mean the requested services were found
+ *      -1
+ *        The request completed unsuccessfully due to a timeout
+ */
+sdp_record_t *sdp_service_attr_req(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list);
+
+/*
+ *  This is a service search request combined with the service
+ *  attribute request. First a service class match is done and
+ *  for matching service, requested attributes are extracted
+ *
+ *  INPUT :
+ *
+ *    sdp_list_t *search
+ *      Singly linked list containing elements of the search
+ *      pattern. Each entry in the list is a UUID(DataTypeSDP_UUID16)
+ *      of the service to be searched
+ *
+ *    AttributeSpecification attrSpec
+ *      Attribute identifiers are 16 bit unsigned integers specified
+ *      in one of 2 ways described below :
+ *      SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers
+ *         They are the actual attribute identifiers in ascending order
+ *
+ *      SDP_ATTR_REQ_RANGE - 32bit identifier range
+ *         The high-order 16bits is the start of range
+ *         the low-order 16bits are the end of range
+ *         0x0000 to 0xFFFF gets all attributes
+ *
+ *    sdp_list_t *attrid_list
+ *      Singly linked list containing attribute identifiers desired.
+ *      Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL)
+ *      or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE)
+ *
+ *  OUTPUT :
+ *    int return value
+ *      0
+ *        The request completed successfully. This does not
+ *        mean the requested services were found
+ *      -1
+ *        The request completed unsuccessfully due to a timeout
+ *
+ *    sdp_list_t *rsp_list
+ *      This variable is set on a successful return to point to
+ *      service(s) found. Each element of this list is of type
+ *      sdp_record_t *.
+ */
+int sdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list, sdp_list_t **rsp_list);
+
+/*
+ * Allocate/free a service record and its attributes
+ */
+sdp_record_t *sdp_record_alloc(void);
+void sdp_record_free(sdp_record_t *rec);
+
+/*
+ * Register a service record.
+ *
+ * Note: It is the responsbility of the Service Provider to create the
+ * record first and set its attributes using setXXX() methods.
+ *
+ * The service provider must then call sdp_record_register() to make
+ * the service record visible to SDP clients.  This function returns 0
+ * on success or -1 on failure (and sets errno).
+ */
+int sdp_device_record_register_binary(sdp_session_t *session, bdaddr_t *device, uint8_t *data, uint32_t size, uint8_t flags, uint32_t *handle);
+int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags);
+int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags);
+
+/*
+ * Unregister a service record.
+ */
+int sdp_device_record_unregister_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle);
+int sdp_device_record_unregister(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec);
+int sdp_record_unregister(sdp_session_t *session, sdp_record_t *rec);
+
+/*
+ * Update an existing service record.  (Calling this function
+ * before a previous call to sdp_record_register() will result
+ * in an error.)
+ */
+int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle, uint8_t *data, uint32_t size);
+int sdp_device_record_update(sdp_session_t *session, bdaddr_t *device, const sdp_record_t *rec);
+int sdp_record_update(sdp_session_t *sess, const sdp_record_t *rec);
+
+void sdp_record_print(const sdp_record_t *rec);
+
+/*
+ * UUID functions
+ */
+uuid_t *sdp_uuid16_create(uuid_t *uuid, uint16_t data);
+uuid_t *sdp_uuid32_create(uuid_t *uuid, uint32_t data);
+uuid_t *sdp_uuid128_create(uuid_t *uuid, const void *data);
+int sdp_uuid16_cmp(const void *p1, const void *p2);
+int sdp_uuid128_cmp(const void *p1, const void *p2);
+int sdp_uuid_cmp(const void *p1, const void *p2);
+uuid_t *sdp_uuid_to_uuid128(const uuid_t *uuid);
+void sdp_uuid16_to_uuid128(uuid_t *uuid128, const uuid_t *uuid16);
+void sdp_uuid32_to_uuid128(uuid_t *uuid128, const uuid_t *uuid32);
+int sdp_uuid128_to_uuid(uuid_t *uuid);
+int sdp_uuid_to_proto(uuid_t *uuid);
+int sdp_uuid_extract(const uint8_t *buffer, int bufsize, uuid_t *uuid, int *scanned);
+void sdp_uuid_print(const uuid_t *uuid);
+
+#define MAX_LEN_UUID_STR 37
+#define MAX_LEN_PROTOCOL_UUID_STR 8
+#define MAX_LEN_SERVICECLASS_UUID_STR 28
+#define MAX_LEN_PROFILEDESCRIPTOR_UUID_STR 28
+
+int sdp_uuid2strn(const uuid_t *uuid, char *str, size_t n);
+int sdp_proto_uuid2strn(const uuid_t *uuid, char *str, size_t n);
+int sdp_svclass_uuid2strn(const uuid_t *uuid, char *str, size_t n);
+int sdp_profile_uuid2strn(const uuid_t *uuid, char *str, size_t n);
+
+/*
+ * In all the sdp_get_XXX(handle, XXX *xxx) functions below,
+ * the XXX * is set to point to the value, should it exist
+ * and 0 is returned. If the value does not exist, -1 is
+ * returned and errno set to ENODATA.
+ *
+ * In all the methods below, the memory management rules are
+ * simple. Don't free anything! The pointer returned, in the
+ * case of constructed types, is a pointer to the contents
+ * of the sdp_record_t.
+ */
+
+/*
+ * Get the access protocols from the service record
+ */
+int sdp_get_access_protos(const sdp_record_t *rec, sdp_list_t **protos);
+
+/*
+ * Get the additional access protocols from the service record
+ */
+int sdp_get_add_access_protos(const sdp_record_t *rec, sdp_list_t **protos);
+
+/*
+ * Extract the list of browse groups to which the service belongs.
+ * When set, seqp contains elements of GroupID (uint16_t)
+ */
+static inline int sdp_get_browse_groups(const sdp_record_t *rec, sdp_list_t **seqp)
+{
+       return sdp_get_uuidseq_attr(rec, SDP_ATTR_BROWSE_GRP_LIST, seqp);
+}
+
+/*
+ * Extract language attribute meta-data of the service record.
+ * For each language in the service record, LangSeq has a struct of type
+ * sdp_lang_attr_t.
+ */
+int sdp_get_lang_attr(const sdp_record_t *rec, sdp_list_t **langSeq);
+
+/*
+ * Extract the Bluetooth profile descriptor sequence from a record.
+ * Each element in the list is of type sdp_profile_desc_t
+ * which contains the UUID of the profile and its version number
+ * (encoded as major and minor in the high-order 8bits
+ * and low-order 8bits respectively of the uint16_t)
+ */
+int sdp_get_profile_descs(const sdp_record_t *rec, sdp_list_t **profDesc);
+
+/*
+ * Extract SDP server version numbers
+ *
+ * Note: that this is an attribute of the SDP server only and
+ * contains a list of uint16_t each of which represent the
+ * major and minor SDP version numbers supported by this server
+ */
+int sdp_get_server_ver(const sdp_record_t *rec, sdp_list_t **pVnumList);
+
+int sdp_get_service_id(const sdp_record_t *rec, uuid_t *uuid);
+int sdp_get_group_id(const sdp_record_t *rec, uuid_t *uuid);
+int sdp_get_record_state(const sdp_record_t *rec, uint32_t *svcRecState);
+int sdp_get_service_avail(const sdp_record_t *rec, uint8_t *svcAvail);
+int sdp_get_service_ttl(const sdp_record_t *rec, uint32_t *svcTTLInfo);
+int sdp_get_database_state(const sdp_record_t *rec, uint32_t *svcDBState);
+
+static inline int sdp_get_service_name(const sdp_record_t *rec, char *str, int len)
+{
+       return sdp_get_string_attr(rec, SDP_ATTR_SVCNAME_PRIMARY, str, len);
+}
+
+static inline int sdp_get_service_desc(const sdp_record_t *rec, char *str, int len)
+{
+       return sdp_get_string_attr(rec, SDP_ATTR_SVCDESC_PRIMARY, str, len);
+}
+
+static inline int sdp_get_provider_name(const sdp_record_t *rec, char *str, int len)
+{
+       return sdp_get_string_attr(rec, SDP_ATTR_PROVNAME_PRIMARY, str, len);
+}
+
+static inline int sdp_get_doc_url(const sdp_record_t *rec, char *str, int len)
+{
+       return sdp_get_string_attr(rec, SDP_ATTR_DOC_URL, str, len);
+}
+
+static inline int sdp_get_clnt_exec_url(const sdp_record_t *rec, char *str, int len)
+{
+       return sdp_get_string_attr(rec, SDP_ATTR_CLNT_EXEC_URL, str, len);
+}
+
+static inline int sdp_get_icon_url(const sdp_record_t *rec, char *str, int len)
+{
+       return sdp_get_string_attr(rec, SDP_ATTR_ICON_URL, str, len);
+}
+
+/*
+ * Set the supported features
+ * sf should be a list of list with each feature data
+ * Returns 0 on success -1 on fail
+ */
+int sdp_set_supp_feat(sdp_record_t *rec, const sdp_list_t *sf);
+
+/*
+ * Get the supported features
+ * seqp is set to a list of list with each feature data
+ * Returns 0 on success, if an error occurred -1 is returned and errno is set
+ */
+int sdp_get_supp_feat(const sdp_record_t *rec, sdp_list_t **seqp);
+
+sdp_record_t *sdp_extract_pdu(const uint8_t *pdata, int bufsize, int *scanned);
+sdp_record_t *sdp_copy_record(sdp_record_t *rec);
+
+void sdp_data_print(sdp_data_t *data);
+void sdp_print_service_attr(sdp_list_t *alist);
+
+int sdp_attrid_comp_func(const void *key1, const void *key2);
+
+void sdp_set_seq_len(uint8_t *ptr, uint32_t length);
+void sdp_set_attrid(sdp_buf_t *pdu, uint16_t id);
+void sdp_append_to_pdu(sdp_buf_t *dst, sdp_data_t *d);
+void sdp_append_to_buf(sdp_buf_t *dst, uint8_t *data, uint32_t len);
+
+int sdp_gen_pdu(sdp_buf_t *pdu, sdp_data_t *data);
+int sdp_gen_record_pdu(const sdp_record_t *rec, sdp_buf_t *pdu);
+
+int sdp_extract_seqtype(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size);
+
+sdp_data_t *sdp_extract_attr(const uint8_t *pdata, int bufsize, int *extractedLength, sdp_record_t *rec);
+
+void sdp_pattern_add_uuid(sdp_record_t *rec, uuid_t *uuid);
+void sdp_pattern_add_uuidseq(sdp_record_t *rec, sdp_list_t *seq);
+
+int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *req, uint8_t *rsp, uint32_t reqsize, uint32_t *rspsize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SDP_LIB_H */
diff --git a/lib/uuid.c b/lib/uuid.c
new file mode 100644 (file)
index 0000000..325016a
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *  Copyright (C) 2011  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "uuid.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+static uint128_t bluetooth_base_uuid = {
+       .data = {       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+                       0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }
+};
+
+#define BASE_UUID16_OFFSET     2
+#define BASE_UUID32_OFFSET     0
+
+#else
+static uint128_t bluetooth_base_uuid = {
+       .data = {       0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
+                       0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+};
+
+#define BASE_UUID16_OFFSET     12
+#define BASE_UUID32_OFFSET     BASE_UUID16_OFFSET
+
+#endif
+
+static void bt_uuid16_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst)
+{
+       dst->value.u128 = bluetooth_base_uuid;
+       dst->type = BT_UUID128;
+
+       memcpy(&dst->value.u128.data[BASE_UUID16_OFFSET],
+                       &src->value.u16, sizeof(src->value.u16));
+}
+
+static void bt_uuid32_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst)
+{
+       dst->value.u128 = bluetooth_base_uuid;
+       dst->type = BT_UUID128;
+
+       memcpy(&dst->value.u128.data[BASE_UUID32_OFFSET],
+                               &src->value.u32, sizeof(src->value.u32));
+}
+
+void bt_uuid_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst)
+{
+       switch (src->type) {
+       case BT_UUID128:
+               memcpy(dst, src, sizeof(bt_uuid_t));
+               break;
+       case BT_UUID32:
+               bt_uuid32_to_uuid128(src, dst);
+               break;
+       case BT_UUID16:
+               bt_uuid16_to_uuid128(src, dst);
+               break;
+       default:
+               break;
+       }
+}
+
+static int bt_uuid128_cmp(const bt_uuid_t *u1, const bt_uuid_t *u2)
+{
+       return memcmp(&u1->value.u128, &u2->value.u128, sizeof(uint128_t));
+}
+
+int bt_uuid16_create(bt_uuid_t *btuuid, uint16_t value)
+{
+       memset(btuuid, 0, sizeof(bt_uuid_t));
+       btuuid->type = BT_UUID16;
+       btuuid->value.u16 = value;
+
+       return 0;
+}
+
+int bt_uuid32_create(bt_uuid_t *btuuid, uint32_t value)
+{
+       memset(btuuid, 0, sizeof(bt_uuid_t));
+       btuuid->type = BT_UUID32;
+       btuuid->value.u32 = value;
+
+       return 0;
+}
+
+int bt_uuid128_create(bt_uuid_t *btuuid, uint128_t value)
+{
+       memset(btuuid, 0, sizeof(bt_uuid_t));
+       btuuid->type = BT_UUID128;
+       btuuid->value.u128 = value;
+
+       return 0;
+}
+
+int bt_uuid_cmp(const bt_uuid_t *uuid1, const bt_uuid_t *uuid2)
+{
+       bt_uuid_t u1, u2;
+
+       bt_uuid_to_uuid128(uuid1, &u1);
+       bt_uuid_to_uuid128(uuid2, &u2);
+
+       return bt_uuid128_cmp(&u1, &u2);
+}
+
+/*
+ * convert the UUID to string, copying a maximum of n characters.
+ */
+int bt_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n)
+{
+       if (!uuid) {
+               snprintf(str, n, "NULL");
+               return -EINVAL;
+       }
+
+       switch (uuid->type) {
+       case BT_UUID16:
+               snprintf(str, n, "%.4x", uuid->value.u16);
+               break;
+       case BT_UUID32:
+               snprintf(str, n, "%.8x", uuid->value.u32);
+               break;
+       case BT_UUID128: {
+               unsigned int   data0;
+               unsigned short data1;
+               unsigned short data2;
+               unsigned short data3;
+               unsigned int   data4;
+               unsigned short data5;
+
+               uint128_t nvalue;
+               const uint8_t *data = (uint8_t *) &nvalue;
+
+               hton128(&uuid->value.u128, &nvalue);
+
+               memcpy(&data0, &data[0], 4);
+               memcpy(&data1, &data[4], 2);
+               memcpy(&data2, &data[6], 2);
+               memcpy(&data3, &data[8], 2);
+               memcpy(&data4, &data[10], 4);
+               memcpy(&data5, &data[14], 2);
+
+               snprintf(str, n, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x",
+                               ntohl(data0), ntohs(data1),
+                               ntohs(data2), ntohs(data3),
+                               ntohl(data4), ntohs(data5));
+               }
+               break;
+       default:
+               snprintf(str, n, "Type of UUID (%x) unknown.", uuid->type);
+               return -EINVAL; /* Enum type of UUID not set */
+       }
+
+       return 0;
+}
+
+static inline int is_uuid128(const char *string)
+{
+       return (strlen(string) == 36 &&
+                       string[8] == '-' &&
+                       string[13] == '-' &&
+                       string[18] == '-' &&
+                       string[23] == '-');
+}
+
+static inline int is_uuid32(const char *string)
+{
+       return (strlen(string) == 8 || strlen(string) == 10);
+}
+
+static inline int is_uuid16(const char *string)
+{
+       return (strlen(string) == 4 || strlen(string) == 6);
+}
+
+static int bt_string_to_uuid16(bt_uuid_t *uuid, const char *string)
+{
+       uint16_t u16;
+       char *endptr = NULL;
+
+       u16 = strtol(string, &endptr, 16);
+       if (endptr && *endptr == '\0') {
+               bt_uuid16_create(uuid, u16);
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int bt_string_to_uuid32(bt_uuid_t *uuid, const char *string)
+{
+       uint32_t u32;
+       char *endptr = NULL;
+
+       u32 = strtol(string, &endptr, 16);
+       if (endptr && *endptr == '\0') {
+               bt_uuid32_create(uuid, u32);
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int bt_string_to_uuid128(bt_uuid_t *uuid, const char *string)
+{
+       uint32_t data0, data4;
+       uint16_t data1, data2, data3, data5;
+       uint128_t n128, u128;
+       uint8_t *val = (uint8_t *) &n128;
+
+       if (sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
+                               &data0, &data1, &data2,
+                               &data3, &data4, &data5) != 6)
+               return -EINVAL;
+
+       data0 = htonl(data0);
+       data1 = htons(data1);
+       data2 = htons(data2);
+       data3 = htons(data3);
+       data4 = htonl(data4);
+       data5 = htons(data5);
+
+       memcpy(&val[0], &data0, 4);
+       memcpy(&val[4], &data1, 2);
+       memcpy(&val[6], &data2, 2);
+       memcpy(&val[8], &data3, 2);
+       memcpy(&val[10], &data4, 4);
+       memcpy(&val[14], &data5, 2);
+
+       ntoh128(&n128, &u128);
+
+       bt_uuid128_create(uuid, u128);
+
+       return 0;
+}
+
+int bt_string_to_uuid(bt_uuid_t *uuid, const char *string)
+{
+       if (is_uuid128(string))
+               return bt_string_to_uuid128(uuid, string);
+       else if (is_uuid32(string))
+               return bt_string_to_uuid32(uuid, string);
+       else if (is_uuid16(string))
+               return bt_string_to_uuid16(uuid, string);
+
+       return -EINVAL;
+}
diff --git a/lib/uuid.h b/lib/uuid.h
new file mode 100644 (file)
index 0000000..9c082d3
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *  Copyright (C) 2011  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 __BLUETOOTH_UUID_H
+#define __BLUETOOTH_UUID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <bluetooth/bluetooth.h>
+
+typedef struct {
+       enum {
+               BT_UUID_UNSPEC = 0,
+               BT_UUID16 = 16,
+               BT_UUID32 = 32,
+               BT_UUID128 = 128,
+       } type;
+       union {
+               uint16_t  u16;
+               uint32_t  u32;
+               uint128_t u128;
+       } value;
+} bt_uuid_t;
+
+int bt_uuid16_create(bt_uuid_t *btuuid, uint16_t value);
+int bt_uuid32_create(bt_uuid_t *btuuid, uint32_t value);
+int bt_uuid128_create(bt_uuid_t *btuuid, uint128_t value);
+
+int bt_uuid_cmp(const bt_uuid_t *uuid1, const bt_uuid_t *uuid2);
+void bt_uuid_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst);
+
+#define MAX_LEN_UUID_STR 37
+
+int bt_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n);
+int bt_string_to_uuid(bt_uuid_t *uuid, const char *string);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __BLUETOOTH_UUID_H */
diff --git a/network/common.c b/network/common.c
new file mode 100644 (file)
index 0000000..3813205
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <linux/sockios.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/bnep.h>
+
+#include <glib.h>
+
+#include "log.h"
+#include "common.h"
+
+static int ctl;
+
+static struct {
+       const char      *name;          /* Friendly name */
+       const char      *uuid128;       /* UUID 128 */
+       uint16_t        id;             /* Service class identifier */
+} __svc[] = {
+       { "panu",       PANU_UUID,      BNEP_SVC_PANU   },
+       { "gn",         GN_UUID,        BNEP_SVC_GN     },
+       { "nap",        NAP_UUID,       BNEP_SVC_NAP    },
+       { NULL }
+};
+
+#ifdef  __TIZEN_PATCH__
+struct bnep_data {
+       char *devname;
+       char *script;
+       int pid;
+};
+
+struct bnep_data data;
+#endif
+
+
+
+uint16_t bnep_service_id(const char *svc)
+{
+       int i;
+       uint16_t id;
+
+       /* Friendly service name */
+       for (i = 0; __svc[i].name; i++)
+               if (!strcasecmp(svc, __svc[i].name)) {
+                       return __svc[i].id;
+               }
+
+       /* UUID 128 string */
+       for (i = 0; __svc[i].uuid128; i++)
+               if (!strcasecmp(svc, __svc[i].uuid128)) {
+                       return __svc[i].id;
+               }
+
+       /* Try convert to HEX */
+       id = strtol(svc, NULL, 16);
+       if ((id < BNEP_SVC_PANU) || (id > BNEP_SVC_GN))
+               return 0;
+
+       return id;
+}
+
+const char *bnep_uuid(uint16_t id)
+{
+       int i;
+
+       for (i = 0; __svc[i].uuid128; i++)
+               if (__svc[i].id == id)
+                       return __svc[i].uuid128;
+       return NULL;
+}
+
+const char *bnep_name(uint16_t id)
+{
+       int i;
+
+       for (i = 0; __svc[i].name; i++)
+               if (__svc[i].id == id)
+                       return __svc[i].name;
+       return NULL;
+}
+
+int bnep_init(void)
+{
+       ctl = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_BNEP);
+
+       if (ctl < 0) {
+               int err = errno;
+               error("Failed to open control socket: %s (%d)",
+                                               strerror(err), err);
+               return -err;
+       }
+
+       return 0;
+}
+
+int bnep_cleanup(void)
+{
+       close(ctl);
+       return 0;
+}
+
+int bnep_kill_connection(bdaddr_t *dst)
+{
+       struct bnep_conndel_req req;
+
+       memset(&req, 0, sizeof(req));
+       baswap((bdaddr_t *)&req.dst, dst);
+       req.flags = 0;
+       if (ioctl(ctl, BNEPCONNDEL, &req)) {
+               int err = errno;
+               error("Failed to kill connection: %s (%d)",
+                                               strerror(err), err);
+               return -err;
+       }
+       return 0;
+}
+
+int bnep_kill_all_connections(void)
+{
+       struct bnep_connlist_req req;
+       struct bnep_conninfo ci[7];
+       unsigned int i;
+       int err;
+
+       memset(&req, 0, sizeof(req));
+       req.cnum = 7;
+       req.ci   = ci;
+       if (ioctl(ctl, BNEPGETCONNLIST, &req)) {
+               err = errno;
+               error("Failed to get connection list: %s (%d)",
+                                               strerror(err), err);
+               return -err;
+       }
+
+       for (i = 0; i < req.cnum; i++) {
+               struct bnep_conndel_req del;
+
+               memset(&del, 0, sizeof(del));
+               memcpy(del.dst, ci[i].dst, ETH_ALEN);
+               del.flags = 0;
+               ioctl(ctl, BNEPCONNDEL, &del);
+       }
+       return 0;
+}
+
+int bnep_connadd(int sk, uint16_t role, char *dev)
+{
+       struct bnep_connadd_req req;
+
+       memset(&req, 0, sizeof(req));
+       strncpy(req.device, dev, 16);
+       req.device[15] = '\0';
+       req.sock = sk;
+       req.role = role;
+       if (ioctl(ctl, BNEPCONNADD, &req) < 0) {
+               int err = errno;
+               error("Failed to add device %s: %s(%d)",
+                               dev, strerror(err), err);
+               return -err;
+       }
+
+       strncpy(dev, req.device, 16);
+       return 0;
+}
+
+#ifdef  __TIZEN_PATCH__
+static void bnep_child_setup(gpointer data)
+{
+}
+
+void bnep_server_connect(char *devname)
+{
+       // Run bluetooth-ics
+       const char *argv[5];
+       GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH;
+
+       argv[0] = "bluetooth-ics";
+       argv[1] = devname;
+       argv[2] = NULL;
+       argv[3] = NULL;
+
+       if (!g_spawn_async(NULL, (char **) argv, NULL, flags, bnep_child_setup, NULL,
+                               &data.pid, NULL)) {
+               error("Unable to execute %s %s", argv[0], argv[1]);
+       }
+}
+
+void bnep_server_disconnect(void)
+{
+       int err = -1;
+
+       // Kill bluetooth-ics
+       info("data.pid: %d", data.pid);
+
+       if (data.pid > 0)
+       {
+               /* Kill script */
+               err = kill(data.pid, SIGTERM);
+               if (err < 0)
+                       error("kill(%d, SIGTERM): %s (%d)", data.pid, strerror(errno), errno);
+
+               data.pid = 0;
+       }
+}
+#endif
+
+int bnep_if_up(const char *devname)
+{
+       struct ifreq ifr;
+       int sk, err;
+
+       sk = socket(AF_INET, SOCK_DGRAM, 0);
+
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy(ifr.ifr_name, devname, IF_NAMESIZE - 1);
+
+       ifr.ifr_flags |= IFF_UP;
+       ifr.ifr_flags |= IFF_MULTICAST;
+
+       err = ioctl(sk, SIOCSIFFLAGS, (caddr_t) &ifr);
+
+       close(sk);
+
+       if (err < 0) {
+               error("Could not bring up %s", devname);
+               return err;
+       }
+
+       return 0;
+}
+
+int bnep_if_down(const char *devname)
+{
+       struct ifreq ifr;
+       int sk, err;
+
+       sk = socket(AF_INET, SOCK_DGRAM, 0);
+
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy(ifr.ifr_name, devname, IF_NAMESIZE - 1);
+
+       ifr.ifr_flags &= ~IFF_UP;
+
+       /* Bring down the interface */
+       err = ioctl(sk, SIOCSIFFLAGS, (caddr_t) &ifr);
+
+       close(sk);
+
+       return 0;
+}
+
+int bnep_add_to_bridge(const char *devname, const char *bridge)
+{
+       int ifindex = if_nametoindex(devname);
+       struct ifreq ifr;
+       int sk, err;
+
+       if (!devname || !bridge)
+               return -EINVAL;
+
+       sk = socket(AF_INET, SOCK_STREAM, 0);
+       if (sk < 0)
+               return -1;
+
+#ifdef  __TIZEN_PATCH__
+       err = ioctl(sk, SIOCBRADDBR, bridge);
+       if (err < 0)
+       {
+               info("bridge create err: %d", err);
+               close(sk);
+               return -errno;
+       }
+#endif
+
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1);
+       ifr.ifr_ifindex = ifindex;
+
+       err = ioctl(sk, SIOCBRADDIF, &ifr);
+
+       close(sk);
+
+       if (err < 0)
+               return err;
+
+       info("bridge %s: interface %s added", bridge, devname);
+
+       return 0;
+}
diff --git a/network/common.h b/network/common.h
new file mode 100644 (file)
index 0000000..47dba9d
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 PANU_UUID      "00001115-0000-1000-8000-00805f9b34fb"
+#define NAP_UUID       "00001116-0000-1000-8000-00805f9b34fb"
+#define GN_UUID                "00001117-0000-1000-8000-00805f9b34fb"
+#define BNEP_SVC_UUID  "0000000f-0000-1000-8000-00805f9b34fb"
+
+int bnep_init(void);
+int bnep_cleanup(void);
+
+uint16_t bnep_service_id(const char *svc);
+const char *bnep_uuid(uint16_t id);
+const char *bnep_name(uint16_t id);
+
+int bnep_kill_connection(bdaddr_t *dst);
+int bnep_kill_all_connections(void);
+
+int bnep_connadd(int sk, uint16_t role, char *dev);
+int bnep_if_up(const char *devname);
+int bnep_if_down(const char *devname);
+int bnep_add_to_bridge(const char *devname, const char *bridge);
+
+#ifndef  __TIZEN_PATCH__
+void bnep_server_connect(char *devname);
+void bnep_server_disconnect(void);
+#endif
diff --git a/network/connection.c b/network/connection.c
new file mode 100644 (file)
index 0000000..943aca4
--- /dev/null
@@ -0,0 +1,643 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/bnep.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "dbus-common.h"
+#include "adapter.h"
+#include "device.h"
+
+#include "error.h"
+#include "common.h"
+#include "connection.h"
+
+#define NETWORK_PEER_INTERFACE "org.bluez.Network"
+
+typedef enum {
+       CONNECTED,
+       CONNECTING,
+       DISCONNECTED
+} conn_state;
+
+struct network_peer {
+       bdaddr_t        src;
+       bdaddr_t        dst;
+       char            *path;          /* D-Bus path */
+       struct btd_device *device;
+       GSList          *connections;
+};
+
+struct network_conn {
+       DBusMessage     *msg;
+       char            dev[16];        /* Interface name */
+       uint16_t        id;             /* Role: Service Class Identifier */
+       conn_state      state;
+       GIOChannel      *io;
+       guint           watch;          /* Disconnect watch */
+       guint           dc_id;
+       struct network_peer *peer;
+};
+
+struct __service_16 {
+       uint16_t dst;
+       uint16_t src;
+} __attribute__ ((packed));
+
+static DBusConnection *connection = NULL;
+static GSList *peers = NULL;
+
+static struct network_peer *find_peer(GSList *list, const char *path)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct network_peer *peer = l->data;
+
+               if (!strcmp(peer->path, path))
+                       return peer;
+       }
+
+       return NULL;
+}
+
+static struct network_conn *find_connection(GSList *list, uint16_t id)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct network_conn *nc = l->data;
+
+               if (nc->id == id)
+                       return nc;
+       }
+
+       return NULL;
+}
+
+static gboolean bnep_watchdog_cb(GIOChannel *chan, GIOCondition cond,
+                               gpointer data)
+{
+       struct network_conn *nc = data;
+
+       if (connection != NULL) {
+               gboolean connected = FALSE;
+               const char *property = "";
+#ifdef __TIZEN_PATCH__
+               char address[20] = {0};
+               const gchar* paddr = address;
+               ba2str(&nc->peer->dst, address);
+#endif
+               emit_property_changed(connection, nc->peer->path,
+                                       NETWORK_PEER_INTERFACE, "Connected",
+                                       DBUS_TYPE_BOOLEAN, &connected);
+               emit_property_changed(connection, nc->peer->path,
+                                       NETWORK_PEER_INTERFACE, "Interface",
+                                       DBUS_TYPE_STRING, &property);
+               emit_property_changed(connection, nc->peer->path,
+                                       NETWORK_PEER_INTERFACE, "UUID",
+                                       DBUS_TYPE_STRING, &property);
+#ifdef __TIZEN_PATCH__
+               emit_property_changed(connection, nc->peer->path,
+                                       NETWORK_PEER_INTERFACE, "Address",
+                                       DBUS_TYPE_STRING, &paddr);
+#endif
+               device_remove_disconnect_watch(nc->peer->device, nc->dc_id);
+               nc->dc_id = 0;
+               if (nc->watch) {
+                       g_dbus_remove_watch(connection, nc->watch);
+                       nc->watch = 0;
+               }
+       }
+
+       info("%s disconnected", nc->dev);
+
+       bnep_if_down(nc->dev);
+       nc->state = DISCONNECTED;
+       memset(nc->dev, 0, sizeof(nc->dev));
+       strcpy(nc->dev, "bnep%d");
+
+       return FALSE;
+}
+
+static void cancel_connection(struct network_conn *nc, const char *err_msg)
+{
+       DBusMessage *reply;
+
+       if (nc->watch) {
+               g_dbus_remove_watch(connection, nc->watch);
+               nc->watch = 0;
+       }
+
+       if (nc->msg && err_msg) {
+               reply = btd_error_failed(nc->msg, err_msg);
+               g_dbus_send_message(connection, reply);
+       }
+
+       g_io_channel_shutdown(nc->io, TRUE, NULL);
+       g_io_channel_unref(nc->io);
+       nc->io = NULL;
+
+       nc->state = DISCONNECTED;
+}
+
+static void connection_destroy(DBusConnection *conn, void *user_data)
+{
+       struct network_conn *nc = user_data;
+
+       if (nc->state == CONNECTED) {
+               bnep_if_down(nc->dev);
+               bnep_kill_connection(&nc->peer->dst);
+       } else if (nc->io)
+               cancel_connection(nc, NULL);
+}
+
+static void disconnect_cb(struct btd_device *device, gboolean removal,
+                               void *user_data)
+{
+       struct network_conn *nc = user_data;
+
+       info("Network: disconnect %s", nc->peer->path);
+
+       connection_destroy(NULL, user_data);
+}
+
+static gboolean bnep_setup_cb(GIOChannel *chan, GIOCondition cond,
+                                                       gpointer data)
+{
+       struct network_conn *nc = data;
+       struct bnep_control_rsp *rsp;
+       struct timeval timeo;
+       char pkt[BNEP_MTU];
+       ssize_t r;
+       int sk;
+       const char *pdev, *uuid;
+       gboolean connected;
+#ifdef __TIZEN_PATCH__
+       char address[20] = {0};
+       const gchar* paddr = address;
+#endif
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       if (cond & (G_IO_HUP | G_IO_ERR)) {
+               error("Hangup or error on l2cap server socket");
+               goto failed;
+       }
+
+       sk = g_io_channel_unix_get_fd(chan);
+
+       memset(pkt, 0, BNEP_MTU);
+       r = read(sk, pkt, sizeof(pkt) -1);
+       if (r < 0) {
+               error("IO Channel read error");
+               goto failed;
+       }
+
+       if (r == 0) {
+               error("No packet received on l2cap socket");
+               goto failed;
+       }
+
+       errno = EPROTO;
+
+       if ((size_t) r < sizeof(*rsp)) {
+               error("Packet received is not bnep type");
+               goto failed;
+       }
+
+       rsp = (void *) pkt;
+       if (rsp->type != BNEP_CONTROL) {
+               error("Packet received is not bnep type");
+               goto failed;
+       }
+
+       if (rsp->ctrl != BNEP_SETUP_CONN_RSP)
+               return TRUE;
+
+       r = ntohs(rsp->resp);
+
+       if (r != BNEP_SUCCESS) {
+               error("bnep failed");
+               goto failed;
+       }
+
+       memset(&timeo, 0, sizeof(timeo));
+       timeo.tv_sec = 0;
+
+       setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+
+       if (bnep_connadd(sk, BNEP_SVC_PANU, nc->dev)) {
+               error("%s could not be added", nc->dev);
+               goto failed;
+       }
+
+       bnep_if_up(nc->dev);
+       pdev = nc->dev;
+       uuid = bnep_uuid(nc->id);
+
+       g_dbus_send_reply(connection, nc->msg,
+                       DBUS_TYPE_STRING, &pdev,
+                       DBUS_TYPE_INVALID);
+
+       connected = TRUE;
+#ifdef __TIZEN_PATCH__
+       ba2str(&nc->peer->dst, address);
+#endif
+       emit_property_changed(connection, nc->peer->path,
+                               NETWORK_PEER_INTERFACE, "Connected",
+                               DBUS_TYPE_BOOLEAN, &connected);
+       emit_property_changed(connection, nc->peer->path,
+                               NETWORK_PEER_INTERFACE, "Interface",
+                               DBUS_TYPE_STRING, &pdev);
+       emit_property_changed(connection, nc->peer->path,
+                               NETWORK_PEER_INTERFACE, "UUID",
+                               DBUS_TYPE_STRING, &uuid);
+#ifdef __TIZEN_PATCH__
+       emit_property_changed(connection, nc->peer->path,
+                               NETWORK_PEER_INTERFACE, "Address",
+                               DBUS_TYPE_STRING, &paddr);
+#endif
+       nc->state = CONNECTED;
+       nc->dc_id = device_add_disconnect_watch(nc->peer->device, disconnect_cb,
+                                               nc, NULL);
+
+       info("%s connected", nc->dev);
+       /* Start watchdog */
+       g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                       (GIOFunc) bnep_watchdog_cb, nc);
+       g_io_channel_unref(nc->io);
+       nc->io = NULL;
+
+       return FALSE;
+
+failed:
+       cancel_connection(nc, "bnep setup failed");
+
+       return FALSE;
+}
+
+static int bnep_connect(struct network_conn *nc)
+{
+       struct bnep_setup_conn_req *req;
+       struct __service_16 *s;
+       struct timeval timeo;
+       unsigned char pkt[BNEP_MTU];
+       int fd;
+
+       /* Send request */
+       req = (void *) pkt;
+       req->type = BNEP_CONTROL;
+       req->ctrl = BNEP_SETUP_CONN_REQ;
+       req->uuid_size = 2;     /* 16bit UUID */
+       s = (void *) req->service;
+       s->dst = htons(nc->id);
+       s->src = htons(BNEP_SVC_PANU);
+
+       memset(&timeo, 0, sizeof(timeo));
+       timeo.tv_sec = 30;
+
+       fd = g_io_channel_unix_get_fd(nc->io);
+       setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+
+       if (send(fd, pkt, sizeof(*req) + sizeof(*s), 0) < 0)
+               return -errno;
+
+       g_io_add_watch(nc->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                       (GIOFunc) bnep_setup_cb, nc);
+
+       return 0;
+}
+
+static void connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+       struct network_conn *nc = data;
+       const char *err_msg;
+       int perr;
+
+       if (err) {
+               error("%s", err->message);
+               err_msg = err->message;
+               goto failed;
+       }
+
+       perr = bnep_connect(nc);
+       if (perr < 0) {
+               err_msg = strerror(-perr);
+               error("bnep connect(): %s (%d)", err_msg, -perr);
+               goto failed;
+       }
+
+       return;
+
+failed:
+       cancel_connection(nc, err_msg);
+}
+
+/* Connect and initiate BNEP session */
+static DBusMessage *connection_connect(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct network_peer *peer = data;
+       struct network_conn *nc;
+       const char *svc;
+       uint16_t id;
+       GError *err = NULL;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &svc,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return NULL;
+
+       id = bnep_service_id(svc);
+       nc = find_connection(peer->connections, id);
+       if (!nc)
+               return btd_error_not_supported(msg);
+
+       if (nc->state != DISCONNECTED)
+               return btd_error_already_connected(msg);
+
+       nc->io = bt_io_connect(BT_IO_L2CAP, connect_cb, nc,
+                               NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, &peer->src,
+                               BT_IO_OPT_DEST_BDADDR, &peer->dst,
+                               BT_IO_OPT_PSM, BNEP_PSM,
+                               BT_IO_OPT_OMTU, BNEP_MTU,
+                               BT_IO_OPT_IMTU, BNEP_MTU,
+                               BT_IO_OPT_INVALID);
+       if (!nc->io) {
+               DBusMessage *reply;
+               error("%s", err->message);
+               reply = btd_error_failed(msg, err->message);
+               g_error_free(err);
+               return reply;
+       }
+
+       nc->state = CONNECTING;
+       nc->msg = dbus_message_ref(msg);
+       nc->watch = g_dbus_add_disconnect_watch(conn,
+                                               dbus_message_get_sender(msg),
+                                               connection_destroy,
+                                               nc, NULL);
+
+       return NULL;
+}
+
+static DBusMessage *connection_cancel(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct network_conn *nc = data;
+       const char *owner = dbus_message_get_sender(nc->msg);
+       const char *caller = dbus_message_get_sender(msg);
+
+       if (!g_str_equal(owner, caller))
+               return btd_error_not_authorized(msg);
+
+       connection_destroy(conn, nc);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *connection_disconnect(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct network_peer *peer = data;
+       GSList *l;
+
+       for (l = peer->connections; l; l = l->next) {
+               struct network_conn *nc = l->data;
+
+               if (nc->state == DISCONNECTED)
+                       continue;
+
+               return connection_cancel(conn, msg, nc);
+       }
+
+       return btd_error_not_connected(msg);
+}
+
+static DBusMessage *connection_get_properties(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct network_peer *peer = data;
+       struct network_conn *nc = NULL;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       DBusMessageIter dict;
+       dbus_bool_t connected;
+       const char *property;
+       GSList *l;
+
+       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 */
+       for (l = peer->connections; l; l = l->next) {
+               struct network_conn *tmp = l->data;
+
+               if (tmp->state != CONNECTED)
+                       continue;
+
+               nc = tmp;
+               break;
+       }
+
+       connected = nc ? TRUE : FALSE;
+       dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+       /* Interface */
+       property = nc ? nc->dev : "";
+       dict_append_entry(&dict, "Interface", DBUS_TYPE_STRING, &property);
+
+       /* UUID */
+       property = nc ? bnep_uuid(nc->id) : "";
+       dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &property);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static void connection_free(struct network_conn *nc)
+{
+       if (nc->dc_id)
+               device_remove_disconnect_watch(nc->peer->device, nc->dc_id);
+
+       connection_destroy(connection, nc);
+
+       g_free(nc);
+       nc = NULL;
+}
+
+static void peer_free(struct network_peer *peer)
+{
+       g_slist_foreach(peer->connections, (GFunc) connection_free, NULL);
+       g_slist_free(peer->connections);
+       btd_device_unref(peer->device);
+       g_free(peer->path);
+       g_free(peer);
+}
+
+static void path_unregister(void *data)
+{
+       struct network_peer *peer = data;
+
+       DBG("Unregistered interface %s on path %s",
+               NETWORK_PEER_INTERFACE, peer->path);
+
+       peers = g_slist_remove(peers, peer);
+       peer_free(peer);
+}
+
+static GDBusMethodTable connection_methods[] = {
+       { "Connect",            "s",    "s",    connection_connect,
+                                               G_DBUS_METHOD_FLAG_ASYNC },
+       { "Disconnect",         "",     "",     connection_disconnect   },
+       { "GetProperties",      "",     "a{sv}",connection_get_properties },
+       { }
+};
+
+static GDBusSignalTable connection_signals[] = {
+       { "PropertyChanged",    "sv"    },
+       { }
+};
+
+void connection_unregister(const char *path, uint16_t id)
+{
+       struct network_peer *peer;
+       struct network_conn *nc;
+
+       peer = find_peer(peers, path);
+       if (!peer)
+               return;
+
+       nc = find_connection(peer->connections, id);
+       if (!nc)
+               return;
+
+       peer->connections = g_slist_remove(peer->connections, nc);
+       connection_free(nc);
+       if (peer->connections)
+               return;
+
+       g_dbus_unregister_interface(connection, path, NETWORK_PEER_INTERFACE);
+}
+
+static struct network_peer *create_peer(struct btd_device *device,
+                                       const char *path, bdaddr_t *src,
+                                       bdaddr_t *dst)
+{
+       struct network_peer *peer;
+
+       peer = g_new0(struct network_peer, 1);
+       peer->device = btd_device_ref(device);
+       peer->path = g_strdup(path);
+       bacpy(&peer->src, src);
+       bacpy(&peer->dst, dst);
+
+       if (g_dbus_register_interface(connection, path,
+                                       NETWORK_PEER_INTERFACE,
+                                       connection_methods,
+                                       connection_signals, NULL,
+                                       peer, path_unregister) == FALSE) {
+               error("D-Bus failed to register %s interface",
+                       NETWORK_PEER_INTERFACE);
+               peer_free(peer);
+               return NULL;
+       }
+
+       DBG("Registered interface %s on path %s",
+               NETWORK_PEER_INTERFACE, path);
+
+       return peer;
+}
+
+int connection_register(struct btd_device *device, const char *path,
+                       bdaddr_t *src, bdaddr_t *dst, uint16_t id)
+{
+       struct network_peer *peer;
+       struct network_conn *nc;
+
+       if (!path)
+               return -EINVAL;
+
+       peer = find_peer(peers, path);
+       if (!peer) {
+               peer = create_peer(device, path, src, dst);
+               if (!peer)
+                       return -1;
+               peers = g_slist_append(peers, peer);
+       }
+
+       nc = find_connection(peer->connections, id);
+       if (nc)
+               return 0;
+
+       nc = g_new0(struct network_conn, 1);
+       nc->id = id;
+       memset(nc->dev, 0, sizeof(nc->dev));
+       strcpy(nc->dev, "bnep%d");
+       nc->state = DISCONNECTED;
+       nc->peer = peer;
+
+       peer->connections = g_slist_append(peer->connections, nc);
+
+       return 0;
+}
+
+int connection_init(DBusConnection *conn)
+{
+       connection = dbus_connection_ref(conn);
+
+       return 0;
+}
+
+void connection_exit(void)
+{
+       dbus_connection_unref(connection);
+       connection = NULL;
+}
diff --git a/network/connection.h b/network/connection.h
new file mode 100644 (file)
index 0000000..5ea4147
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 connection_init(DBusConnection *conn);
+void connection_exit(void);
+int connection_register(struct btd_device *device, const char *path,
+                       bdaddr_t *src, bdaddr_t *dst, uint16_t id);
+void connection_unregister(const char *path, uint16_t id);
diff --git a/network/main.c b/network/main.c
new file mode 100644 (file)
index 0000000..88e77ee
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "manager.h"
+
+static DBusConnection *connection;
+
+static int network_init(void)
+{
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (connection == NULL)
+               return -EIO;
+
+       if (network_manager_init(connection) < 0) {
+               dbus_connection_unref(connection);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void network_exit(void)
+{
+       network_manager_exit();
+
+       dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(network, VERSION,
+                       BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, network_init, network_exit)
diff --git a/network/manager.c b/network/manager.c
new file mode 100644 (file)
index 0000000..321640b
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/bnep.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "log.h"
+
+#include "adapter.h"
+#include "device.h"
+#include "manager.h"
+#include "common.h"
+#include "connection.h"
+#include "server.h"
+
+static DBusConnection *connection = NULL;
+
+static gboolean conf_security = TRUE;
+
+static void read_config(const char *file)
+{
+       GKeyFile *keyfile;
+       GError *err = NULL;
+
+       keyfile = g_key_file_new();
+
+       if (!g_key_file_load_from_file(keyfile, file, 0, &err)) {
+               g_clear_error(&err);
+               goto done;
+       }
+
+       conf_security = !g_key_file_get_boolean(keyfile, "General",
+                                               "DisableSecurity", &err);
+       if (err) {
+               DBG("%s: %s", file, err->message);
+               g_clear_error(&err);
+       }
+
+done:
+       g_key_file_free(keyfile);
+
+       DBG("Config options: Security=%s",
+                               conf_security ? "true" : "false");
+}
+
+static int network_probe(struct btd_device *device, GSList *uuids, uint16_t id)
+{
+       struct btd_adapter *adapter = device_get_adapter(device);
+       const gchar *path = device_get_path(device);
+       bdaddr_t src, dst;
+
+       DBG("path %s", path);
+
+       adapter_get_address(adapter, &src);
+       device_get_address(device, &dst);
+
+       return connection_register(device, path, &src, &dst, id);
+}
+
+static void network_remove(struct btd_device *device, uint16_t id)
+{
+       const gchar *path = device_get_path(device);
+
+       DBG("path %s", path);
+
+       connection_unregister(path, id);
+}
+
+static int panu_probe(struct btd_device *device, GSList *uuids)
+{
+       return network_probe(device, uuids, BNEP_SVC_PANU);
+}
+
+static void panu_remove(struct btd_device *device)
+{
+       network_remove(device, BNEP_SVC_PANU);
+}
+
+static int gn_probe(struct btd_device *device, GSList *uuids)
+{
+       return network_probe(device, uuids, BNEP_SVC_GN);
+}
+
+static void gn_remove(struct btd_device *device)
+{
+       network_remove(device, BNEP_SVC_GN);
+}
+
+static int nap_probe(struct btd_device *device, GSList *uuids)
+{
+       return network_probe(device, uuids, BNEP_SVC_NAP);
+}
+
+static void nap_remove(struct btd_device *device)
+{
+       network_remove(device, BNEP_SVC_NAP);
+}
+
+static int network_server_probe(struct btd_adapter *adapter)
+{
+       const gchar *path = adapter_get_path(adapter);
+
+       DBG("path %s", path);
+
+       return server_register(adapter);
+}
+
+static void network_server_remove(struct btd_adapter *adapter)
+{
+       const gchar *path = adapter_get_path(adapter);
+
+       DBG("path %s", path);
+
+       server_unregister(adapter);
+}
+
+static struct btd_device_driver network_panu_driver = {
+       .name   = "network-panu",
+       .uuids  = BTD_UUIDS(PANU_UUID),
+       .probe  = panu_probe,
+       .remove = panu_remove,
+};
+
+static struct btd_device_driver network_gn_driver = {
+       .name   = "network-gn",
+       .uuids  = BTD_UUIDS(GN_UUID),
+       .probe  = gn_probe,
+       .remove = gn_remove,
+};
+
+static struct btd_device_driver network_nap_driver = {
+       .name   = "network-nap",
+       .uuids  = BTD_UUIDS(NAP_UUID),
+       .probe  = nap_probe,
+       .remove = nap_remove,
+};
+
+static struct btd_adapter_driver network_server_driver = {
+       .name   = "network-server",
+       .probe  = network_server_probe,
+       .remove = network_server_remove,
+};
+
+int network_manager_init(DBusConnection *conn)
+{
+       read_config(CONFIGDIR "/network.conf");
+
+       if (bnep_init()) {
+               error("Can't init bnep module");
+               return -1;
+       }
+
+       /*
+        * There is one socket to handle the incomming connections. NAP,
+        * GN and PANU servers share the same PSM. The initial BNEP message
+        * (setup connection request) contains the destination service
+        * field that defines which service the source is connecting to.
+        */
+
+       if (server_init(conn, conf_security) < 0)
+               return -1;
+
+       /* Register network server if it doesn't exist */
+       btd_register_adapter_driver(&network_server_driver);
+
+       if (connection_init(conn) < 0)
+               return -1;
+
+       btd_register_device_driver(&network_panu_driver);
+       btd_register_device_driver(&network_gn_driver);
+       btd_register_device_driver(&network_nap_driver);
+
+       connection = dbus_connection_ref(conn);
+
+       return 0;
+}
+
+void network_manager_exit(void)
+{
+       server_exit();
+
+       btd_unregister_device_driver(&network_panu_driver);
+       btd_unregister_device_driver(&network_gn_driver);
+       btd_unregister_device_driver(&network_nap_driver);
+
+       connection_exit();
+
+       btd_unregister_adapter_driver(&network_server_driver);
+
+       dbus_connection_unref(connection);
+       connection = NULL;
+
+       bnep_cleanup();
+}
diff --git a/network/manager.h b/network/manager.h
new file mode 100644 (file)
index 0000000..27bc13f
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 network_manager_init(DBusConnection *conn);
+void network_manager_exit(void);
diff --git a/network/network.conf b/network/network.conf
new file mode 100644 (file)
index 0000000..5f11639
--- /dev/null
@@ -0,0 +1,6 @@
+# Configuration file for the network service
+
+[General]
+
+# Disable link encryption: default=false
+#DisableSecurity=true
diff --git a/network/server.c b/network/server.c
new file mode 100644 (file)
index 0000000..5c993d7
--- /dev/null
@@ -0,0 +1,950 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/bnep.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <netinet/in.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/dbus-common.h"
+#include "../src/adapter.h"
+
+#include "log.h"
+#include "error.h"
+#include "sdpd.h"
+#include "btio.h"
+#include "glib-helper.h"
+
+#include "common.h"
+#include "server.h"
+
+#define NETWORK_SERVER_INTERFACE "org.bluez.NetworkServer"
+#define SETUP_TIMEOUT          1
+
+/* Pending Authorization */
+struct network_session {
+       bdaddr_t        dst;            /* Remote Bluetooth Address */
+       GIOChannel      *io;            /* Pending connect channel */
+       guint           watch;          /* BNEP socket watch */
+};
+
+struct network_adapter {
+       struct btd_adapter *adapter;    /* Adapter pointer */
+       GIOChannel      *io;            /* Bnep socket */
+       struct network_session *setup;  /* Setup in progress */
+       GSList          *servers;       /* Server register to adapter */
+};
+
+/* Main server structure */
+struct network_server {
+       bdaddr_t        src;            /* Bluetooth Local Address */
+       char            *iface;         /* DBus interface */
+       char            *name;          /* Server service name */
+       char            *bridge;        /* Bridge name */
+       uint32_t        record_id;      /* Service record id */
+       uint16_t        id;             /* Service class identifier */
+       GSList          *sessions;      /* Active connections */
+       struct network_adapter *na;     /* Adapter reference */
+       guint           watch_id;       /* Client service watch */
+       char            dev[16];        /* Interface name */
+};
+
+static DBusConnection *connection = NULL;
+static GSList *adapters = NULL;
+static gboolean security = TRUE;
+
+#ifdef  __TIZEN_PATCH__
+static gboolean server_disconnected_cb(GIOChannel *chan,
+                       GIOCondition cond, gpointer user_data);
+#endif
+
+static struct network_adapter *find_adapter(GSList *list,
+                                       struct btd_adapter *adapter)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct network_adapter *na = l->data;
+
+               if (na->adapter == adapter)
+                       return na;
+       }
+
+       return NULL;
+}
+
+static struct network_server *find_server(GSList *list, uint16_t id)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct network_server *ns = l->data;
+
+               if (ns->id == id)
+                       return ns;
+       }
+
+       return NULL;
+}
+
+#ifdef  __TIZEN_PATCH__
+static struct network_session *find_session(GSList *list, GIOChannel *io)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct network_session *session = l->data;
+
+               if (session->io == io)
+                       return session;
+       }
+
+       return NULL;
+}
+#endif
+
+static void add_lang_attr(sdp_record_t *r)
+{
+       sdp_lang_attr_t base_lang;
+       sdp_list_t *langs = 0;
+
+       /* UTF-8 MIBenum (http://www.iana.org/assignments/character-sets) */
+       base_lang.code_ISO639 = (0x65 << 8) | 0x6e;
+       base_lang.encoding = 106;
+       base_lang.base_offset = SDP_PRIMARY_LANG_BASE;
+       langs = sdp_list_append(0, &base_lang);
+       sdp_set_lang_attr(r, langs);
+       sdp_list_free(langs, 0);
+}
+
+static sdp_record_t *server_record_new(const char *name, uint16_t id)
+{
+       sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto;
+       uuid_t root_uuid, pan, l2cap, bnep;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *proto[2];
+       sdp_data_t *v, *p;
+       uint16_t psm = BNEP_PSM, version = 0x0100;
+       uint16_t security_desc = (security ? 0x0001 : 0x0000);
+       uint16_t net_access_type = 0xfffe;
+       uint32_t max_net_access_rate = 0;
+       const char *desc = "Network service";
+       sdp_record_t *record;
+
+       record = sdp_record_alloc();
+       if (!record)
+               return NULL;
+
+       record->attrlist = NULL;
+       record->pattern = NULL;
+
+       switch (id) {
+       case BNEP_SVC_NAP:
+               sdp_uuid16_create(&pan, NAP_SVCLASS_ID);
+               svclass = sdp_list_append(NULL, &pan);
+               sdp_set_service_classes(record, svclass);
+
+               sdp_uuid16_create(&profile[0].uuid, NAP_PROFILE_ID);
+               profile[0].version = 0x0100;
+               pfseq = sdp_list_append(NULL, &profile[0]);
+               sdp_set_profile_descs(record, pfseq);
+
+               sdp_set_info_attr(record, name, NULL, desc);
+
+               sdp_attr_add_new(record, SDP_ATTR_NET_ACCESS_TYPE,
+                                       SDP_UINT16, &net_access_type);
+               sdp_attr_add_new(record, SDP_ATTR_MAX_NET_ACCESSRATE,
+                                       SDP_UINT32, &max_net_access_rate);
+               break;
+       case BNEP_SVC_GN:
+               sdp_uuid16_create(&pan, GN_SVCLASS_ID);
+               svclass = sdp_list_append(NULL, &pan);
+               sdp_set_service_classes(record, svclass);
+
+               sdp_uuid16_create(&profile[0].uuid, GN_PROFILE_ID);
+               profile[0].version = 0x0100;
+               pfseq = sdp_list_append(NULL, &profile[0]);
+               sdp_set_profile_descs(record, pfseq);
+
+               sdp_set_info_attr(record, name, NULL, desc);
+               break;
+       case BNEP_SVC_PANU:
+               sdp_uuid16_create(&pan, PANU_SVCLASS_ID);
+               svclass = sdp_list_append(NULL, &pan);
+               sdp_set_service_classes(record, svclass);
+
+               sdp_uuid16_create(&profile[0].uuid, PANU_PROFILE_ID);
+               profile[0].version = 0x0100;
+               pfseq = sdp_list_append(NULL, &profile[0]);
+               sdp_set_profile_descs(record, pfseq);
+
+               sdp_set_info_attr(record, name, NULL, desc);
+               break;
+       default:
+               sdp_record_free(record);
+               return NULL;
+       }
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(record, root);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap);
+       p = sdp_data_alloc(SDP_UINT16, &psm);
+       proto[0] = sdp_list_append(proto[0], p);
+       apseq    = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&bnep, BNEP_UUID);
+       proto[1] = sdp_list_append(NULL, &bnep);
+       v = sdp_data_alloc(SDP_UINT16, &version);
+       proto[1] = sdp_list_append(proto[1], v);
+
+       /* Supported protocols */
+       {
+               uint16_t ptype[] = {
+                       0x0800,  /* IPv4 */
+                       0x0806,  /* ARP */
+               };
+               sdp_data_t *head, *pseq;
+               int p;
+
+               for (p = 0, head = NULL; p < 2; p++) {
+                       sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]);
+                       if (head)
+                               sdp_seq_append(head, data);
+                       else
+                               head = data;
+               }
+               pseq = sdp_data_alloc(SDP_SEQ16, head);
+               proto[1] = sdp_list_append(proto[1], pseq);
+       }
+
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(NULL, apseq);
+       sdp_set_access_protos(record, aproto);
+
+       add_lang_attr(record);
+
+       sdp_attr_add_new(record, SDP_ATTR_SECURITY_DESC,
+                               SDP_UINT16, &security_desc);
+
+       sdp_data_free(p);
+       sdp_data_free(v);
+       sdp_list_free(apseq, NULL);
+       sdp_list_free(root, NULL);
+       sdp_list_free(aproto, NULL);
+       sdp_list_free(proto[0], NULL);
+       sdp_list_free(proto[1], NULL);
+       sdp_list_free(svclass, NULL);
+       sdp_list_free(pfseq, NULL);
+
+       return record;
+}
+
+static ssize_t send_bnep_ctrl_rsp(int sk, uint16_t val)
+{
+       struct bnep_control_rsp rsp;
+
+       rsp.type = BNEP_CONTROL;
+       rsp.ctrl = BNEP_SETUP_CONN_RSP;
+       rsp.resp = htons(val);
+
+       return send(sk, &rsp, sizeof(rsp), 0);
+}
+
+static int server_connadd(struct network_server *ns,
+                               struct network_session *session,
+                               uint16_t dst_role)
+{
+       char devname[16];
+       int err, nsk;
+
+       memset(devname, 0, sizeof(devname));
+       strcpy(devname, "bnep%d");
+
+       nsk = g_io_channel_unix_get_fd(session->io);
+       err = bnep_connadd(nsk, dst_role, devname);
+       if (err < 0)
+               return err;
+
+       info("Added new connection: %s", devname);
+
+#ifdef  __TIZEN_PATCH__
+       {
+               guint watch = 0;
+
+               bnep_if_up(devname);
+               memcpy(ns->dev, devname, sizeof(devname));
+
+               watch = g_io_add_watch_full(session->io, G_PRIORITY_DEFAULT,
+                                       G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                                       server_disconnected_cb, ns, NULL);
+       }
+#else
+       if (bnep_add_to_bridge(devname, ns->bridge) < 0) {
+               error("Can't add %s to the bridge %s: %s(%d)",
+                               devname, ns->bridge, strerror(errno), errno);
+               return -EPERM;
+       }
+
+       bnep_if_up(devname);
+#endif
+
+       ns->sessions = g_slist_append(ns->sessions, session);
+
+       return 0;
+}
+
+static uint16_t bnep_setup_chk(uint16_t dst_role, uint16_t src_role)
+{
+       /* Allowed PAN Profile scenarios */
+       switch (dst_role) {
+       case BNEP_SVC_NAP:
+       case BNEP_SVC_GN:
+               if (src_role == BNEP_SVC_PANU)
+                       return 0;
+               return BNEP_CONN_INVALID_SRC;
+       case BNEP_SVC_PANU:
+               if (src_role == BNEP_SVC_PANU ||
+                               src_role == BNEP_SVC_GN ||
+                               src_role == BNEP_SVC_NAP)
+                       return 0;
+
+               return BNEP_CONN_INVALID_SRC;
+       }
+
+       return BNEP_CONN_INVALID_DST;
+}
+
+static uint16_t bnep_setup_decode(struct bnep_setup_conn_req *req,
+                               uint16_t *dst_role, uint16_t *src_role)
+{
+       uint8_t *dest, *source;
+
+       dest = req->service;
+       source = req->service + req->uuid_size;
+
+       switch (req->uuid_size) {
+       case 2: /* UUID16 */
+               *dst_role = ntohs(bt_get_unaligned((uint16_t *) dest));
+               *src_role = ntohs(bt_get_unaligned((uint16_t *) source));
+               break;
+       case 4: /* UUID32 */
+       case 16: /* UUID128 */
+               *dst_role = ntohl(bt_get_unaligned((uint32_t *) dest));
+               *src_role = ntohl(bt_get_unaligned((uint32_t *) source));
+               break;
+       default:
+               return BNEP_CONN_INVALID_SVC;
+       }
+
+       return 0;
+}
+
+static void session_free(void *data)
+{
+       struct network_session *session = data;
+
+       if (session->watch)
+               g_source_remove(session->watch);
+
+       if (session->io)
+               g_io_channel_unref(session->io);
+
+       g_free(session);
+}
+
+static void setup_destroy(void *user_data)
+{
+       struct network_adapter *na = user_data;
+       struct network_session *setup = na->setup;
+
+       if (!setup)
+               return;
+
+       na->setup = NULL;
+
+       session_free(setup);
+}
+
+#ifdef  __TIZEN_PATCH__
+static gboolean server_disconnected_cb(GIOChannel *chan,
+                       GIOCondition cond, gpointer user_data)
+{
+       struct network_server *ns = NULL;
+       struct network_session *session = NULL;
+       gboolean connected = FALSE;
+       char address[20] = {0};
+       GError *gerr = NULL;
+       const char* paddr = address;
+       char *name_str = NULL;
+
+       info("server_disconnected_cb");
+
+       if (!user_data)
+               return FALSE;
+
+       ns = (struct network_server *) user_data;
+
+       name_str = ns->dev;
+
+       bt_io_get(chan, BT_IO_L2CAP, &gerr,
+                       BT_IO_OPT_DEST, &address,
+                       BT_IO_OPT_INVALID);
+
+       g_dbus_emit_signal(connection, adapter_get_path(ns->na->adapter),
+                       NETWORK_SERVER_INTERFACE, "PeerDisconnected",
+                       DBUS_TYPE_STRING, &name_str,
+                       DBUS_TYPE_STRING, &paddr,
+                       DBUS_TYPE_INVALID);
+
+       bnep_server_disconnect();
+
+       /* Remove the session info */
+       session = find_session(ns->sessions, chan);
+       if (session) {
+               ns->sessions = g_slist_remove(ns->sessions, session);
+               session_free(session);
+       }
+       else {
+               info("Session is not exist!");
+       }
+
+       if (g_slist_length(ns->sessions) == 0)
+               bnep_if_down(ns->dev);
+
+       return FALSE;
+}
+#endif
+
+static gboolean bnep_setup(GIOChannel *chan,
+                       GIOCondition cond, gpointer user_data)
+{
+       struct network_adapter *na = user_data;
+       struct network_server *ns;
+       uint8_t packet[BNEP_MTU];
+       struct bnep_setup_conn_req *req = (void *) packet;
+       uint16_t src_role, dst_role, rsp = BNEP_CONN_NOT_ALLOWED;
+       int n, sk;
+#ifdef  __TIZEN_PATCH__
+       gboolean connected = TRUE;
+#endif
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       if (cond & (G_IO_ERR | G_IO_HUP)) {
+               error("Hangup or error on BNEP socket");
+               return FALSE;
+       }
+
+       sk = g_io_channel_unix_get_fd(chan);
+
+       /* Reading BNEP_SETUP_CONNECTION_REQUEST_MSG */
+       n = read(sk, packet, sizeof(packet));
+       if (n < 0) {
+               error("read(): %s(%d)", strerror(errno), errno);
+               return FALSE;
+       }
+
+       /* Highest known Control command ID
+        * is BNEP_FILTER_MULT_ADDR_RSP = 0x06 */
+       if (req->type == BNEP_CONTROL &&
+                               req->ctrl > BNEP_FILTER_MULT_ADDR_RSP) {
+               uint8_t pkt[3];
+
+               pkt[0] = BNEP_CONTROL;
+               pkt[1] = BNEP_CMD_NOT_UNDERSTOOD;
+               pkt[2] = req->ctrl;
+
+               send(sk, pkt, sizeof(pkt), 0);
+
+               return FALSE;
+       }
+
+       if (req->type != BNEP_CONTROL || req->ctrl != BNEP_SETUP_CONN_REQ)
+               return FALSE;
+
+       rsp = bnep_setup_decode(req, &dst_role, &src_role);
+       if (rsp)
+               goto reply;
+
+       rsp = bnep_setup_chk(dst_role, src_role);
+       if (rsp)
+               goto reply;
+
+       ns = find_server(na->servers, dst_role);
+       if (!ns) {
+               error("Server unavailable: (0x%x)", dst_role);
+               goto reply;
+       }
+
+       if (!ns->record_id) {
+               error("Service record not available");
+               goto reply;
+       }
+
+       if (!ns->bridge) {
+               error("Bridge interface not configured");
+               goto reply;
+       }
+
+       if (server_connadd(ns, na->setup, dst_role) < 0)
+               goto reply;
+
+#ifndef  __TIZEN_PATCH__
+       na->setup = NULL;
+#endif
+
+       rsp = BNEP_SUCCESS;
+
+#ifdef  __TIZEN_PATCH__
+{
+// Emit connected signal to BT application
+       const gchar* adapter_path = adapter_get_path(na->adapter);
+       const char* pdev = ns->dev;
+       char address[24] = {0};
+       char* paddr = address;
+
+       ba2str(&na->setup->dst,paddr);
+
+       na->setup = NULL;
+
+       g_dbus_emit_signal(connection, adapter_path,
+                       NETWORK_SERVER_INTERFACE, "PeerConnected",
+                       DBUS_TYPE_STRING, &pdev,
+                       DBUS_TYPE_STRING, &paddr,
+                       DBUS_TYPE_INVALID);
+}
+#endif
+
+reply:
+       send_bnep_ctrl_rsp(sk, rsp);
+
+       return FALSE;
+}
+
+static void connect_event(GIOChannel *chan, GError *err, gpointer user_data)
+{
+       struct network_adapter *na = user_data;
+
+       if (err) {
+               error("%s", err->message);
+               setup_destroy(na);
+               return;
+       }
+
+       g_io_channel_set_close_on_unref(chan, TRUE);
+
+       na->setup->watch = g_io_add_watch_full(chan, G_PRIORITY_DEFAULT,
+                               G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                               bnep_setup, na, setup_destroy);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+       struct network_adapter *na = user_data;
+       GError *err = NULL;
+
+       if (derr) {
+               error("Access denied: %s", derr->message);
+               goto reject;
+       }
+
+       if (!bt_io_accept(na->setup->io, connect_event, na, NULL,
+                                                       &err)) {
+               error("bt_io_accept: %s", err->message);
+               g_error_free(err);
+               goto reject;
+       }
+
+       return;
+
+reject:
+       g_io_channel_shutdown(na->setup->io, TRUE, NULL);
+       setup_destroy(na);
+}
+
+static void confirm_event(GIOChannel *chan, gpointer user_data)
+{
+       struct network_adapter *na = user_data;
+       struct network_server *ns;
+       int perr;
+       bdaddr_t src, dst;
+       char address[18];
+       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;
+       }
+
+       DBG("BNEP: incoming connect from %s", address);
+
+       if (na->setup) {
+               error("Refusing connect from %s: setup in progress", address);
+               goto drop;
+       }
+
+       ns = find_server(na->servers, BNEP_SVC_NAP);
+       if (!ns)
+               goto drop;
+
+       if (!ns->record_id)
+               goto drop;
+
+       if (!ns->bridge)
+               goto drop;
+
+       na->setup = g_new0(struct network_session, 1);
+       bacpy(&na->setup->dst, &dst);
+       na->setup->io = g_io_channel_ref(chan);
+
+       perr = btd_request_authorization(&src, &dst, BNEP_SVC_UUID,
+                                       auth_cb, na);
+       if (perr < 0) {
+               error("Refusing connect from %s: %s (%d)", address,
+                               strerror(-perr), -perr);
+               setup_destroy(na);
+               goto drop;
+       }
+
+       return;
+
+drop:
+       g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+int server_init(DBusConnection *conn, gboolean secure)
+{
+       security = secure;
+       connection = dbus_connection_ref(conn);
+
+       return 0;
+}
+
+void server_exit(void)
+{
+       dbus_connection_unref(connection);
+       connection = NULL;
+}
+
+static uint32_t register_server_record(struct network_server *ns)
+{
+       sdp_record_t *record;
+
+       record = server_record_new(ns->name, ns->id);
+       if (!record) {
+               error("Unable to allocate new service record");
+               return 0;
+       }
+
+       if (add_record_to_server(&ns->src, record) < 0) {
+               error("Failed to register service record");
+               sdp_record_free(record);
+               return 0;
+       }
+
+       DBG("got record id 0x%x", record->handle);
+
+       return record->handle;
+}
+
+static void server_disconnect(DBusConnection *conn, void *user_data)
+{
+       struct network_server *ns = user_data;
+
+       ns->watch_id = 0;
+
+       if (ns->record_id) {
+               remove_record_from_server(ns->record_id);
+               ns->record_id = 0;
+       }
+
+       g_free(ns->bridge);
+       ns->bridge = NULL;
+}
+
+static DBusMessage *register_server(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       struct network_server *ns = data;
+       DBusMessage *reply;
+       const char *uuid, *bridge;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &uuid,
+                               DBUS_TYPE_STRING, &bridge, DBUS_TYPE_INVALID))
+               return NULL;
+
+       if (g_strcmp0(uuid, "nap"))
+               return btd_error_failed(msg, "Invalid UUID");
+
+       if (ns->record_id)
+               return btd_error_already_exists(msg);
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       ns->record_id = register_server_record(ns);
+       if (!ns->record_id)
+               return btd_error_failed(msg, "SDP record registration failed");
+
+       g_free(ns->bridge);
+       ns->bridge = g_strdup(bridge);
+
+#ifndef  __TIZEN_PATCH__
+       ns->watch_id = g_dbus_add_disconnect_watch(conn,
+                                       dbus_message_get_sender(msg),
+                                       server_disconnect, ns, NULL);
+#endif
+
+       return reply;
+}
+
+static DBusMessage *unregister_server(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct network_server *ns = data;
+       DBusMessage *reply;
+       const char *uuid;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &uuid,
+                                                       DBUS_TYPE_INVALID))
+               return NULL;
+
+       if (g_strcmp0(uuid, "nap"))
+               return btd_error_failed(msg, "Invalid UUID");
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       g_dbus_remove_watch(conn, ns->watch_id);
+
+       server_disconnect(conn, ns);
+
+#ifdef  __TIZEN_PATCH__
+       /* Down the bnep interface, and disconnect all connection */
+       bnep_kill_all_connections();
+       bnep_if_down(ns->dev);
+
+       if (ns->sessions) {
+               g_slist_foreach(ns->sessions, (GFunc) session_free, NULL);
+               g_slist_free(ns->sessions);
+               ns->sessions = NULL;
+       }
+#endif
+       return reply;
+}
+
+static void adapter_free(struct network_adapter *na)
+{
+       if (na->io != NULL) {
+               g_io_channel_shutdown(na->io, TRUE, NULL);
+               g_io_channel_unref(na->io);
+       }
+
+       setup_destroy(na);
+       btd_adapter_unref(na->adapter);
+       g_free(na);
+}
+
+static void server_free(struct network_server *ns)
+{
+       if (!ns)
+               return;
+
+       /* FIXME: Missing release/free all bnepX interfaces */
+       if (ns->record_id)
+               remove_record_from_server(ns->record_id);
+
+       g_free(ns->iface);
+       g_free(ns->name);
+       g_free(ns->bridge);
+
+       if (ns->sessions) {
+               g_slist_foreach(ns->sessions, (GFunc) session_free, NULL);
+               g_slist_free(ns->sessions);
+       }
+
+       g_free(ns);
+}
+
+static void path_unregister(void *data)
+{
+       struct network_server *ns = data;
+       struct network_adapter *na = ns->na;
+
+       DBG("Unregistered interface %s on path %s",
+               ns->iface, adapter_get_path(na->adapter));
+
+       na->servers = g_slist_remove(na->servers, ns);
+       server_free(ns);
+
+       if (na->servers)
+               return;
+
+       adapters = g_slist_remove(adapters, na);
+       adapter_free(na);
+}
+
+static GDBusMethodTable server_methods[] = {
+       { "Register",   "ss",   "",     register_server         },
+       { "Unregister", "s",    "",     unregister_server       },
+       { }
+};
+
+#ifdef  __TIZEN_PATCH__
+static GDBusSignalTable server_signals[] = {
+       { "PeerConnected",      "ss"    },
+       { "PeerDisconnected",   "ss"    },
+       { }
+};
+#endif
+static struct network_adapter *create_adapter(struct btd_adapter *adapter)
+{
+       struct network_adapter *na;
+       GError *err = NULL;
+       bdaddr_t src;
+
+       na = g_new0(struct network_adapter, 1);
+       na->adapter = btd_adapter_ref(adapter);
+
+       adapter_get_address(adapter, &src);
+
+       na->io = bt_io_listen(BT_IO_L2CAP, NULL, confirm_event, na,
+                               NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, &src,
+                               BT_IO_OPT_PSM, BNEP_PSM,
+                               BT_IO_OPT_OMTU, BNEP_MTU,
+                               BT_IO_OPT_IMTU, BNEP_MTU,
+                               BT_IO_OPT_SEC_LEVEL,
+                               security ? BT_IO_SEC_MEDIUM : BT_IO_SEC_LOW,
+                               BT_IO_OPT_INVALID);
+       if (!na->io) {
+               error("%s", err->message);
+               g_error_free(err);
+               adapter_free(na);
+               return NULL;
+       }
+
+       return na;
+}
+
+int server_register(struct btd_adapter *adapter)
+{
+       struct network_adapter *na;
+       struct network_server *ns;
+       const char *path;
+
+       na = find_adapter(adapters, adapter);
+       if (!na) {
+               na = create_adapter(adapter);
+               if (!na)
+                       return -EINVAL;
+               adapters = g_slist_append(adapters, na);
+       }
+
+       ns = find_server(na->servers, BNEP_SVC_NAP);
+       if (ns)
+               return 0;
+
+       ns = g_new0(struct network_server, 1);
+
+       ns->iface = g_strdup(NETWORK_SERVER_INTERFACE);
+       ns->name = g_strdup("Network service");
+
+       path = adapter_get_path(adapter);
+
+#ifndef  __TIZEN_PATCH__
+       if (!g_dbus_register_interface(connection, path, ns->iface,
+                                       server_methods, NULL, NULL,
+                                       ns, path_unregister)) {
+               error("D-Bus failed to register %s interface",
+                               ns->iface);
+               server_free(ns);
+               return -1;
+       }
+#else
+       if (!g_dbus_register_interface(connection, path, ns->iface,
+                                       server_methods, server_signals, NULL,
+                                       ns, path_unregister)) {
+               error("D-Bus failed to register %s interface",
+                               ns->iface);
+               server_free(ns);
+               return -1;
+       }
+#endif
+
+       adapter_get_address(adapter, &ns->src);
+       ns->id = BNEP_SVC_NAP;
+       ns->na = na;
+       ns->record_id = 0;
+       na->servers = g_slist_append(na->servers, ns);
+
+       DBG("Registered interface %s on path %s", ns->iface, path);
+
+       return 0;
+}
+
+int server_unregister(struct btd_adapter *adapter)
+{
+       struct network_adapter *na;
+       struct network_server *ns;
+       uint16_t id = BNEP_SVC_NAP;
+
+       na = find_adapter(adapters, adapter);
+       if (!na)
+               return -EINVAL;
+
+       ns = find_server(na->servers, id);
+       if (!ns)
+               return -EINVAL;
+
+       g_dbus_unregister_interface(connection, adapter_get_path(adapter),
+                                       ns->iface);
+
+       return 0;
+}
diff --git a/network/server.h b/network/server.h
new file mode 100644 (file)
index 0000000..d88acab
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 server_init(DBusConnection *conn, gboolean secure);
+void server_exit(void);
+int server_register(struct btd_adapter *adapter);
+int server_unregister(struct btd_adapter *adapter);
+
+int server_find_data(const char *path, const char *pattern);
diff --git a/packaging/bluez.spec b/packaging/bluez.spec
new file mode 100644 (file)
index 0000000..87ee15c
--- /dev/null
@@ -0,0 +1,178 @@
+
+Name:       bluez
+Summary:    Bluetooth utilities
+Version:    4.90
+Release:    1
+Group:      Applications/System
+License:    GPLv2+
+URL:        http://www.bluez.org/
+Source0:    http://www.kernel.org/pub/linux/bluetooth/%{name}-%{version}.tar.gz
+Requires:   bluez-libs = %{version}
+Requires:   dbus >= 0.60
+Requires:   usbutils
+Requires:   pciutils
+BuildRequires:  pkgconfig(dbus-1)
+BuildRequires:  pkgconfig(alsa)
+BuildRequires:  pkgconfig(udev)
+BuildRequires:  pkgconfig(sndfile)
+BuildRequires:  pkgconfig(glib-2.0)
+BuildRequires:  pkgconfig(gstreamer-plugins-base-0.10)
+BuildRequires:  pkgconfig(gstreamer-0.10)
+BuildRequires:  flex
+BuildRequires:  bison
+
+
+%description
+Utilities for use in Bluetooth applications:
+       --ciptool
+       --dfutool
+       --hcitool
+       --l2ping
+       --rfcomm
+       --sdptool
+       --hciattach
+       --hciconfig
+       --hid2hci
+
+The BLUETOOTH trademarks are owned by Bluetooth SIG, Inc., U.S.A.
+
+
+
+%package libs
+Summary:    Libraries for use in Bluetooth applications
+Group:      System/Libraries
+Requires:   %{name} = %{version}-%{release}
+Requires(post): /sbin/ldconfig
+Requires(postun): /sbin/ldconfig
+
+%description libs
+Libraries for use in Bluetooth applications.
+
+%package libs-devel
+Summary:    Development libraries for Bluetooth applications
+Group:      Development/Libraries
+Requires:   %{name} = %{version}-%{release}
+Requires:   bluez-libs = %{version}
+
+%description libs-devel
+bluez-libs-devel contains development libraries and headers for
+use in Bluetooth applications.
+
+
+%package cups
+Summary:    CUPS printer backend for Bluetooth printers
+Group:      System/Daemons
+Requires:   %{name} = %{version}-%{release}
+Requires:   bluez-libs = %{version}
+Requires:   cups
+
+%description cups
+This package contains the CUPS backend
+
+%package alsa
+Summary:    ALSA support for Bluetooth audio devices
+Group:      System/Daemons
+Requires:   %{name} = %{version}-%{release}
+Requires:   bluez-libs = %{version}
+
+%description alsa
+This package contains ALSA support for Bluetooth audio devices
+
+%package gstreamer
+Summary:    GStreamer support for SBC audio format
+Group:      System/Daemons
+Requires:   %{name} = %{version}-%{release}
+Requires:   bluez-libs = %{version}
+
+%description gstreamer
+This package contains gstreamer plugins for the Bluetooth SBC audio format
+
+%package test
+Summary:    Test Programs for BlueZ
+Group:      Development/Tools
+Requires:   %{name} = %{version}-%{release}
+Requires:   bluez-libs = %{version}
+Requires:   dbus-python
+Requires:   pygobject2
+
+%description test
+Scripts for testing BlueZ and its functionality
+
+
+%prep
+%setup -q -n %{name}-%{version}
+
+
+%build
+
+export CFLAGS="${CFLAGS} -D__TIZEN_PATCH__ -D__BROADCOM_PATCH__"
+%reconfigure --disable-static \
+                       --localstatedir=/opt/var \
+                        --enable-pie \
+                        --enable-network \
+                        --enable-serial \
+                        --enable-input \
+                        --enable-usb=no \
+                        --enable-tools \
+                       --disable-bccmd \
+                        --enable-pcmcia=no \
+                        --enable-hid2hci=no \
+                        --enable-alsa=no \
+                        --enable-gstreamer \
+                        --disable-dfutool \
+                        --disable-cups \
+                        --disable-tests \
+                        --disable-udevrules \
+                        --with-telephony=tizen
+
+make %{?jobs:-j%jobs}
+
+%install
+rm -rf %{buildroot}
+%make_install
+
+
+%post libs -p /sbin/ldconfig
+
+%postun libs -p /sbin/ldconfig
+
+
+%docs_package
+
+
+%files
+%defattr(-,root,root,-)
+%{_bindir}/ciptool
+%{_bindir}/hcitool
+%{_bindir}/l2ping
+%{_bindir}/rfcomm
+%{_bindir}/sdptool
+%{_sbindir}/*
+%config(noreplace) %{_sysconfdir}/bluetooth/*
+%config %{_sysconfdir}/dbus-1/system.d/bluetooth.conf
+#%{_localstatedir}/lib/bluetooth
+#/lib/udev/*
+
+
+%files libs
+%defattr(-,root,root,-)
+%{_libdir}/libbluetooth.so.*
+%doc COPYING
+
+%files libs-devel
+%defattr(-, root, root)
+%{_libdir}/libbluetooth.so
+%dir %{_includedir}/bluetooth
+%{_includedir}/bluetooth/*
+%{_libdir}/pkgconfig/bluez.pc
+
+
+%files alsa
+%defattr(-,root,root,-)
+#%{_libdir}/alsa-lib/*.so
+#%{_datadir}/alsa/bluetooth.conf
+
+%files gstreamer
+%defattr(-,root,root,-)
+%{_libdir}/gstreamer-*/*.so
+
diff --git a/plugins/echo.c b/plugins/echo.c
new file mode 100644 (file)
index 0000000..23f6e49
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+#include <glib.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "adapter.h"
+#include "log.h"
+
+static gboolean session_event(GIOChannel *chan,
+                                       GIOCondition cond, gpointer data)
+{
+       unsigned char buf[672];
+       gsize len, written;
+       GIOError err;
+
+       if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL))
+               return FALSE;
+
+       err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len);
+       if (err == G_IO_ERROR_AGAIN)
+               return TRUE;
+
+       g_io_channel_write(chan, (const gchar *) buf, len, &written);
+
+       return TRUE;
+}
+
+static gboolean connect_event(GIOChannel *chan,
+                                       GIOCondition cond, gpointer data)
+{
+       GIOChannel *io;
+       struct sockaddr_rc addr;
+       socklen_t optlen;
+       char address[18];
+       int sk, nsk;
+
+       sk = g_io_channel_unix_get_fd(chan);
+
+       memset(&addr, 0, sizeof(addr));
+       optlen = sizeof(addr);
+
+       nsk = accept(sk, (struct sockaddr *) &addr, &optlen);
+       if (nsk < 0)
+               return TRUE;
+
+       io = g_io_channel_unix_new(nsk);
+       g_io_channel_set_close_on_unref(io, TRUE);
+
+       ba2str(&addr.rc_bdaddr, address);
+
+       g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                                                       session_event, NULL);
+
+       return TRUE;
+}
+
+static GIOChannel *setup_rfcomm(uint8_t channel)
+{
+       GIOChannel *io;
+       struct sockaddr_rc addr;
+       int sk;
+
+       sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+       if (sk < 0)
+               return NULL;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, BDADDR_ANY);
+       addr.rc_channel = channel;
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               close(sk);
+               return NULL;
+       }
+
+       if (listen(sk, 10) < 0) {
+               close(sk);
+               return NULL;
+       }
+
+       io = g_io_channel_unix_new(sk);
+       g_io_channel_set_close_on_unref(io, TRUE);
+
+       g_io_add_watch(io, G_IO_IN, connect_event, NULL);
+
+       return io;
+}
+
+static GIOChannel *chan = NULL;
+
+static int echo_probe(struct btd_adapter *adapter)
+{
+       const char *path = adapter_get_path(adapter);
+
+       DBG("path %s", path);
+
+       chan = setup_rfcomm(23);
+
+       return 0;
+}
+
+static void echo_remove(struct btd_adapter *adapter)
+{
+       const char *path = adapter_get_path(adapter);
+
+       DBG("path %s", path);
+
+       g_io_channel_unref(chan);
+}
+
+static struct btd_adapter_driver echo_server = {
+       .name   = "echo-server",
+       .probe  = echo_probe,
+       .remove = echo_remove,
+};
+
+static int echo_init(void)
+{
+       DBG("Setup echo plugin");
+
+       return btd_register_adapter_driver(&echo_server);
+}
+
+static void echo_exit(void)
+{
+       DBG("Cleanup echo plugin");
+
+       btd_unregister_adapter_driver(&echo_server);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(echo, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, echo_init, echo_exit)
diff --git a/plugins/formfactor.c b/plugins/formfactor.c
new file mode 100644 (file)
index 0000000..758d481
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "plugin.h"
+#include "adapter.h"
+#include "log.h"
+
+#define DMI_CHASSIS_FILE "/sys/class/dmi/id/chassis_type"
+#define DMI_CHASSIS_FILE_FALLBACK "/sys/devices/virtual/dmi/id/chassis_type"
+
+/* Map the chassis type from chassis_type to a sensible type used in hal
+ *
+ * See also 3.3.4.1 of the "System Management BIOS Reference Specification,
+ * Version 2.6.1" (Preliminary Standard) document, available from
+ * http://www.dmtf.org/standards/smbios.
+ *
+ * TODO: figure out WTF the mapping should be; "Lunch Box"? Give me a break :-)
+ *
+ * Copied from hal/hald/linux/osspec.c
+ */
+static const char *chassis_map[] = {
+       "Other",                 "unknown", /* 0x01 */
+       "Unknown",               "unknown",
+       "Desktop",               "desktop",
+       "Low Profile Desktop",   "desktop",
+       "Pizza Box",             "server",
+       "Mini Tower",            "desktop",
+       "Tower",                 "desktop",
+       "Portable",              "laptop",
+       "Laptop",                "laptop",
+       "Notebook",              "laptop",
+       "Hand Held",             "handheld",
+       "Docking Station",       "laptop",
+       "All In One",            "unknown",
+       "Sub Notebook",          "laptop",
+       "Space-saving",          "desktop",
+       "Lunch Box",             "unknown",
+       "Main Server Chassis",   "server",
+       "Expansion Chassis",     "unknown",
+       "Sub Chassis",           "unknown",
+       "Bus Expansion Chassis", "unknown",
+       "Peripheral Chassis",    "unknown",
+       "RAID Chassis",          "unknown",
+       "Rack Mount Chassis",    "unknown",
+       "Sealed-case PC",        "unknown",
+       "Multi-system",          "unknown",
+       "CompactPCI",            "unknonw",
+       "AdvancedTCA",           "unknown",
+       "Blade",                 "server",
+       "Blade Enclosure"        "unknown", /* 0x1D */
+       NULL
+};
+
+static int formfactor_probe(struct btd_adapter *adapter)
+{
+       int chassis_type;
+       uint8_t minor = 0;
+       const char *formfactor;
+       char *contents;
+
+       if (g_file_get_contents(DMI_CHASSIS_FILE,
+                               &contents, NULL, NULL) == FALSE) {
+               if (g_file_get_contents(DMI_CHASSIS_FILE_FALLBACK,
+                                       &contents, NULL, NULL) == FALSE) {
+                       error("Could not get the contents of DMI chassis type");
+                       return 0;
+               }
+       }
+
+       chassis_type = atoi(contents);
+       g_free (contents);
+
+       if (chassis_type > 0x1D || chassis_type <= 0) {
+               error ("Chassis type is not a known chassis type");
+               return 0;
+       }
+
+       formfactor = chassis_map[chassis_type * 2];
+       if (formfactor != NULL) {
+               if (g_str_equal(formfactor, "laptop") == TRUE)
+                       minor |= (1 << 2) | (1 << 3);
+               else if (g_str_equal(formfactor, "desktop") == TRUE)
+                       minor |= 1 << 2;
+               else if (g_str_equal(formfactor, "server") == TRUE)
+                       minor |= 1 << 3;
+               else if (g_str_equal(formfactor, "handheld") == TRUE)
+                       minor += 1 << 4;
+       }
+
+       /* Computer major class */
+       DBG("Setting 0x%06x for major/minor device class", (1 << 8) | minor);
+
+       btd_adapter_set_class(adapter, 0x01, minor);
+
+       return 0;
+}
+
+static void formfactor_remove(struct btd_adapter *adapter)
+{
+}
+
+static struct btd_adapter_driver formfactor_driver = {
+       .name   = "formfactor",
+       .probe  = formfactor_probe,
+       .remove = formfactor_remove,
+};
+
+static int formfactor_init(void)
+{
+       return btd_register_adapter_driver(&formfactor_driver);
+}
+
+static void formfactor_exit(void)
+{
+       btd_unregister_adapter_driver(&formfactor_driver);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(formfactor, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_LOW, formfactor_init, formfactor_exit)
diff --git a/plugins/hal.c b/plugins/hal.c
new file mode 100644 (file)
index 0000000..21d7688
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <dbus/dbus.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "plugin.h"
+#include "adapter.h"
+#include "log.h"
+
+static void formfactor_reply(DBusPendingCall *call, void *user_data)
+{
+       struct btd_adapter *adapter = user_data;
+       const char *formfactor = NULL;
+       DBusMessage *reply;
+       uint8_t minor = 0;
+
+       reply = dbus_pending_call_steal_reply(call);
+
+       if (dbus_set_error_from_message(NULL, reply) == TRUE) {
+               error("Failed to access HAL");
+               dbus_message_unref(reply);
+               return;
+       }
+
+       if (dbus_message_get_args(reply, NULL, DBUS_TYPE_STRING, &formfactor,
+                                               DBUS_TYPE_INVALID) == FALSE) {
+               error("Wrong formfactor arguments");
+               dbus_message_unref(reply);
+               return;
+       }
+
+       DBG("Computer is classified as %s", formfactor);
+
+       if (formfactor != NULL) {
+               if (g_str_equal(formfactor, "laptop") == TRUE)
+                       minor |= (1 << 2) | (1 << 3);
+               else if (g_str_equal(formfactor, "desktop") == TRUE)
+                       minor |= 1 << 2;
+               else if (g_str_equal(formfactor, "server") == TRUE)
+                       minor |= 1 << 3;
+               else if (g_str_equal(formfactor, "handheld") == TRUE)
+                       minor += 1 << 4;
+       }
+
+       dbus_message_unref(reply);
+
+       /* Computer major class */
+       DBG("Setting 0x%06x for major/minor device class", (1 << 8) | minor);
+
+       btd_adapter_set_class(adapter, 0x01, minor);
+}
+
+static DBusConnection *connection;
+
+static int hal_probe(struct btd_adapter *adapter)
+{
+       const char *property = "system.formfactor";
+       DBusMessage *message;
+       DBusPendingCall *call;
+
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (connection == NULL)
+               return -ENOMEM;
+
+       message = dbus_message_new_method_call("org.freedesktop.Hal",
+                               "/org/freedesktop/Hal/devices/computer",
+                                               "org.freedesktop.Hal.Device",
+                                                       "GetPropertyString");
+       if (message == NULL) {
+               error("Failed to create formfactor request");
+               dbus_connection_unref(connection);
+               return -ENOMEM;
+       }
+
+       dbus_message_append_args(message, DBUS_TYPE_STRING, &property,
+                                                       DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(connection, message,
+                                               &call, -1) == FALSE) {
+               error("Failed to send formfactor request");
+               dbus_message_unref(message);
+               dbus_connection_unref(connection);
+               return -EIO;
+       }
+
+       dbus_pending_call_set_notify(call, formfactor_reply, adapter, NULL);
+
+       dbus_pending_call_unref(call);
+
+       dbus_message_unref(message);
+
+       return 0;
+}
+
+static void hal_remove(struct btd_adapter *adapter)
+{
+       dbus_connection_unref(connection);
+}
+
+static struct btd_adapter_driver hal_driver = {
+       .name   = "hal",
+       .probe  = hal_probe,
+       .remove = hal_remove,
+};
+
+static int hal_init(void)
+{
+       return btd_register_adapter_driver(&hal_driver);
+}
+
+static void hal_exit(void)
+{
+       btd_unregister_adapter_driver(&hal_driver);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(hal, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_LOW, hal_init, hal_exit)
diff --git a/plugins/hciops.c b/plugins/hciops.c
new file mode 100644 (file)
index 0000000..163577f
--- /dev/null
@@ -0,0 +1,3612 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+
+#include "hcid.h"
+#include "sdpd.h"
+#include "btio.h"
+#include "adapter.h"
+#include "device.h"
+#include "plugin.h"
+#include "log.h"
+#include "storage.h"
+#include "event.h"
+#include "manager.h"
+
+static int child_pipe[2] = { -1, -1 };
+
+static guint child_io_id = 0;
+static guint ctl_io_id = 0;
+
+/* Commands sent by kernel on starting an adapter */
+enum {
+       PENDING_BDADDR,
+       PENDING_VERSION,
+       PENDING_FEATURES,
+       PENDING_NAME,
+};
+
+struct uuid_info {
+       uuid_t uuid;
+       uint8_t svc_hint;
+};
+
+struct bt_conn {
+       struct dev_info *dev;
+       bdaddr_t bdaddr;
+       uint16_t handle;
+       uint8_t loc_cap;
+       uint8_t loc_auth;
+       uint8_t rem_cap;
+       uint8_t rem_auth;
+       gboolean bonding_initiator;
+       gboolean secmode3;
+       GIOChannel *io; /* For raw L2CAP socket (bonding) */
+};
+
+static int max_dev = -1;
+static struct dev_info {
+       int id;
+       int sk;
+       bdaddr_t bdaddr;
+       char name[249];
+       uint8_t eir[240];
+       uint8_t features[8];
+       uint8_t ssp_mode;
+
+       int8_t tx_power;
+
+       uint32_t current_cod;
+       uint32_t wanted_cod;
+       uint32_t pending_cod;
+       gboolean cache_enable;
+       gboolean already_up;
+       gboolean registered;
+       gboolean pairable;
+
+       uint8_t io_capability;
+
+       struct hci_version ver;
+
+       uint16_t did_vendor;
+       uint16_t did_product;
+       uint16_t did_version;
+
+       gboolean up;
+       unsigned long pending;
+
+       GIOChannel *io;
+       guint watch_id;
+
+       gboolean debug_keys;
+       GSList *keys;
+       uint8_t pin_length;
+
+       GSList *uuids;
+
+       GSList *connections;
+} *devs = NULL;
+
+static int ignore_device(struct hci_dev_info *di)
+{
+       return hci_test_bit(HCI_RAW, &di->flags) || di->type >> 4 != HCI_BREDR;
+}
+
+static struct dev_info *init_dev_info(int index, int sk, gboolean registered,
+                                                       gboolean already_up)
+{
+       struct dev_info *dev = &devs[index];
+
+       memset(dev, 0, sizeof(*dev));
+
+       dev->id = index;
+       dev->sk = sk;
+       dev->cache_enable = TRUE;
+       dev->registered = registered;
+       dev->already_up = already_up;
+       dev->io_capability = 0x03; /* No Input No Output */
+
+       return dev;
+}
+
+/* Async HCI command handling with callback support */
+
+struct hci_cmd_data {
+       bt_hci_result_t         cb;
+       uint16_t                handle;
+       uint16_t                ocf;
+       gpointer                caller_data;
+};
+
+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;
+}
+
+static int write_inq_mode(int index, uint8_t mode)
+{
+       struct dev_info *dev = &devs[index];
+       write_inquiry_mode_cp cp;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.mode = mode;
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_WRITE_INQUIRY_MODE,
+                                       WRITE_INQUIRY_MODE_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static uint8_t get_inquiry_mode(int index)
+{
+       struct dev_info *dev = &devs[index];
+
+       if (dev->features[6] & LMP_EXT_INQ)
+               return 2;
+
+       if (dev->features[3] & LMP_RSSI_INQ)
+               return 1;
+
+       if (dev->ver.manufacturer == 11 && dev->ver.hci_rev == 0x00 &&
+                                       dev->ver.lmp_subver == 0x0757)
+               return 1;
+
+       if (dev->ver.manufacturer == 15) {
+               if (dev->ver.hci_rev == 0x03 &&
+                                       dev->ver.lmp_subver == 0x6963)
+                       return 1;
+               if (dev->ver.hci_rev == 0x09 &&
+                                       dev->ver.lmp_subver == 0x6963)
+                       return 1;
+               if (dev->ver.hci_rev == 0x00 &&
+                                       dev->ver.lmp_subver == 0x6965)
+                       return 1;
+       }
+
+       if (dev->ver.manufacturer == 31 && dev->ver.hci_rev == 0x2005 &&
+                                       dev->ver.lmp_subver == 0x1805)
+               return 1;
+
+       return 0;
+}
+
+static int init_ssp_mode(int index)
+{
+       struct dev_info *dev = &devs[index];
+       write_simple_pairing_mode_cp cp;
+
+       if (ioctl(dev->sk, HCIGETAUTHINFO, NULL) < 0 && errno == EINVAL)
+               return 0;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.mode = 0x01;
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL,
+                               OCF_WRITE_SIMPLE_PAIRING_MODE,
+                               WRITE_SIMPLE_PAIRING_MODE_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_set_discoverable(int index, gboolean discoverable)
+{
+       struct dev_info *dev = &devs[index];
+       uint8_t mode;
+
+       if (discoverable)
+               mode = (SCAN_PAGE | SCAN_INQUIRY);
+       else
+               mode = SCAN_PAGE;
+
+       DBG("hci%d discoverable %d", index, discoverable);
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE,
+                                                               1, &mode) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_set_pairable(int index, gboolean pairable)
+{
+       struct btd_adapter *adapter;
+
+       DBG("hci%d pairable %d", index, pairable);
+
+       adapter = manager_find_adapter(&devs[index].bdaddr);
+       if (adapter)
+               btd_adapter_pairable_changed(adapter, pairable);
+
+       devs[index].pairable = pairable;
+
+       return 0;
+}
+
+static int hciops_power_off(int index)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d", index);
+
+       if (ioctl(dev->sk, HCIDEVDOWN, index) < 0 && errno != EALREADY)
+               return -errno;
+
+       return 0;
+}
+
+static void set_event_mask(int index)
+{
+       struct dev_info *dev = &devs[index];
+       /* The second byte is 0xff instead of 0x9f (two reserved bits
+        * disabled) since a Broadcom 1.2 dongle doesn't respond to the
+        * command otherwise */
+       uint8_t events[8] = { 0xff, 0xff, 0xfb, 0xff, 0x00, 0x00, 0x00, 0x00 };
+
+       /* Events for 1.2 and newer controllers */
+       if (dev->ver.lmp_ver > 1) {
+               events[4] |= 0x01; /* Flow Specification Complete */
+               events[4] |= 0x02; /* Inquiry Result with RSSI */
+               events[4] |= 0x04; /* Read Remote Extended Features Complete */
+               events[5] |= 0x08; /* Synchronous Connection Complete */
+               events[5] |= 0x10; /* Synchronous Connection Changed */
+       }
+
+       if (dev->features[3] & LMP_RSSI_INQ)
+               events[4] |= 0x04; /* Inquiry Result with RSSI */
+
+       if (dev->features[5] & LMP_SNIFF_SUBR)
+               events[5] |= 0x20; /* Sniff Subrating */
+
+       if (dev->features[5] & LMP_PAUSE_ENC)
+               events[5] |= 0x80; /* Encryption Key Refresh Complete */
+
+       if (dev->features[6] & LMP_EXT_INQ)
+               events[5] |= 0x40; /* Extended Inquiry Result */
+
+       if (dev->features[6] & LMP_NFLUSH_PKTS)
+               events[7] |= 0x01; /* Enhanced Flush Complete */
+
+       if (dev->features[7] & LMP_LSTO)
+               events[6] |= 0x80; /* Link Supervision Timeout Changed */
+
+       if (dev->features[6] & LMP_SIMPLE_PAIR) {
+               events[6] |= 0x01;      /* IO Capability Request */
+               events[6] |= 0x02;      /* IO Capability Response */
+               events[6] |= 0x04;      /* User Confirmation Request */
+               events[6] |= 0x08;      /* User Passkey Request */
+               events[6] |= 0x10;      /* Remote OOB Data Request */
+               events[6] |= 0x20;      /* Simple Pairing Complete */
+               events[7] |= 0x04;      /* User Passkey Notification */
+               events[7] |= 0x08;      /* Keypress Notification */
+               events[7] |= 0x10;      /* Remote Host Supported
+                                        * Features Notification */
+       }
+
+       if (dev->features[4] & LMP_LE)
+               events[7] |= 0x20;      /* LE Meta-Event */
+
+       hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_SET_EVENT_MASK,
+                                               sizeof(events), events);
+}
+
+static void start_adapter(int index)
+{
+       struct dev_info *dev = &devs[index];
+       uint8_t inqmode;
+       uint16_t link_policy;
+
+       set_event_mask(index);
+
+       if (dev->features[6] & LMP_SIMPLE_PAIR)
+               init_ssp_mode(index);
+
+       inqmode = get_inquiry_mode(index);
+       if (inqmode)
+               write_inq_mode(index, inqmode);
+
+       if (dev->features[7] & LMP_INQ_TX_PWR)
+               hci_send_cmd(dev->sk, OGF_HOST_CTL,
+                               OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL, 0, NULL);
+
+       /* Set default link policy */
+       link_policy = main_opts.link_policy;
+
+       if (!(dev->features[0] & LMP_RSWITCH))
+               link_policy &= ~HCI_LP_RSWITCH;
+       if (!(dev->features[0] & LMP_HOLD))
+               link_policy &= ~HCI_LP_HOLD;
+       if (!(dev->features[0] & LMP_SNIFF))
+               link_policy &= ~HCI_LP_SNIFF;
+       if (!(dev->features[1] & LMP_PARK))
+               link_policy &= ~HCI_LP_PARK;
+
+       link_policy = htobs(link_policy);
+       hci_send_cmd(dev->sk, OGF_LINK_POLICY, OCF_WRITE_DEFAULT_LINK_POLICY,
+                                       sizeof(link_policy), &link_policy);
+
+       dev->current_cod = 0;
+       memset(dev->eir, 0, sizeof(dev->eir));
+}
+
+static int hciops_stop_inquiry(int index)
+{
+       struct dev_info *dev = &devs[index];
+       struct hci_dev_info di;
+       int err;
+
+       DBG("hci%d", index);
+
+       if (hci_devinfo(index, &di) < 0)
+               return -errno;
+
+       if (hci_test_bit(HCI_INQUIRY, &di.flags))
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                               OCF_INQUIRY_CANCEL, 0, 0);
+       else
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                       OCF_EXIT_PERIODIC_INQUIRY, 0, 0);
+       if (err < 0)
+               err = -errno;
+
+       return err;
+}
+
+static gboolean init_adapter(int index)
+{
+       struct dev_info *dev = &devs[index];
+       struct btd_adapter *adapter = NULL;
+       gboolean existing_adapter = dev->registered;
+       uint8_t mode, on_mode;
+       gboolean pairable, discoverable;
+
+       if (!dev->registered) {
+               adapter = btd_manager_register_adapter(index);
+               if (adapter)
+                       dev->registered = TRUE;
+       } else {
+               adapter = manager_find_adapter(&dev->bdaddr);
+               /* FIXME: manager_find_adapter should return a new ref */
+               btd_adapter_ref(adapter);
+       }
+
+       if (adapter == NULL)
+               return FALSE;
+
+       btd_adapter_get_mode(adapter, &mode, &on_mode, &pairable);
+       DBG("SYAM Mode =  %d, on_mode = %d, pairable = %d", mode, on_mode, pairable);
+       if (existing_adapter)
+               mode = on_mode;
+
+       if (mode == MODE_OFF) {
+               hciops_power_off(index);
+               goto done;
+       }
+
+       start_adapter(index);
+       btd_adapter_start(adapter);
+
+       discoverable = (mode == MODE_DISCOVERABLE);
+
+       hciops_set_discoverable(index, discoverable);
+       hciops_set_pairable(index, pairable);
+
+       if (dev->already_up)
+               hciops_stop_inquiry(index);
+
+done:
+       btd_adapter_unref(adapter);
+       return TRUE;
+}
+
+static int hciops_encrypt_link(int index, 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, err;
+       uint32_t link_mode;
+       uint16_t handle;
+
+       dd = hci_open_dev(index);
+       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 fail;
+       }
+
+       if (link_mode & HCI_LM_ENCRYPT) {
+               err = -EALREADY;
+               goto fail;
+       }
+
+       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 fail;
+       }
+
+       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;
+               g_free(cmd);
+               goto fail;
+       }
+
+       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;
+
+fail:
+       close(dd);
+       return err;
+}
+
+static int hciops_set_did(int index, uint16_t vendor, uint16_t product,
+                                                       uint16_t version)
+{
+       struct dev_info *dev = &devs[index];
+
+       dev->did_vendor = vendor;
+       dev->did_product = product;
+       dev->did_version = version;
+
+       return 0;
+}
+
+/* End async HCI command handling */
+
+/* Start of HCI event callbacks */
+
+static gint conn_handle_cmp(gconstpointer a, gconstpointer b)
+{
+       const struct bt_conn *conn = a;
+       uint16_t handle = *((const uint16_t *) b);
+
+       return (int) conn->handle - (int) handle;
+}
+
+static struct bt_conn *find_conn_by_handle(struct dev_info *dev,
+                                                       uint16_t handle)
+{
+       GSList *match;
+
+       match = g_slist_find_custom(dev->connections, &handle,
+                                                       conn_handle_cmp);
+       if (match)
+               return match->data;
+
+       return NULL;
+}
+
+static gint conn_bdaddr_cmp(gconstpointer a, gconstpointer b)
+{
+       const struct bt_conn *conn = a;
+       const bdaddr_t *bdaddr = b;
+
+       return bacmp(&conn->bdaddr, bdaddr);
+}
+
+static struct bt_conn *find_connection(struct dev_info *dev, bdaddr_t *bdaddr)
+{
+       GSList *match;
+
+       match = g_slist_find_custom(dev->connections, bdaddr, conn_bdaddr_cmp);
+       if (match)
+               return match->data;
+
+       return NULL;
+}
+
+static struct bt_conn *get_connection(struct dev_info *dev, bdaddr_t *bdaddr)
+{
+       struct bt_conn *conn;
+
+       conn = find_connection(dev, bdaddr);
+       if (conn)
+               return conn;
+
+       conn = g_new0(struct bt_conn, 1);
+
+       conn->dev = dev;
+       conn->loc_cap = dev->io_capability;
+       conn->loc_auth = 0xff;
+       conn->rem_auth = 0xff;
+       bacpy(&conn->bdaddr, bdaddr);
+
+       dev->connections = g_slist_append(dev->connections, conn);
+
+       return conn;
+}
+
+static int get_handle(int index, bdaddr_t *bdaddr, uint16_t *handle)
+{
+       struct dev_info *dev = &devs[index];
+       struct bt_conn *conn;
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       conn = find_connection(dev, bdaddr);
+       if (conn == NULL)
+               return -ENOENT;
+
+       *handle = conn->handle;
+
+       return 0;
+}
+
+static int disconnect_addr(int index, bdaddr_t *dba, uint8_t reason)
+{
+       disconnect_cp cp;
+       uint16_t handle;
+       int err;
+
+       err = get_handle(index, dba, &handle);
+       if (err < 0)
+               return err;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = htobs(handle);
+       cp.reason = reason;
+
+       if (hci_send_cmd(devs[index].sk, OGF_LINK_CTL, OCF_DISCONNECT,
+                                               DISCONNECT_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static void bonding_complete(struct dev_info *dev, struct bt_conn *conn,
+                                                               uint8_t status)
+{
+       DBG("status 0x%02x", status);
+
+       if (conn->io != NULL) {
+               /* bonding_connect_cb takes care of the successul case */
+               if (status != 0)
+                       g_io_channel_shutdown(conn->io, TRUE, NULL);
+               g_io_channel_unref(conn->io);
+               conn->io = NULL;
+       }
+
+       conn->bonding_initiator = FALSE;
+
+       btd_event_bonding_complete(&dev->bdaddr, &conn->bdaddr, status);
+}
+
+static int get_auth_info(int index, bdaddr_t *bdaddr, uint8_t *auth)
+{
+       struct dev_info *dev = &devs[index];
+       struct hci_auth_info_req req;
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       memset(&req, 0, sizeof(req));
+       bacpy(&req.bdaddr, bdaddr);
+
+       if (ioctl(dev->sk, HCIGETAUTHINFO, (unsigned long) &req) < 0)
+               return -errno;
+
+       if (auth)
+               *auth = req.type;
+
+       return 0;
+}
+
+/* Link Key handling */
+
+static void link_key_request(int index, bdaddr_t *dba)
+{
+       struct dev_info *dev = &devs[index];
+       struct link_key_info *key_info;
+       struct bt_conn *conn;
+       GSList *match;
+       char da[18];
+
+       ba2str(dba, da);
+       DBG("hci%d dba %s", index, da);
+
+       conn = get_connection(dev, dba);
+       if (conn->handle == 0)
+               conn->secmode3 = TRUE;
+
+       get_auth_info(index, dba, &conn->loc_auth);
+
+       DBG("kernel auth requirements = 0x%02x", conn->loc_auth);
+
+       match = g_slist_find_custom(dev->keys, dba, (GCompareFunc) bacmp);
+       if (match)
+               key_info = match->data;
+       else
+               key_info = NULL;
+
+       DBG("Matching key %s", key_info ? "found" : "not found");
+
+       if (key_info == NULL || (!dev->debug_keys && key_info->type == 0x03)) {
+               /* Link key not found */
+               hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY,
+                                                               6, dba);
+               return;
+       }
+
+       /* Link key found */
+
+       DBG("link key type 0x%02x", key_info->type);
+
+       /* Don't use unauthenticated combination keys if MITM is
+        * required */
+       if (key_info->type == 0x04 && conn->loc_auth != 0xff &&
+                                               (conn->loc_auth & 0x01))
+               hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY,
+                                                               6, dba);
+       else {
+               link_key_reply_cp lr;
+
+               memcpy(lr.link_key, key_info->key, 16);
+               bacpy(&lr.bdaddr, dba);
+
+               hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_LINK_KEY_REPLY,
+                                               LINK_KEY_REPLY_CP_SIZE, &lr);
+       }
+}
+
+static void link_key_notify(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_link_key_notify *evt = ptr;
+       bdaddr_t *dba = &evt->bdaddr;
+       struct link_key_info *key_info;
+       uint8_t old_key_type, key_type;
+       struct bt_conn *conn;
+       GSList *match;
+       char da[18];
+       uint8_t status = 0;
+
+       ba2str(dba, da);
+       DBG("hci%d dba %s type %d", index, da, evt->key_type);
+
+       conn = get_connection(dev, &evt->bdaddr);
+
+       match = g_slist_find_custom(dev->keys, dba, (GCompareFunc) bacmp);
+       if (match)
+               key_info = match->data;
+       else
+               key_info = NULL;
+
+       if (key_info == NULL) {
+               key_info = g_new0(struct link_key_info, 1);
+               bacpy(&key_info->bdaddr, &evt->bdaddr);
+               old_key_type = 0xff;
+       } else {
+               dev->keys = g_slist_remove(dev->keys, key_info);
+               old_key_type = key_info->type;
+       }
+
+       memcpy(key_info->key, evt->link_key, sizeof(evt->link_key));
+       key_info->type = evt->key_type;
+       key_info->pin_len = dev->pin_length;
+
+       key_type = evt->key_type;
+
+       DBG("key type 0x%02x old key type 0x%02x", key_type, old_key_type);
+       DBG("local auth 0x%02x and remote auth 0x%02x",
+                                       conn->loc_auth, conn->rem_auth);
+
+       if (key_type == 0x06) {
+               /* Some buggy controller combinations generate a changed
+                * combination key for legacy pairing even when there's no
+                * previous key */
+               if ((!conn || conn->rem_auth == 0xff) && old_key_type == 0xff)
+                       key_type = 0x00;
+               else if (old_key_type != 0xff)
+                       key_type = old_key_type;
+               else
+                       /* This is Changed Combination Link Key for
+                        * a temporary link key.*/
+                       goto done;
+       }
+
+       key_info->type = key_type;
+
+       /* Skip the storage check if this is a debug key */
+       if (key_type == 0x03)
+               goto done;
+
+       /* Store the link key persistently if one of the following is true:
+        * 1. this is a legacy link key
+        * 2. this is a changed combination key and there was a previously
+        *    stored one
+        * 3. neither local nor remote side had no-bonding as a requirement
+        * 4. the local side had dedicated bonding as a requirement
+        * 5. the remote side is using dedicated bonding since in that case
+        *    also the local requirements are set to dedicated bonding
+        * If none of the above match only keep the link key around for
+        * this connection and set the temporary flag for the device.
+        */
+       if (key_type < 0x03 || (key_type == 0x06 && old_key_type != 0xff) ||
+                       (conn->loc_auth > 0x01 && conn->rem_auth > 0x01) ||
+                       (conn->loc_auth == 0x02 || conn->loc_auth == 0x03) ||
+                       (conn->rem_auth == 0x02 || conn->rem_auth == 0x03)) {
+               int err;
+
+               err = btd_event_link_key_notify(&dev->bdaddr, dba,
+                                               evt->link_key, key_type,
+                                               dev->pin_length);
+
+               if (err == -ENODEV)
+                       status = HCI_OE_LOW_RESOURCES;
+               else if (err < 0)
+                       status = HCI_MEMORY_FULL;
+
+               goto done;
+       }
+
+done:
+       dev->pin_length = 0;
+
+       if (status != 0) {
+               g_free(key_info);
+               bonding_complete(dev, conn, status);
+               disconnect_addr(index, dba, status);
+               return;
+       }
+
+       dev->keys = g_slist_prepend(dev->keys, key_info);
+
+       /* If we're connected and not dedicated bonding initiators we're
+        * done with the bonding process */
+       if (!conn->bonding_initiator && conn->handle != 0)
+               bonding_complete(dev, conn, 0);
+}
+
+static void return_link_keys(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_return_link_keys *evt = ptr;
+       uint8_t num = evt->num_keys;
+       unsigned char key[16];
+       char da[18];
+       bdaddr_t dba;
+       int i;
+
+       DBG("hci%d num_keys %u", index, num);
+
+       ptr++;
+
+       for (i = 0; i < num; i++) {
+               bacpy(&dba, ptr); ba2str(&dba, da);
+               memcpy(key, ptr + 6, 16);
+
+               DBG("hci%d returned key for %s", index, da);
+
+               btd_event_returned_link_key(&dev->bdaddr, &dba);
+
+               ptr += 22;
+       }
+}
+
+/* Simple Pairing handling */
+
+static int hciops_confirm_reply(int index, bdaddr_t *bdaddr, gboolean success)
+{
+       struct dev_info *dev = &devs[index];
+       user_confirm_reply_cp cp;
+       char addr[18];
+       int err;
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s success %d", index, addr, success);
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+
+       if (success)
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                       OCF_USER_CONFIRM_REPLY,
+                                       USER_CONFIRM_REPLY_CP_SIZE, &cp);
+       else
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                       OCF_USER_CONFIRM_NEG_REPLY,
+                                       USER_CONFIRM_REPLY_CP_SIZE, &cp);
+
+       if (err < 0)
+               err = -errno;
+
+       return err;
+}
+
+static void user_confirm_request(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_user_confirm_request *req = ptr;
+       gboolean loc_mitm, rem_mitm;
+       struct bt_conn *conn;
+
+       DBG("hci%d", index);
+
+       conn = find_connection(dev, &req->bdaddr);
+       if (conn == NULL)
+               return;
+
+       loc_mitm = (conn->loc_auth & 0x01) ? TRUE : FALSE;
+       rem_mitm = (conn->rem_auth & 0x01) ? TRUE : FALSE;
+
+       /* If we require MITM but the remote device can't provide that
+        * (it has NoInputNoOutput) then reject the confirmation
+        * request. The only exception is when we're dedicated bonding
+        * initiators since then we always have the MITM bit set. */
+       if (!conn->bonding_initiator && loc_mitm && conn->rem_cap == 0x03) {
+               error("Rejecting request: remote device can't provide MITM");
+               goto fail;
+       }
+
+       /* If no side requires MITM protection; auto-accept */
+       if ((conn->loc_auth == 0xff || !loc_mitm || conn->rem_cap == 0x03) &&
+                                       (!rem_mitm || conn->loc_cap == 0x03)) {
+               DBG("auto accept of confirmation");
+
+               /* Wait 5 milliseconds before doing auto-accept */
+               usleep(5000);
+
+               if (hciops_confirm_reply(index, &req->bdaddr, TRUE) < 0)
+                       goto fail;
+
+               return;
+       }
+
+       if (btd_event_user_confirm(&dev->bdaddr, &req->bdaddr,
+                                               btohl(req->passkey)) == 0)
+               return;
+
+fail:
+       hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY,
+                                                               6, ptr);
+}
+
+static void user_passkey_request(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_user_passkey_request *req = ptr;
+
+       DBG("hci%d", index);
+
+       if (btd_event_user_passkey(&dev->bdaddr, &req->bdaddr) < 0)
+               hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                               OCF_USER_PASSKEY_NEG_REPLY, 6, ptr);
+}
+
+static void user_passkey_notify(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_user_passkey_notify *req = ptr;
+
+       DBG("hci%d", index);
+
+       btd_event_user_notify(&dev->bdaddr, &req->bdaddr,
+                                               btohl(req->passkey));
+}
+
+static void remote_oob_data_request(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d", index);
+
+       hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                               OCF_REMOTE_OOB_DATA_NEG_REPLY, 6, ptr);
+}
+
+static int get_io_cap(int index, bdaddr_t *bdaddr, uint8_t *cap, uint8_t *auth)
+{
+       struct dev_info *dev = &devs[index];
+       struct bt_conn *conn;
+       int err;
+
+       conn = find_connection(dev, bdaddr);
+       if (conn == NULL)
+               return -ENOENT;
+
+       err = get_auth_info(index, bdaddr, &conn->loc_auth);
+       if (err < 0)
+               return err;
+
+       DBG("initial authentication requirement is 0x%02x", conn->loc_auth);
+
+       if (!dev->pairable && !conn->bonding_initiator) {
+               if (conn->rem_auth < 0x02) {
+                       DBG("Allowing no bonding in non-bondable mode");
+                       /* Kernel defaults to general bonding and so
+                        * overwrite for this special case. Otherwise
+                        * non-pairable test cases will fail. */
+                       conn->loc_auth = conn->rem_auth;
+                       goto done;
+               }
+
+               return -EPERM;
+       }
+
+       /* If the kernel doesn't know the local requirement just mirror
+        * the remote one */
+       if (conn->loc_auth == 0xff)
+               conn->loc_auth = conn->rem_auth;
+
+       if (conn->loc_auth == 0x00 || conn->loc_auth == 0x04) {
+               /* If remote requests dedicated bonding follow that lead */
+               if (conn->rem_auth == 0x02 || conn->rem_auth == 0x03) {
+
+                       /* If both remote and local IO capabilities allow MITM
+                        * then require it, otherwise don't */
+                       if (conn->rem_cap == 0x03 || conn->loc_cap == 0x03)
+                               conn->loc_auth = 0x02;
+                       else
+                               conn->loc_auth = 0x03;
+               }
+
+               /* If remote indicates no bonding then follow that. This
+                * is important since the kernel might give general bonding
+                * as default. */
+               if (conn->rem_auth == 0x00 || conn->rem_auth == 0x01)
+                       conn->loc_auth = 0x00;
+
+               /* If remote requires MITM then also require it, unless
+                * our IO capability is NoInputNoOutput (so some
+                * just-works security cases can be tested) */
+               if (conn->rem_auth != 0xff && (conn->rem_auth & 0x01) &&
+                                                       conn->loc_cap != 0x03)
+                       conn->loc_auth |= 0x01;
+       }
+
+done:
+       *cap = conn->loc_cap;
+       *auth = conn->loc_auth;
+
+       DBG("final authentication requirement is 0x%02x", *auth);
+
+       return 0;
+}
+
+static void io_capa_request(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       bdaddr_t *dba = ptr;
+       uint8_t cap, auth = 0xff;
+       char da[18];
+       int err;
+
+       ba2str(dba, da);
+       DBG("hci%d IO capability request for %s", index, da);
+
+       err = get_io_cap(index, dba, &cap, &auth);
+       if (err < 0) {
+               io_capability_neg_reply_cp cp;
+
+               error("Getting IO capability failed: %s (%d)",
+                                               strerror(-err), -err);
+
+               memset(&cp, 0, sizeof(cp));
+               bacpy(&cp.bdaddr, dba);
+               cp.reason = HCI_PAIRING_NOT_ALLOWED;
+               hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                       OCF_IO_CAPABILITY_NEG_REPLY,
+                                       IO_CAPABILITY_NEG_REPLY_CP_SIZE, &cp);
+       } else {
+               io_capability_reply_cp cp;
+               memset(&cp, 0, sizeof(cp));
+               bacpy(&cp.bdaddr, dba);
+               cp.capability = cap;
+               cp.oob_data = 0x00;
+               cp.authentication = auth;
+               hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_IO_CAPABILITY_REPLY,
+                                       IO_CAPABILITY_REPLY_CP_SIZE, &cp);
+       }
+}
+
+static void io_capa_response(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_io_capability_response *evt = ptr;
+       struct bt_conn *conn;
+       char da[18];
+
+       ba2str(&evt->bdaddr, da);
+       DBG("hci%d IO capability response from %s", index, da);
+
+       conn = find_connection(dev, &evt->bdaddr);
+       if (conn) {
+               conn->rem_cap = evt->capability;
+               conn->rem_auth = evt->authentication;
+       }
+}
+
+/* PIN code handling */
+
+static void pin_code_request(int index, bdaddr_t *dba)
+{
+       struct dev_info *dev = &devs[index];
+       struct bt_conn *conn;
+       char addr[18];
+       int err;
+
+       ba2str(dba, addr);
+       DBG("hci%d PIN request for %s", index, addr);
+
+       conn = get_connection(dev, dba);
+       if (conn->handle == 0)
+               conn->secmode3 = TRUE;
+
+       /* Check if the adapter is not pairable and if there isn't a bonding in
+        * progress */
+       if (!dev->pairable && !conn->bonding_initiator) {
+               DBG("Rejecting PIN request in non-pairable mode");
+               goto reject;
+       }
+
+       err = btd_event_request_pin(&dev->bdaddr, dba);
+       if (err < 0) {
+               error("PIN code negative reply: %s", strerror(-err));
+               goto reject;
+       }
+
+       return;
+
+reject:
+       hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, dba);
+}
+
+static void start_inquiry(bdaddr_t *local, uint8_t status, gboolean periodic)
+{
+       struct btd_adapter *adapter;
+       int state;
+
+       /* Don't send the signal if the cmd failed */
+       if (status) {
+               error("Inquiry Failed with status 0x%02x", status);
+               return;
+       }
+
+       adapter = manager_find_adapter(local);
+       if (!adapter) {
+               error("Unable to find matching adapter");
+               return;
+       }
+
+       state = adapter_get_state(adapter);
+
+       if (periodic)
+               state |= STATE_PINQ;
+       else
+               state |= STATE_STDINQ;
+
+       adapter_set_state(adapter, state);
+}
+
+static void inquiry_complete(bdaddr_t *local, uint8_t status,
+                                                       gboolean periodic)
+{
+       struct btd_adapter *adapter;
+       int state;
+
+       /* Don't send the signal if the cmd failed */
+       if (status) {
+               error("Inquiry Failed with status 0x%02x", status);
+               return;
+       }
+
+       adapter = manager_find_adapter(local);
+       if (!adapter) {
+               error("Unable to find matching adapter");
+               return;
+       }
+
+       state = adapter_get_state(adapter);
+       state &= ~(STATE_STDINQ | STATE_PINQ);
+       adapter_set_state(adapter, state);
+}
+
+static inline void remote_features_notify(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_remote_host_features_notify *evt = ptr;
+
+       if (evt->features[0] & 0x01)
+               btd_event_set_legacy_pairing(&dev->bdaddr, &evt->bdaddr,
+                                                                       FALSE);
+       else
+               btd_event_set_legacy_pairing(&dev->bdaddr, &evt->bdaddr,
+                                                                       TRUE);
+
+       write_features_info(&dev->bdaddr, &evt->bdaddr, NULL, evt->features);
+}
+
+static void write_le_host_complete(int index, uint8_t status)
+{
+       struct dev_info *dev = &devs[index];
+       uint8_t page_num = 0x01;
+
+       if (status)
+               return;
+
+       if (hci_send_cmd(dev->sk, OGF_INFO_PARAM,
+                               OCF_READ_LOCAL_EXT_FEATURES, 1, &page_num) < 0)
+               error("Unable to read extended local features: %s (%d)",
+                                               strerror(errno), errno);
+}
+
+static void read_local_version_complete(int index,
+                               const read_local_version_rp *rp)
+{
+       struct dev_info *dev = &devs[index];
+
+       if (rp->status)
+               return;
+
+       dev->ver.manufacturer = btohs(bt_get_unaligned(&rp->manufacturer));
+       dev->ver.hci_ver = rp->hci_ver;
+       dev->ver.hci_rev = btohs(bt_get_unaligned(&rp->hci_rev));
+       dev->ver.lmp_ver = rp->lmp_ver;
+       dev->ver.lmp_subver = btohs(bt_get_unaligned(&rp->lmp_subver));
+
+       if (!dev->pending)
+               return;
+
+       hci_clear_bit(PENDING_VERSION, &dev->pending);
+
+       DBG("Got version for hci%d", index);
+
+       if (!dev->pending && dev->up)
+               init_adapter(index);
+}
+
+static void read_local_features_complete(int index,
+                               const read_local_features_rp *rp)
+{
+       struct dev_info *dev = &devs[index];
+
+       if (rp->status)
+               return;
+
+       memcpy(dev->features, rp->features, 8);
+
+       if (!dev->pending)
+               return;
+
+       hci_clear_bit(PENDING_FEATURES, &dev->pending);
+
+       DBG("Got features for hci%d", index);
+
+       if (!dev->pending && dev->up)
+               init_adapter(index);
+}
+
+#define SIZEOF_UUID128 16
+
+static void eir_generate_uuid128(GSList *list, uint8_t *ptr, uint16_t *eir_len)
+{
+       int i, k, uuid_count = 0;
+       uint16_t len = *eir_len;
+       uint8_t *uuid128;
+       gboolean truncated = FALSE;
+
+       /* Store UUIDs in place, skip 2 bytes to write type and length later */
+       uuid128 = ptr + 2;
+
+       for (; list; list = list->next) {
+               struct uuid_info *uuid = list->data;
+               uint8_t *uuid128_data = uuid->uuid.value.uuid128.data;
+
+               if (uuid->uuid.type != SDP_UUID128)
+                       continue;
+
+               /* Stop if not enough space to put next UUID128 */
+               if ((len + 2 + SIZEOF_UUID128) > EIR_DATA_LENGTH) {
+                       truncated = TRUE;
+                       break;
+               }
+
+               /* Check for duplicates, EIR data is Little Endian */
+               for (i = 0; i < uuid_count; i++) {
+                       for (k = 0; k < SIZEOF_UUID128; k++) {
+                               if (uuid128[i * SIZEOF_UUID128 + k] !=
+                                       uuid128_data[SIZEOF_UUID128 - 1 - k])
+                                       break;
+                       }
+                       if (k == SIZEOF_UUID128)
+                               break;
+               }
+
+               if (i < uuid_count)
+                       continue;
+
+               /* EIR data is Little Endian */
+               for (k = 0; k < SIZEOF_UUID128; k++)
+                       uuid128[uuid_count * SIZEOF_UUID128 + k] =
+                               uuid128_data[SIZEOF_UUID128 - 1 - k];
+
+               len += SIZEOF_UUID128;
+               uuid_count++;
+       }
+
+       if (uuid_count > 0 || truncated) {
+               /* EIR Data length */
+               ptr[0] = (uuid_count * SIZEOF_UUID128) + 1;
+               /* EIR Data type */
+               ptr[1] = truncated ? EIR_UUID128_SOME : EIR_UUID128_ALL;
+               len += 2;
+               *eir_len = len;
+       }
+}
+
+static void create_ext_inquiry_response(int index, uint8_t *data)
+{
+       struct dev_info *dev = &devs[index];
+       GSList *l;
+       uint8_t *ptr = data;
+       uint16_t eir_len = 0;
+       uint16_t uuid16[EIR_DATA_LENGTH / 2];
+       int i, uuid_count = 0;
+       gboolean truncated = FALSE;
+       size_t name_len;
+
+       name_len = strlen(dev->name);
+
+       if (name_len > 0) {
+               /* EIR Data type */
+               if (name_len > 48) {
+                       name_len = 48;
+                       ptr[1] = EIR_NAME_SHORT;
+               } else
+                       ptr[1] = EIR_NAME_COMPLETE;
+
+               /* EIR Data length */
+               ptr[0] = name_len + 1;
+
+               memcpy(ptr + 2, dev->name, name_len);
+
+               eir_len += (name_len + 2);
+               ptr += (name_len + 2);
+       }
+
+       if (dev->tx_power != 0) {
+               *ptr++ = 2;
+               *ptr++ = EIR_TX_POWER;
+               *ptr++ = (uint8_t) dev->tx_power;
+               eir_len += 3;
+       }
+
+       if (dev->did_vendor != 0x0000) {
+               uint16_t source = 0x0002;
+               *ptr++ = 9;
+               *ptr++ = EIR_DEVICE_ID;
+               *ptr++ = (source & 0x00ff);
+               *ptr++ = (source & 0xff00) >> 8;
+               *ptr++ = (dev->did_vendor & 0x00ff);
+               *ptr++ = (dev->did_vendor & 0xff00) >> 8;
+               *ptr++ = (dev->did_product & 0x00ff);
+               *ptr++ = (dev->did_product & 0xff00) >> 8;
+               *ptr++ = (dev->did_version & 0x00ff);
+               *ptr++ = (dev->did_version & 0xff00) >> 8;
+               eir_len += 10;
+       }
+
+       /* Group all UUID16 types */
+       for (l = dev->uuids; l != NULL; l = g_slist_next(l)) {
+               struct uuid_info *uuid = l->data;
+
+               if (uuid->uuid.type != SDP_UUID16)
+                       continue;
+
+               if (uuid->uuid.value.uuid16 < 0x1100)
+                       continue;
+
+               if (uuid->uuid.value.uuid16 == PNP_INFO_SVCLASS_ID)
+                       continue;
+
+               /* Stop if not enough space to put next UUID16 */
+               if ((eir_len + 2 + sizeof(uint16_t)) > EIR_DATA_LENGTH) {
+                       truncated = TRUE;
+                       break;
+               }
+
+               /* Check for duplicates */
+               for (i = 0; i < uuid_count; i++)
+                       if (uuid16[i] == uuid->uuid.value.uuid16)
+                               break;
+
+               if (i < uuid_count)
+                       continue;
+
+               uuid16[uuid_count++] = uuid->uuid.value.uuid16;
+               eir_len += sizeof(uint16_t);
+       }
+
+       if (uuid_count > 0) {
+               /* EIR Data length */
+               ptr[0] = (uuid_count * sizeof(uint16_t)) + 1;
+               /* EIR Data type */
+               ptr[1] = truncated ? EIR_UUID16_SOME : EIR_UUID16_ALL;
+
+               ptr += 2;
+               eir_len += 2;
+
+               for (i = 0; i < uuid_count; i++) {
+                       *ptr++ = (uuid16[i] & 0x00ff);
+                       *ptr++ = (uuid16[i] & 0xff00) >> 8;
+               }
+       }
+
+       /* Group all UUID128 types */
+       if (eir_len <= EIR_DATA_LENGTH - 2)
+               eir_generate_uuid128(dev->uuids, ptr, &eir_len);
+}
+
+static void update_ext_inquiry_response(int index)
+{
+       struct dev_info *dev = &devs[index];
+       write_ext_inquiry_response_cp cp;
+
+       DBG("hci%d", index);
+
+       if (!(dev->features[6] & LMP_EXT_INQ))
+               return;
+
+       if (dev->ssp_mode == 0)
+               return;
+
+       if (dev->cache_enable)
+               return;
+
+       memset(&cp, 0, sizeof(cp));
+
+       create_ext_inquiry_response(index, cp.data);
+
+       if (memcmp(cp.data, dev->eir, sizeof(cp.data)) == 0)
+               return;
+
+       memcpy(dev->eir, cp.data, sizeof(cp.data));
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL,
+                               OCF_WRITE_EXT_INQUIRY_RESPONSE,
+                               WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE, &cp) < 0)
+               error("Unable to write EIR data: %s (%d)",
+                                               strerror(errno), errno);
+}
+
+static void update_name(int index, const char *name)
+{
+       struct btd_adapter *adapter;
+
+       adapter = manager_find_adapter_by_id(index);
+       if (adapter)
+               adapter_update_local_name(adapter, name);
+
+       update_ext_inquiry_response(index);
+}
+
+static void read_local_name_complete(int index, read_local_name_rp *rp)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d status %u", index, rp->status);
+
+       if (rp->status)
+               return;
+
+       memcpy(dev->name, rp->name, 248);
+
+       if (!dev->pending) {
+               update_name(index, (char *) rp->name);
+               return;
+       }
+
+       hci_clear_bit(PENDING_NAME, &dev->pending);
+
+       DBG("Got name for hci%d", index);
+
+       /* Even though it shouldn't happen (assuming the kernel behaves
+        * properly) it seems like we might miss the very first
+        * initialization commands that the kernel sends. So check for
+        * it here (since read_local_name is one of the last init
+        * commands) and resend the first ones if we haven't seen
+        * their results yet */
+
+       if (hci_test_bit(PENDING_FEATURES, &dev->pending))
+               hci_send_cmd(dev->sk, OGF_INFO_PARAM,
+                                       OCF_READ_LOCAL_FEATURES, 0, NULL);
+
+       if (hci_test_bit(PENDING_VERSION, &dev->pending))
+               hci_send_cmd(dev->sk, OGF_INFO_PARAM,
+                                       OCF_READ_LOCAL_VERSION, 0, NULL);
+
+       if (!dev->pending)
+               init_adapter(index);
+}
+
+static void read_tx_power_complete(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+
+       read_inq_response_tx_power_level_rp *rp = ptr;
+
+       DBG("hci%d status %u", index, rp->status);
+
+       if (rp->status)
+               return;
+
+       dev->tx_power = rp->level;
+       update_ext_inquiry_response(index);
+}
+
+static void read_simple_pairing_mode_complete(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       read_simple_pairing_mode_rp *rp = ptr;
+       struct btd_adapter *adapter;
+
+       DBG("hci%d status %u", index, rp->status);
+
+       if (rp->status)
+               return;
+
+       dev->ssp_mode = rp->mode;
+       update_ext_inquiry_response(index);
+
+       adapter = manager_find_adapter(&dev->bdaddr);
+       if (!adapter) {
+               error("No matching adapter found");
+               return;
+       }
+
+       adapter_update_ssp_mode(adapter, rp->mode);
+}
+
+static void read_local_ext_features_complete(int index,
+                               const read_local_ext_features_rp *rp)
+{
+       struct btd_adapter *adapter;
+
+       DBG("hci%d status %u", index, rp->status);
+
+       if (rp->status)
+               return;
+
+       adapter = manager_find_adapter_by_id(index);
+       if (!adapter) {
+               error("No matching adapter found");
+               return;
+       }
+
+       /* Local Extended feature page number is 1 */
+       if (rp->page_num != 1)
+               return;
+
+       btd_adapter_update_local_ext_features(adapter, rp->features);
+}
+
+static void read_bd_addr_complete(int index, read_bd_addr_rp *rp)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d status %u", index, rp->status);
+
+       if (rp->status)
+               return;
+
+       bacpy(&dev->bdaddr, &rp->bdaddr);
+
+       if (!dev->pending)
+               return;
+
+       hci_clear_bit(PENDING_BDADDR, &dev->pending);
+
+       DBG("Got bdaddr for hci%d", index);
+
+       if (!dev->pending && dev->up)
+               init_adapter(index);
+}
+
+static inline void cmd_status(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_cmd_status *evt = ptr;
+       uint16_t opcode = btohs(evt->opcode);
+
+       if (opcode == cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY))
+               start_inquiry(&dev->bdaddr, evt->status, FALSE);
+}
+
+static void read_scan_complete(int index, uint8_t status, void *ptr)
+{
+       struct btd_adapter *adapter;
+       read_scan_enable_rp *rp = ptr;
+
+       DBG("hci%d status %u", index, status);
+
+       adapter = manager_find_adapter_by_id(index);
+       if (!adapter) {
+               error("Unable to find matching adapter");
+               return;
+       }
+
+       adapter_mode_changed(adapter, rp->enable);
+}
+
+static int write_class(int index, uint32_t class)
+{
+       struct dev_info *dev = &devs[index];
+       write_class_of_dev_cp cp;
+
+       DBG("hci%d class 0x%06x", index, class);
+
+       memcpy(cp.dev_class, &class, 3);
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV,
+                                       WRITE_CLASS_OF_DEV_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       dev->pending_cod = class;
+
+       return 0;
+}
+
+/* Limited Discoverable bit mask in CoD */
+#define LIMITED_BIT                    0x002000
+
+static int hciops_set_limited_discoverable(int index, gboolean limited)
+{
+       struct dev_info *dev = &devs[index];
+       int num = (limited ? 2 : 1);
+       uint8_t lap[] = { 0x33, 0x8b, 0x9e, 0x00, 0x8b, 0x9e };
+       write_current_iac_lap_cp cp;
+
+       DBG("hci%d limited %d", index, limited);
+
+       /* Check if limited bit needs to be set/reset */
+       if (limited)
+               dev->wanted_cod |= LIMITED_BIT;
+       else
+               dev->wanted_cod &= ~LIMITED_BIT;
+
+       /* If we dont need the toggling, save an unnecessary CoD write */
+       if (dev->pending_cod || dev->wanted_cod == dev->current_cod)
+               return 0;
+
+       /*
+        * 1: giac
+        * 2: giac + liac
+        */
+       memset(&cp, 0, sizeof(cp));
+       cp.num_current_iac = num;
+       memcpy(&cp.lap, lap, num * 3);
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_WRITE_CURRENT_IAC_LAP,
+                                               (num * 3 + 1), &cp) < 0)
+               return -errno;
+
+       return write_class(index, dev->wanted_cod);
+}
+
+static void write_class_complete(int index, uint8_t status)
+{
+       struct dev_info *dev = &devs[index];
+       struct btd_adapter *adapter;
+
+       if (status)
+               return;
+
+       if (dev->pending_cod == 0)
+               return;
+
+       dev->current_cod = dev->pending_cod;
+       dev->pending_cod = 0;
+
+       adapter = manager_find_adapter(&dev->bdaddr);
+       if (adapter)
+               btd_adapter_class_changed(adapter, dev->current_cod);
+
+       update_ext_inquiry_response(index);
+
+       if (dev->wanted_cod == dev->current_cod)
+               return;
+
+       if (dev->wanted_cod & LIMITED_BIT &&
+                       !(dev->current_cod & LIMITED_BIT))
+               hciops_set_limited_discoverable(index, TRUE);
+       else if (!(dev->wanted_cod & LIMITED_BIT) &&
+                                       (dev->current_cod & LIMITED_BIT))
+               hciops_set_limited_discoverable(index, FALSE);
+       else
+               write_class(index, dev->wanted_cod);
+}
+
+static inline void cmd_complete(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_cmd_complete *evt = ptr;
+       uint16_t opcode = btohs(evt->opcode);
+       uint8_t status = *((uint8_t *) ptr + EVT_CMD_COMPLETE_SIZE);
+
+       switch (opcode) {
+       case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_VERSION):
+               ptr += sizeof(evt_cmd_complete);
+               read_local_version_complete(index, ptr);
+               break;
+       case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_FEATURES):
+               ptr += sizeof(evt_cmd_complete);
+               read_local_features_complete(index, ptr);
+               break;
+       case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_EXT_FEATURES):
+               ptr += sizeof(evt_cmd_complete);
+               read_local_ext_features_complete(index, ptr);
+               break;
+       case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_BD_ADDR):
+               ptr += sizeof(evt_cmd_complete);
+               read_bd_addr_complete(index, ptr);
+               break;
+       case cmd_opcode_pack(OGF_LINK_CTL, OCF_PERIODIC_INQUIRY):
+               start_inquiry(&dev->bdaddr, status, TRUE);
+               break;
+       case cmd_opcode_pack(OGF_LINK_CTL, OCF_EXIT_PERIODIC_INQUIRY):
+               inquiry_complete(&dev->bdaddr, status, TRUE);
+               break;
+       case cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY_CANCEL):
+               inquiry_complete(&dev->bdaddr, status, FALSE);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_LE_HOST_SUPPORTED):
+               write_le_host_complete(index, status);
+               break;
+       case cmd_opcode_pack(OGF_LE_CTL, OCF_LE_SET_SCAN_ENABLE):
+               btd_event_le_set_scan_enable_complete(&dev->bdaddr, status);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME):
+               if (!status)
+                       hci_send_cmd(dev->sk, OGF_HOST_CTL,
+                                               OCF_READ_LOCAL_NAME, 0, 0);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE):
+               hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_READ_SCAN_ENABLE,
+                                                               0, NULL);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_SCAN_ENABLE):
+               ptr += sizeof(evt_cmd_complete);
+               read_scan_complete(index, status, ptr);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV):
+               write_class_complete(index, status);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SIMPLE_PAIRING_MODE):
+               if (!status)
+                       hci_send_cmd(dev->sk, OGF_HOST_CTL,
+                                       OCF_READ_SIMPLE_PAIRING_MODE, 0, NULL);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_SIMPLE_PAIRING_MODE):
+               ptr += sizeof(evt_cmd_complete);
+               read_simple_pairing_mode_complete(index, ptr);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_LOCAL_NAME):
+               ptr += sizeof(evt_cmd_complete);
+               read_local_name_complete(index, ptr);
+               break;
+       case cmd_opcode_pack(OGF_HOST_CTL,
+                                       OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL):
+               ptr += sizeof(evt_cmd_complete);
+               read_tx_power_complete(index, ptr);
+               break;
+       };
+}
+
+static inline void remote_name_information(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_remote_name_req_complete *evt = ptr;
+       char name[MAX_NAME_LENGTH + 1];
+
+       DBG("hci%d status %u", index, evt->status);
+
+       memset(name, 0, sizeof(name));
+
+       if (!evt->status)
+               memcpy(name, evt->name, MAX_NAME_LENGTH);
+
+       btd_event_remote_name(&dev->bdaddr, &evt->bdaddr, evt->status, name);
+}
+
+static inline void remote_version_information(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_read_remote_version_complete *evt = ptr;
+       struct bt_conn *conn;
+
+       DBG("hci%d status %u", index, evt->status);
+
+       if (evt->status)
+               return;
+
+       conn = find_conn_by_handle(dev, btohs(evt->handle));
+       if (conn == NULL)
+               return;
+
+       write_version_info(&dev->bdaddr, &conn->bdaddr,
+                               btohs(evt->manufacturer), evt->lmp_ver,
+                               btohs(evt->lmp_subver));
+}
+
+static inline void inquiry_result(int index, int plen, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       uint8_t num = *(uint8_t *) ptr++;
+       int i;
+
+       for (i = 0; i < num; i++) {
+               inquiry_info *info = ptr;
+               uint32_t class = info->dev_class[0] |
+                                               (info->dev_class[1] << 8) |
+                                               (info->dev_class[2] << 16);
+
+               btd_event_device_found(&dev->bdaddr, &info->bdaddr, class,
+                                                               0, NULL);
+               ptr += INQUIRY_INFO_SIZE;
+       }
+}
+
+static inline void inquiry_result_with_rssi(int index, int plen, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       uint8_t num = *(uint8_t *) ptr++;
+       int i;
+
+       if (!num)
+               return;
+
+       if ((plen - 1) / num == INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE) {
+               for (i = 0; i < num; i++) {
+                       inquiry_info_with_rssi_and_pscan_mode *info = ptr;
+                       uint32_t class = info->dev_class[0]
+                                               | (info->dev_class[1] << 8)
+                                               | (info->dev_class[2] << 16);
+
+                       btd_event_device_found(&dev->bdaddr, &info->bdaddr,
+                                               class, info->rssi, NULL);
+                       ptr += INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE;
+               }
+       } else {
+               for (i = 0; i < num; i++) {
+                       inquiry_info_with_rssi *info = ptr;
+                       uint32_t class = info->dev_class[0]
+                                               | (info->dev_class[1] << 8)
+                                               | (info->dev_class[2] << 16);
+
+                       btd_event_device_found(&dev->bdaddr, &info->bdaddr,
+                                               class, info->rssi, NULL);
+                       ptr += INQUIRY_INFO_WITH_RSSI_SIZE;
+               }
+       }
+}
+
+static inline void extended_inquiry_result(int index, int plen, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       uint8_t num = *(uint8_t *) ptr++;
+       int i;
+
+       for (i = 0; i < num; i++) {
+               extended_inquiry_info *info = ptr;
+               uint32_t class = info->dev_class[0]
+                                       | (info->dev_class[1] << 8)
+                                       | (info->dev_class[2] << 16);
+
+               btd_event_device_found(&dev->bdaddr, &info->bdaddr, class,
+                                               info->rssi, info->data);
+               ptr += EXTENDED_INQUIRY_INFO_SIZE;
+       }
+}
+
+static inline void remote_features_information(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_read_remote_features_complete *evt = ptr;
+       struct bt_conn *conn;
+
+       DBG("hci%d status %u", index, evt->status);
+
+       if (evt->status)
+               return;
+
+       conn = find_conn_by_handle(dev, btohs(evt->handle));
+       if (conn == NULL)
+               return;
+
+       write_features_info(&dev->bdaddr, &conn->bdaddr, evt->features, NULL);
+}
+
+struct remote_version_req {
+       int index;
+       uint16_t handle;
+};
+
+static gboolean __get_remote_version(gpointer user_data)
+{
+       struct remote_version_req *req = user_data;
+       struct dev_info *dev = &devs[req->index];
+       read_remote_version_cp cp;
+
+       DBG("hci%d handle %u", req->index, req->handle);
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = htobs(req->handle);
+
+       hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_READ_REMOTE_VERSION,
+                                       READ_REMOTE_VERSION_CP_SIZE, &cp);
+
+       return FALSE;
+}
+
+static void get_remote_version(int index, uint16_t handle)
+{
+       struct remote_version_req *req;
+
+       req = g_new0(struct remote_version_req, 1);
+       req->handle = handle;
+       req->index = index;
+
+       g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, 1, __get_remote_version,
+                                                               req, g_free);
+}
+
+static void conn_free(struct bt_conn *conn)
+{
+       if (conn->io != NULL) {
+               g_io_channel_shutdown(conn->io, TRUE, NULL);
+               g_io_channel_unref(conn->io);
+       }
+
+       g_free(conn);
+}
+
+static inline void conn_failed(int index, bdaddr_t *bdaddr, uint8_t status)
+{
+       struct dev_info *dev = &devs[index];
+       struct bt_conn *conn;
+
+       btd_event_conn_failed(&dev->bdaddr, bdaddr, status);
+
+       conn = find_connection(dev, bdaddr);
+       if (conn == NULL)
+               return;
+
+       bonding_complete(dev, conn, status);
+
+       dev->connections = g_slist_remove(dev->connections, conn);
+       conn_free(conn);
+}
+
+static inline void conn_complete(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_conn_complete *evt = ptr;
+       char filename[PATH_MAX];
+       char local_addr[18], peer_addr[18], *str;
+       struct bt_conn *conn;
+
+       if (evt->link_type != ACL_LINK)
+               return;
+
+       DBG("status 0x%02x", evt->status);
+
+       if (evt->status != 0) {
+               conn_failed(index, &evt->bdaddr, evt->status);
+               return;
+       }
+
+       conn = get_connection(dev, &evt->bdaddr);
+       conn->handle = btohs(evt->handle);
+
+       btd_event_conn_complete(&dev->bdaddr, &evt->bdaddr);
+
+       if (conn->secmode3)
+               bonding_complete(dev, conn, 0);
+
+       /* check if the remote version needs be requested */
+       ba2str(&dev->bdaddr, local_addr);
+       ba2str(&evt->bdaddr, peer_addr);
+
+       create_name(filename, sizeof(filename), STORAGEDIR, local_addr,
+                                                       "manufacturers");
+
+       str = textfile_get(filename, peer_addr);
+       if (!str)
+               get_remote_version(index, btohs(evt->handle));
+       else
+               free(str);
+}
+
+static inline void le_conn_complete(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_le_connection_complete *evt = ptr;
+       char filename[PATH_MAX];
+       char local_addr[18], peer_addr[18], *str;
+       struct bt_conn *conn;
+
+       if (evt->status) {
+               btd_event_conn_failed(&dev->bdaddr, &evt->peer_bdaddr,
+                                                               evt->status);
+               return;
+       }
+
+       conn = get_connection(dev, &evt->peer_bdaddr);
+       conn->handle = btohs(evt->handle);
+
+       btd_event_conn_complete(&dev->bdaddr, &evt->peer_bdaddr);
+
+       /* check if the remote version needs be requested */
+       ba2str(&dev->bdaddr, local_addr);
+       ba2str(&evt->peer_bdaddr, peer_addr);
+
+       create_name(filename, sizeof(filename), STORAGEDIR, local_addr,
+                                                       "manufacturers");
+
+       str = textfile_get(filename, peer_addr);
+       if (!str)
+               get_remote_version(index, btohs(evt->handle));
+       else
+               free(str);
+}
+
+static inline void disconn_complete(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_disconn_complete *evt = ptr;
+       struct bt_conn *conn;
+
+       DBG("handle %u status 0x%02x", btohs(evt->handle), evt->status);
+
+       if (evt->status != 0)
+               return;
+
+       conn = find_conn_by_handle(dev, btohs(evt->handle));
+       if (conn == NULL)
+               return;
+
+       dev->connections = g_slist_remove(dev->connections, conn);
+
+       btd_event_disconn_complete(&dev->bdaddr, &conn->bdaddr);
+
+       conn_free(conn);
+}
+
+static inline void auth_complete(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_auth_complete *evt = ptr;
+       struct bt_conn *conn;
+
+       DBG("hci%d status %u", index, evt->status);
+
+       conn = find_conn_by_handle(dev, btohs(evt->handle));
+       if (conn == NULL)
+               return;
+
+       bonding_complete(dev, conn, evt->status);
+}
+
+static inline void simple_pairing_complete(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_simple_pairing_complete *evt = ptr;
+
+       DBG("hci%d status %u", index, evt->status);
+
+       btd_event_simple_pairing_complete(&dev->bdaddr, &evt->bdaddr,
+                                                               evt->status);
+}
+
+static inline void conn_request(int index, void *ptr)
+{
+       struct dev_info *dev = &devs[index];
+       evt_conn_request *evt = ptr;
+       uint32_t class = evt->dev_class[0] | (evt->dev_class[1] << 8)
+                               | (evt->dev_class[2] << 16);
+
+       btd_event_remote_class(&dev->bdaddr, &evt->bdaddr, class);
+}
+
+static inline void le_advertising_report(int index, evt_le_meta_event *meta)
+{
+       struct dev_info *dev = &devs[index];
+       le_advertising_info *info;
+       uint8_t num_reports;
+       const uint8_t RSSI_SIZE = 1;
+
+       num_reports = meta->data[0];
+
+       info = (le_advertising_info *) &meta->data[1];
+       btd_event_advertising_report(&dev->bdaddr, info);
+       num_reports--;
+
+       while (num_reports--) {
+               info = (le_advertising_info *) (info->data + info->length +
+                                                               RSSI_SIZE);
+               btd_event_advertising_report(&dev->bdaddr, info);
+       }
+}
+
+static inline void le_metaevent(int index, void *ptr)
+{
+       evt_le_meta_event *meta = ptr;
+
+       DBG("hci%d LE Meta Event %u", index, meta->subevent);
+
+       switch (meta->subevent) {
+       case EVT_LE_ADVERTISING_REPORT:
+               le_advertising_report(index, meta);
+               break;
+
+       case EVT_LE_CONN_COMPLETE:
+               le_conn_complete(index, meta->data);
+               break;
+       }
+}
+
+static void stop_hci_dev(int index)
+{
+       struct dev_info *dev = &devs[index];
+
+       if (dev->sk < 0)
+               return;
+
+       info("Stopping hci%d event socket", index);
+
+       if (dev->watch_id > 0)
+               g_source_remove(dev->watch_id);
+
+       if (dev->io != NULL)
+               g_io_channel_unref(dev->io);
+
+       hci_close_dev(dev->sk);
+
+       g_slist_foreach(dev->keys, (GFunc) g_free, NULL);
+       g_slist_free(dev->keys);
+
+       g_slist_foreach(dev->uuids, (GFunc) g_free, NULL);
+       g_slist_free(dev->uuids);
+
+       g_slist_foreach(dev->connections, (GFunc) conn_free, NULL);
+       g_slist_free(dev->connections);
+
+       init_dev_info(index, -1, dev->registered, dev->already_up);
+}
+
+static gboolean io_security_event(GIOChannel *chan, GIOCondition cond,
+                                                               gpointer data)
+{
+       unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr = buf;
+       int type, index = GPOINTER_TO_INT(data);
+       struct dev_info *dev = &devs[index];
+       struct hci_dev_info di;
+       ssize_t len;
+       hci_event_hdr *eh;
+       evt_cmd_status *evt;
+       int fd;
+
+       if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) {
+               stop_hci_dev(index);
+               return FALSE;
+       }
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       len = read(fd, buf, sizeof(buf));
+       if (len < 0) {
+               if (errno == EAGAIN)
+                       return TRUE;
+               stop_hci_dev(index);
+               return FALSE;
+       }
+
+       type = *ptr++;
+
+       if (type != HCI_EVENT_PKT)
+               return TRUE;
+
+       eh = (hci_event_hdr *) ptr;
+       ptr += HCI_EVENT_HDR_SIZE;
+
+       memset(&di, 0, sizeof(di));
+       if (hci_devinfo(index, &di) == 0) {
+               bacpy(&dev->bdaddr, &di.bdaddr);
+
+               if (ignore_device(&di))
+                       return TRUE;
+       }
+
+       switch (eh->evt) {
+       case EVT_CMD_STATUS:
+               cmd_status(index, ptr);
+               break;
+
+       case EVT_CMD_COMPLETE:
+               cmd_complete(index, ptr);
+               break;
+
+       case EVT_REMOTE_NAME_REQ_COMPLETE:
+               remote_name_information(index, ptr);
+               break;
+
+       case EVT_READ_REMOTE_VERSION_COMPLETE:
+               remote_version_information(index, ptr);
+               break;
+
+       case EVT_READ_REMOTE_FEATURES_COMPLETE:
+               remote_features_information(index, ptr);
+               break;
+
+       case EVT_REMOTE_HOST_FEATURES_NOTIFY:
+               remote_features_notify(index, ptr);
+               break;
+
+       case EVT_INQUIRY_COMPLETE:
+               evt = (evt_cmd_status *) ptr;
+               inquiry_complete(&dev->bdaddr, evt->status, FALSE);
+               break;
+
+       case EVT_INQUIRY_RESULT:
+               inquiry_result(index, eh->plen, ptr);
+               break;
+
+       case EVT_INQUIRY_RESULT_WITH_RSSI:
+               inquiry_result_with_rssi(index, eh->plen, ptr);
+               break;
+
+       case EVT_EXTENDED_INQUIRY_RESULT:
+               extended_inquiry_result(index, eh->plen, ptr);
+               break;
+
+       case EVT_CONN_COMPLETE:
+               conn_complete(index, ptr);
+               break;
+
+       case EVT_DISCONN_COMPLETE:
+               disconn_complete(index, ptr);
+               break;
+
+       case EVT_AUTH_COMPLETE:
+               auth_complete(index, ptr);
+               break;
+
+       case EVT_SIMPLE_PAIRING_COMPLETE:
+               simple_pairing_complete(index, ptr);
+               break;
+
+       case EVT_CONN_REQUEST:
+               conn_request(index, ptr);
+               break;
+       case EVT_LE_META_EVENT:
+               le_metaevent(index, ptr);
+               break;
+       case EVT_PIN_CODE_REQ:
+               pin_code_request(index, (bdaddr_t *) ptr);
+               break;
+
+       case EVT_LINK_KEY_REQ:
+               link_key_request(index, (bdaddr_t *) ptr);
+               break;
+
+       case EVT_LINK_KEY_NOTIFY:
+               link_key_notify(index, ptr);
+               break;
+
+       case EVT_RETURN_LINK_KEYS:
+               return_link_keys(index, ptr);
+               break;
+
+       case EVT_IO_CAPABILITY_REQUEST:
+               io_capa_request(index, ptr);
+               break;
+
+       case EVT_IO_CAPABILITY_RESPONSE:
+               io_capa_response(index, ptr);
+               break;
+
+       case EVT_USER_CONFIRM_REQUEST:
+               user_confirm_request(index, ptr);
+               break;
+
+       case EVT_USER_PASSKEY_REQUEST:
+               user_passkey_request(index, ptr);
+               break;
+
+       case EVT_USER_PASSKEY_NOTIFY:
+               user_passkey_notify(index, ptr);
+               break;
+
+       case EVT_REMOTE_OOB_DATA_REQUEST:
+               remote_oob_data_request(index, ptr);
+               break;
+       }
+
+       return TRUE;
+}
+
+static void start_hci_dev(int index)
+{
+       struct dev_info *dev = &devs[index];
+       GIOChannel *chan = dev->io;
+       GIOCondition cond;
+       struct hci_filter flt;
+
+       if (chan)
+               return;
+
+       info("Listening for HCI events on hci%d", index);
+
+       /* Set filter */
+       hci_filter_clear(&flt);
+       hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
+       hci_filter_set_event(EVT_CMD_STATUS, &flt);
+       hci_filter_set_event(EVT_CMD_COMPLETE, &flt);
+       hci_filter_set_event(EVT_PIN_CODE_REQ, &flt);
+       hci_filter_set_event(EVT_LINK_KEY_REQ, &flt);
+       hci_filter_set_event(EVT_LINK_KEY_NOTIFY, &flt);
+       hci_filter_set_event(EVT_RETURN_LINK_KEYS, &flt);
+       hci_filter_set_event(EVT_IO_CAPABILITY_REQUEST, &flt);
+       hci_filter_set_event(EVT_IO_CAPABILITY_RESPONSE, &flt);
+       hci_filter_set_event(EVT_USER_CONFIRM_REQUEST, &flt);
+       hci_filter_set_event(EVT_USER_PASSKEY_REQUEST, &flt);
+       hci_filter_set_event(EVT_REMOTE_OOB_DATA_REQUEST, &flt);
+       hci_filter_set_event(EVT_USER_PASSKEY_NOTIFY, &flt);
+       hci_filter_set_event(EVT_KEYPRESS_NOTIFY, &flt);
+       hci_filter_set_event(EVT_SIMPLE_PAIRING_COMPLETE, &flt);
+       hci_filter_set_event(EVT_AUTH_COMPLETE, &flt);
+       hci_filter_set_event(EVT_REMOTE_NAME_REQ_COMPLETE, &flt);
+       hci_filter_set_event(EVT_READ_REMOTE_VERSION_COMPLETE, &flt);
+       hci_filter_set_event(EVT_READ_REMOTE_FEATURES_COMPLETE, &flt);
+       hci_filter_set_event(EVT_REMOTE_HOST_FEATURES_NOTIFY, &flt);
+       hci_filter_set_event(EVT_INQUIRY_COMPLETE, &flt);
+       hci_filter_set_event(EVT_INQUIRY_RESULT, &flt);
+       hci_filter_set_event(EVT_INQUIRY_RESULT_WITH_RSSI, &flt);
+       hci_filter_set_event(EVT_EXTENDED_INQUIRY_RESULT, &flt);
+       hci_filter_set_event(EVT_CONN_REQUEST, &flt);
+       hci_filter_set_event(EVT_CONN_COMPLETE, &flt);
+       hci_filter_set_event(EVT_DISCONN_COMPLETE, &flt);
+       hci_filter_set_event(EVT_LE_META_EVENT, &flt);
+       if (setsockopt(dev->sk, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
+               error("Can't set filter on hci%d: %s (%d)",
+                                               index, strerror(errno), errno);
+               return;
+       }
+
+       chan = g_io_channel_unix_new(dev->sk);
+       cond = G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR;
+       dev->watch_id = g_io_add_watch_full(chan, G_PRIORITY_LOW, cond,
+                                               io_security_event,
+                                               GINT_TO_POINTER(index), NULL);
+       dev->io = chan;
+       dev->pin_length = 0;
+
+}
+
+/* End of HCI event callbacks */
+
+static gboolean child_exit(GIOChannel *io, GIOCondition cond, void *user_data)
+{
+       int status, fd = g_io_channel_unix_get_fd(io);
+       pid_t child_pid;
+
+       if (read(fd, &child_pid, sizeof(child_pid)) != sizeof(child_pid)) {
+               error("child_exit: unable to read child pid from pipe");
+               return TRUE;
+       }
+
+       if (waitpid(child_pid, &status, 0) != child_pid)
+               error("waitpid(%d) failed", child_pid);
+       else
+               DBG("child %d exited", child_pid);
+
+       return TRUE;
+}
+
+static void at_child_exit(void)
+{
+       pid_t pid = getpid();
+
+       if (write(child_pipe[1], &pid, sizeof(pid)) != sizeof(pid))
+               error("unable to write to child pipe");
+}
+
+static void device_devup_setup(int index)
+{
+       struct dev_info *dev = &devs[index];
+       struct hci_dev_info di;
+       read_stored_link_key_cp cp;
+
+       DBG("hci%d", index);
+
+       if (hci_devinfo(index, &di) < 0)
+               return;
+
+       if (ignore_device(&di))
+               return;
+
+       bacpy(&dev->bdaddr, &di.bdaddr);
+       memcpy(dev->features, di.features, 8);
+
+       /* Set page timeout */
+       if ((main_opts.flags & (1 << HCID_SET_PAGETO))) {
+               write_page_timeout_cp cp;
+
+               cp.timeout = htobs(main_opts.pageto);
+               hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_WRITE_PAGE_TIMEOUT,
+                                       WRITE_PAGE_TIMEOUT_CP_SIZE, &cp);
+       }
+
+       bacpy(&cp.bdaddr, BDADDR_ANY);
+       cp.read_all = 1;
+       hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_READ_STORED_LINK_KEY,
+                                       READ_STORED_LINK_KEY_CP_SIZE, &cp);
+
+       if (!dev->pending)
+               init_adapter(index);
+}
+
+static void init_pending(int index)
+{
+       struct dev_info *dev = &devs[index];
+
+       hci_set_bit(PENDING_BDADDR, &dev->pending);
+       hci_set_bit(PENDING_VERSION, &dev->pending);
+       hci_set_bit(PENDING_FEATURES, &dev->pending);
+       hci_set_bit(PENDING_NAME, &dev->pending);
+}
+
+static struct dev_info *init_device(int index, gboolean already_up)
+{
+       struct dev_info *dev;
+       struct hci_dev_req dr;
+       int dd;
+       pid_t pid;
+
+       DBG("hci%d", index);
+
+       dd = hci_open_dev(index);
+       if (dd < 0) {
+               error("Unable to open hci%d: %s (%d)", index,
+                                               strerror(errno), errno);
+               return NULL;
+       }
+
+       if (index > max_dev) {
+               max_dev = index;
+               devs = g_realloc(devs, sizeof(devs[0]) * (max_dev + 1));
+       }
+
+       dev = init_dev_info(index, dd, FALSE, already_up);
+       init_pending(index);
+       start_hci_dev(index);
+
+       /* Avoid forking if nothing else has to be done */
+       if (already_up)
+               return dev;
+
+       /* Do initialization in the separate process */
+       pid = fork();
+       switch (pid) {
+               case 0:
+                       atexit(at_child_exit);
+                       break;
+               case -1:
+                       error("Fork failed. Can't init device hci%d: %s (%d)",
+                                       index, strerror(errno), errno);
+               default:
+                       DBG("child %d forked", pid);
+                       return dev;
+       }
+
+       memset(&dr, 0, sizeof(dr));
+       dr.dev_id = index;
+
+       /* Set link mode */
+       dr.dev_opt = main_opts.link_mode;
+       if (ioctl(dd, HCISETLINKMODE, (unsigned long) &dr) < 0)
+               error("Can't set link mode on hci%d: %s (%d)",
+                                               index, strerror(errno), errno);
+
+       /* Start HCI device */
+       if (ioctl(dd, HCIDEVUP, index) < 0 && errno != EALREADY) {
+               error("Can't init device hci%d: %s (%d)",
+                                       index, strerror(errno), errno);
+               goto fail;
+       }
+
+       hci_close_dev(dd);
+       exit(0);
+
+fail:
+       hci_close_dev(dd);
+       exit(1);
+}
+
+static void init_conn_list(int index)
+{
+       struct dev_info *dev = &devs[index];
+       struct hci_conn_list_req *cl;
+       struct hci_conn_info *ci;
+       int err, i;
+
+       DBG("hci%d", index);
+
+       cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
+
+       cl->dev_id = index;
+       cl->conn_num = 10;
+       ci = cl->conn_info;
+
+       if (ioctl(dev->sk, HCIGETCONNLIST, cl) < 0) {
+               error("Unable to get connection list: %s (%d)",
+                                               strerror(errno), errno);
+               goto failed;
+       }
+
+       for (i = 0; i < cl->conn_num; i++, ci++) {
+               struct bt_conn *conn;
+
+               if (ci->type != ACL_LINK)
+                       continue;
+
+               conn = get_connection(dev, &ci->bdaddr);
+               conn->handle = ci->handle;
+       }
+
+       err = 0;
+
+failed:
+       g_free(cl);
+}
+
+static void device_event(int event, int index)
+{
+       switch (event) {
+       case HCI_DEV_REG:
+               info("HCI dev %d registered", index);
+               init_device(index, FALSE);
+               break;
+
+       case HCI_DEV_UNREG:
+               info("HCI dev %d unregistered", index);
+               stop_hci_dev(index);
+               if (devs[index].registered)
+                       btd_manager_unregister_adapter(index);
+               break;
+
+       case HCI_DEV_UP:
+               info("HCI dev %d up", index);
+               devs[index].up = TRUE;
+               device_devup_setup(index);
+               break;
+
+       case HCI_DEV_DOWN:
+               info("HCI dev %d down", index);
+               devs[index].up = FALSE;
+               devs[index].pending_cod = 0;
+               devs[index].cache_enable = TRUE;
+               if (!devs[index].pending) {
+                       struct btd_adapter *adapter;
+
+                       adapter = manager_find_adapter_by_id(index);
+                       if (adapter)
+                               btd_adapter_stop(adapter);
+
+                       init_pending(index);
+               }
+               break;
+       }
+}
+
+static gboolean init_known_adapters(gpointer user_data)
+{
+       struct hci_dev_list_req *dl;
+       struct hci_dev_req *dr;
+       int i, err, ctl = GPOINTER_TO_INT(user_data);
+       size_t req_size;
+
+       DBG("");
+
+       req_size = HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t);
+
+       dl = g_try_malloc0(req_size);
+       if (!dl) {
+               error("Can't allocate devlist buffer");
+               return FALSE;
+       }
+
+       dl->dev_num = HCI_MAX_DEV;
+       dr = dl->dev_req;
+
+       if (ioctl(ctl, HCIGETDEVLIST, dl) < 0) {
+               err = -errno;
+               error("Can't get device list: %s (%d)", strerror(-err), -err);
+               g_free(dl);
+               return FALSE;
+       }
+
+       for (i = 0; i < dl->dev_num; i++, dr++) {
+               struct dev_info *dev;
+               gboolean already_up;
+
+               already_up = hci_test_bit(HCI_UP, &dr->dev_opt);
+
+               dev = init_device(dr->dev_id, already_up);
+               if (dev == NULL)
+                       continue;
+
+               if (!dev->already_up)
+                       continue;
+
+               init_conn_list(dr->dev_id);
+
+               dev->pending = 0;
+               hci_set_bit(PENDING_VERSION, &dev->pending);
+               hci_send_cmd(dev->sk, OGF_INFO_PARAM,
+                                       OCF_READ_LOCAL_VERSION, 0, NULL);
+               device_event(HCI_DEV_UP, dr->dev_id);
+       }
+
+       g_free(dl);
+
+       return FALSE;
+}
+
+static gboolean io_stack_event(GIOChannel *chan, GIOCondition cond,
+                                                               gpointer data)
+{
+       unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr;
+       evt_stack_internal *si;
+       evt_si_device *sd;
+       hci_event_hdr *eh;
+       int type, fd;
+       ssize_t len;
+
+       ptr = buf;
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       len = read(fd, buf, sizeof(buf));
+       if (len < 0) {
+               if (errno == EAGAIN)
+                       return TRUE;
+
+               error("Read from control socket failed: %s (%d)",
+                                               strerror(errno), errno);
+               return FALSE;
+       }
+
+       type = *ptr++;
+
+       if (type != HCI_EVENT_PKT)
+               return TRUE;
+
+       eh = (hci_event_hdr *) ptr;
+       if (eh->evt != EVT_STACK_INTERNAL)
+               return TRUE;
+
+       ptr += HCI_EVENT_HDR_SIZE;
+
+       si = (evt_stack_internal *) ptr;
+       switch (si->type) {
+       case EVT_SI_DEVICE:
+               sd = (void *) &si->data;
+               device_event(sd->event, sd->dev_id);
+               break;
+       }
+
+       return TRUE;
+}
+
+static int hciops_setup(void)
+{
+       struct sockaddr_hci addr;
+       struct hci_filter flt;
+       GIOChannel *ctl_io, *child_io;
+       int sock, err;
+
+       DBG("");
+
+       if (child_pipe[0] != -1)
+               return -EALREADY;
+
+       if (pipe(child_pipe) < 0) {
+               err = -errno;
+               error("pipe(): %s (%d)", strerror(-err), -err);
+               return err;
+       }
+
+       child_io = g_io_channel_unix_new(child_pipe[0]);
+       g_io_channel_set_close_on_unref(child_io, TRUE);
+       child_io_id = g_io_add_watch(child_io,
+                               G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                               child_exit, NULL);
+       g_io_channel_unref(child_io);
+
+       /* Create and bind HCI socket */
+       sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+       if (sock < 0) {
+               err = -errno;
+               error("Can't open HCI socket: %s (%d)", strerror(-err),
+                                                               -err);
+               return err;
+       }
+
+       /* Set filter */
+       hci_filter_clear(&flt);
+       hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
+       hci_filter_set_event(EVT_STACK_INTERNAL, &flt);
+       if (setsockopt(sock, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
+               err = -errno;
+               error("Can't set filter: %s (%d)", strerror(-err), -err);
+               return err;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.hci_family = AF_BLUETOOTH;
+       addr.hci_dev = HCI_DEV_NONE;
+       if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               err = -errno;
+               error("Can't bind HCI socket: %s (%d)", strerror(-err), -err);
+               return err;
+       }
+
+       ctl_io = g_io_channel_unix_new(sock);
+       g_io_channel_set_close_on_unref(ctl_io, TRUE);
+
+       ctl_io_id = g_io_add_watch(ctl_io, G_IO_IN, io_stack_event, NULL);
+
+       g_io_channel_unref(ctl_io);
+
+       g_idle_add(init_known_adapters, GINT_TO_POINTER(sock));
+
+       return 0;
+}
+
+static void hciops_cleanup(void)
+{
+       int i;
+
+       DBG("");
+
+       for (i = 0; i <= max_dev; i++)
+               stop_hci_dev(i);
+
+       g_free(devs);
+       devs = NULL;
+       max_dev = -1;
+
+       if (child_io_id) {
+               g_source_remove(child_io_id);
+               child_io_id = 0;
+       }
+
+       if (ctl_io_id) {
+               g_source_remove(ctl_io_id);
+               ctl_io_id = 0;
+       }
+
+       if (child_pipe[0] >= 0) {
+               close(child_pipe[0]);
+               child_pipe[0] = -1;
+       }
+
+       if (child_pipe[1] >= 0) {
+               close(child_pipe[1]);
+               child_pipe[1] = -1;
+       }
+}
+
+static int hciops_set_powered(int index, gboolean powered)
+{
+       struct dev_info *dev = &devs[index];
+       int err;
+
+       DBG("hci%d powered %d", index, powered);
+
+       if (powered == FALSE)
+               return hciops_power_off(index);
+
+       if (ioctl(dev->sk, HCIDEVUP, index) == 0)
+               return 0;
+
+       if (errno == EALREADY)
+               return 0;
+
+       err = -errno;
+       error("Can't init device hci%d: %s (%d)",
+                                       index, strerror(-err), -err);
+
+       return err;
+}
+
+static int hciops_set_dev_class(int index, uint8_t major, uint8_t minor)
+{
+       struct dev_info *dev = &devs[index];
+       int err;
+
+       DBG("hci%d major %u minor %u", index, major, minor);
+
+       /* Update only the major and minor class bits keeping remaining bits
+        * intact*/
+       dev->wanted_cod &= 0xffe000;
+       dev->wanted_cod |= ((major & 0x1f) << 8) | minor;
+
+       if (dev->wanted_cod == dev->current_cod ||
+                       dev->cache_enable || dev->pending_cod)
+               return 0;
+
+       DBG("Changing Major/Minor class to 0x%06x", dev->wanted_cod);
+
+       err = write_class(index, dev->wanted_cod);
+       if (err < 0)
+               error("Adapter class update failed: %s (%d)",
+                                               strerror(-err), -err);
+
+       return err;
+}
+
+static int hciops_start_inquiry(int index, uint8_t length, gboolean periodic)
+{
+       struct dev_info *dev = &devs[index];
+       uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
+       int err;
+
+       DBG("hci%d length %u periodic %d", index, length, periodic);
+
+       if (periodic) {
+               periodic_inquiry_cp cp;
+
+               memset(&cp, 0, sizeof(cp));
+               memcpy(&cp.lap, lap, 3);
+               cp.max_period = htobs(24);
+               cp.min_period = htobs(16);
+               cp.length  = length;
+               cp.num_rsp = 0x00;
+
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                               OCF_PERIODIC_INQUIRY,
+                                               PERIODIC_INQUIRY_CP_SIZE, &cp);
+       } else {
+               inquiry_cp inq_cp;
+
+               memset(&inq_cp, 0, sizeof(inq_cp));
+               memcpy(&inq_cp.lap, lap, 3);
+               inq_cp.length = length;
+               inq_cp.num_rsp = 0x00;
+
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                       OCF_INQUIRY, INQUIRY_CP_SIZE, &inq_cp);
+       }
+
+       if (err < 0)
+               err = -errno;
+
+       return err;
+}
+
+static int le_set_scan_enable(int index, uint8_t enable)
+{
+       struct dev_info *dev = &devs[index];
+       le_set_scan_enable_cp cp;
+
+       DBG("hci%d enable %u", index, enable);
+
+       memset(&cp, 0, sizeof(cp));
+       cp.enable = enable;
+       cp.filter_dup = 0;
+
+       if (hci_send_cmd(dev->sk, OGF_LE_CTL, OCF_LE_SET_SCAN_ENABLE,
+                               LE_SET_SCAN_ENABLE_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_start_scanning(int index)
+{
+       struct dev_info *dev = &devs[index];
+       le_set_scan_parameters_cp cp;
+
+       DBG("hci%d", index);
+
+       memset(&cp, 0, sizeof(cp));
+       cp.type = 0x01;                 /* Active scanning */
+       /* The recommended value for scan interval and window is 11.25 msec.
+        * It is calculated by: time = n * 0.625 msec */
+       cp.interval = htobs(0x0012);
+       cp.window = htobs(0x0012);
+       cp.own_bdaddr_type = 0;         /* Public address */
+       cp.filter = 0;                  /* Accept all adv packets */
+
+       if (hci_send_cmd(dev->sk, OGF_LE_CTL, OCF_LE_SET_SCAN_PARAMETERS,
+                               LE_SET_SCAN_PARAMETERS_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return le_set_scan_enable(index, 1);
+}
+
+static int hciops_stop_scanning(int index)
+{
+       DBG("hci%d", index);
+
+       return le_set_scan_enable(index, 0);
+}
+
+static int hciops_resolve_name(int index, bdaddr_t *bdaddr)
+{
+       struct dev_info *dev = &devs[index];
+       remote_name_req_cp cp;
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+       cp.pscan_rep_mode = 0x02;
+
+       if (hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_REMOTE_NAME_REQ,
+                                       REMOTE_NAME_REQ_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_set_name(int index, const char *name)
+{
+       struct dev_info *dev = &devs[index];
+       change_local_name_cp cp;
+
+       DBG("hci%d, name %s", index, name);
+
+       memset(&cp, 0, sizeof(cp));
+       strncpy((char *) cp.name, name, sizeof(cp.name));
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME,
+                               CHANGE_LOCAL_NAME_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       memcpy(dev->name, cp.name, 248);
+       update_ext_inquiry_response(index);
+
+       return 0;
+}
+
+static int hciops_cancel_resolve_name(int index, bdaddr_t *bdaddr)
+{
+       struct dev_info *dev = &devs[index];
+       remote_name_req_cancel_cp cp;
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+
+       if (hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_REMOTE_NAME_REQ_CANCEL,
+                               REMOTE_NAME_REQ_CANCEL_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_fast_connectable(int index, gboolean enable)
+{
+       struct dev_info *dev = &devs[index];
+       write_page_activity_cp cp;
+       uint8_t type;
+
+       DBG("hci%d enable %d", index, enable);
+
+       if (enable) {
+               type = PAGE_SCAN_TYPE_INTERLACED;
+               cp.interval = 0x0024;   /* 22.5 msec page scan interval */
+       } else {
+               type = PAGE_SCAN_TYPE_STANDARD; /* default */
+               cp.interval = 0x0800;   /* default 1.28 sec page scan */
+       }
+
+       cp.window = 0x0012;     /* default 11.25 msec page scan window */
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_WRITE_PAGE_ACTIVITY,
+                                       WRITE_PAGE_ACTIVITY_CP_SIZE, &cp) < 0)
+               return -errno;
+       else if (hci_send_cmd(dev->sk, OGF_HOST_CTL,
+                               OCF_WRITE_PAGE_SCAN_TYPE, 1, &type) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_read_clock(int index, bdaddr_t *bdaddr, int which,
+                                               int timeout, uint32_t *clock,
+                                               uint16_t *accuracy)
+{
+       struct dev_info *dev = &devs[index];
+       uint16_t handle = 0;
+       char addr[18];
+       int ret;
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d addr %s which %d timeout %d", index, addr, which, timeout);
+
+       ret = get_handle(index, bdaddr, &handle);
+       if (ret < 0)
+               return ret;
+
+       if (hci_read_clock(dev->sk, htobs(handle), which, clock, accuracy,
+                                                               timeout) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_read_bdaddr(int index, bdaddr_t *bdaddr)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d", index);
+
+       bacpy(bdaddr, &dev->bdaddr);
+
+       return 0;
+}
+
+static int hciops_block_device(int index, bdaddr_t *bdaddr)
+{
+       struct dev_info *dev = &devs[index];
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       if (ioctl(dev->sk, HCIBLOCKADDR, bdaddr) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_unblock_device(int index, bdaddr_t *bdaddr)
+{
+       struct dev_info *dev = &devs[index];
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       if (ioctl(dev->sk, HCIUNBLOCKADDR, bdaddr) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_get_conn_list(int index, GSList **conns)
+{
+       struct dev_info *dev = &devs[index];
+       GSList *l;
+
+       DBG("hci%d", index);
+
+       *conns = NULL;
+
+       for (l = dev->connections; l != NULL; l = g_slist_next(l)) {
+               struct bt_conn *conn = l->data;
+
+               *conns = g_slist_append(*conns,
+                               g_memdup(&conn->bdaddr, sizeof(bdaddr_t)));
+       }
+
+       return 0;
+}
+
+static int hciops_read_local_version(int index, struct hci_version *ver)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d", index);
+
+       memcpy(ver, &dev->ver, sizeof(*ver));
+
+       return 0;
+}
+
+static int hciops_read_local_features(int index, uint8_t *features)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d", index);
+
+       memcpy(features, dev->features, 8);
+
+       return  0;
+}
+
+static int hciops_disconnect(int index, bdaddr_t *bdaddr)
+{
+       DBG("hci%d", index);
+
+       return disconnect_addr(index, bdaddr, HCI_OE_USER_ENDED_CONNECTION);
+}
+
+static int hciops_remove_bonding(int index, bdaddr_t *bdaddr)
+{
+       struct dev_info *dev = &devs[index];
+       delete_stored_link_key_cp cp;
+       GSList *match;
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       match = g_slist_find_custom(dev->keys, bdaddr, (GCompareFunc) bacmp);
+       if (match) {
+               g_free(match->data);
+               dev->keys = g_slist_delete_link(dev->keys, match);
+       }
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+
+       /* Delete the link key from the Bluetooth chip */
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL, OCF_DELETE_STORED_LINK_KEY,
+                               DELETE_STORED_LINK_KEY_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int hciops_pincode_reply(int index, bdaddr_t *bdaddr, const char *pin)
+{
+       struct dev_info *dev = &devs[index];
+       char addr[18];
+       int err;
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       if (pin) {
+               pin_code_reply_cp pr;
+               size_t len = strlen(pin);
+
+               dev->pin_length = len;
+
+               memset(&pr, 0, sizeof(pr));
+               bacpy(&pr.bdaddr, bdaddr);
+               memcpy(pr.pin_code, pin, len);
+               pr.pin_len = len;
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                               OCF_PIN_CODE_REPLY,
+                                               PIN_CODE_REPLY_CP_SIZE, &pr);
+       } else
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                       OCF_PIN_CODE_NEG_REPLY, 6, bdaddr);
+
+       if (err < 0)
+               err = -errno;
+
+       return err;
+}
+
+static int hciops_passkey_reply(int index, bdaddr_t *bdaddr, uint32_t passkey)
+{
+       struct dev_info *dev = &devs[index];
+       char addr[18];
+       int err;
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d dba %s", index, addr);
+
+       if (passkey != INVALID_PASSKEY) {
+               user_passkey_reply_cp cp;
+
+               memset(&cp, 0, sizeof(cp));
+               bacpy(&cp.bdaddr, bdaddr);
+               cp.passkey = passkey;
+
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                       OCF_USER_PASSKEY_REPLY,
+                                       USER_PASSKEY_REPLY_CP_SIZE, &cp);
+       } else
+               err = hci_send_cmd(dev->sk, OGF_LINK_CTL,
+                                       OCF_USER_PASSKEY_NEG_REPLY, 6, bdaddr);
+
+       if (err < 0)
+               err = -errno;
+
+       return err;
+}
+
+static int hciops_enable_le(int index)
+{
+       struct dev_info *dev = &devs[index];
+       write_le_host_supported_cp cp;
+
+       DBG("hci%d", index);
+
+       if (!(dev->features[4] & LMP_LE))
+               return -ENOTSUP;
+
+       cp.le = 0x01;
+       cp.simul = (dev->features[6] & LMP_LE_BREDR) ? 0x01 : 0x00;
+
+       if (hci_send_cmd(dev->sk, OGF_HOST_CTL,
+                               OCF_WRITE_LE_HOST_SUPPORTED,
+                               WRITE_LE_HOST_SUPPORTED_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static uint8_t generate_service_class(int index)
+{
+       struct dev_info *dev = &devs[index];
+       GSList *l;
+       uint8_t val = 0;
+
+       for (l = dev->uuids; l != NULL; l = g_slist_next(l)) {
+               struct uuid_info *uuid = l->data;
+
+               val |= uuid->svc_hint;
+       }
+
+       return val;
+}
+
+static int update_service_classes(int index)
+{
+       struct dev_info *dev = &devs[index];
+       uint8_t value;
+       int err;
+
+       value = generate_service_class(index);
+
+       DBG("hci%d value %u", index, value);
+
+       /* Update only the service class, keep the limited bit,
+        * major/minor class bits intact */
+       dev->wanted_cod &= 0x00ffff;
+       dev->wanted_cod |= (value << 16);
+
+       /* If the cache is enabled or an existing CoD write is in progress
+        * just bail out */
+       if (dev->cache_enable || dev->pending_cod)
+               return 0;
+
+       /* If we already have the CoD we want, update EIR and return */
+       if (dev->current_cod == dev->wanted_cod) {
+               update_ext_inquiry_response(index);
+               return 0;
+       }
+
+       DBG("Changing service classes to 0x%06x", dev->wanted_cod);
+
+       err = write_class(index, dev->wanted_cod);
+       if (err < 0)
+               error("Adapter class update failed: %s (%d)",
+                                               strerror(-err), -err);
+
+       return err;
+}
+
+static int hciops_add_uuid(int index, uuid_t *uuid, uint8_t svc_hint)
+{
+       struct dev_info *dev = &devs[index];
+       struct uuid_info *info;
+
+       DBG("hci%d", index);
+
+       info = g_new0(struct uuid_info, 1);
+       memcpy(&info->uuid, uuid, sizeof(*uuid));
+       info->svc_hint = svc_hint;
+
+       dev->uuids = g_slist_append(dev->uuids, info);
+
+       return update_service_classes(index);
+}
+
+static int hciops_remove_uuid(int index, uuid_t *uuid)
+{
+       struct dev_info *dev = &devs[index];
+       GSList *match;
+
+       match = g_slist_find_custom(dev->uuids, uuid, sdp_uuid_cmp);
+       if (match) {
+               g_free(match->data);
+               dev->uuids = g_slist_delete_link(dev->uuids, match);
+       }
+
+       DBG("hci%d", index);
+
+       return update_service_classes(index);
+}
+
+static int hciops_disable_cod_cache(int index)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d cache_enable %d", index, dev->cache_enable);
+
+       if (!dev->cache_enable)
+               return 0;
+
+       DBG("hci%d current_cod 0x%06x wanted_cod 0x%06x", index,
+                                       dev->current_cod, dev->wanted_cod);
+
+       /* Disable and flush svc cache. All successive service class
+        * updates * will be written to the device */
+       dev->cache_enable = FALSE;
+
+       if (dev->current_cod == dev->wanted_cod) {
+               update_ext_inquiry_response(index);
+               return 0;
+       }
+
+       return write_class(index, dev->wanted_cod);
+}
+
+static int hciops_restore_powered(int index)
+{
+       struct dev_info *dev = &devs[index];
+
+       if (!dev->already_up && dev->up)
+               return hciops_power_off(index);
+
+       return 0;
+}
+
+static int hciops_load_keys(int index, GSList *keys, gboolean debug_keys)
+{
+       struct dev_info *dev = &devs[index];
+
+       DBG("hci%d keys %d debug_keys %d", index, g_slist_length(keys),
+                                                               debug_keys);
+
+       if (dev->keys != NULL)
+               return -EEXIST;
+
+       dev->keys = keys;
+       dev->debug_keys = debug_keys;
+
+       return 0;
+}
+
+static int hciops_set_io_capability(int index, uint8_t io_capability)
+{
+       struct dev_info *dev = &devs[index];
+
+       dev->io_capability = io_capability;
+
+       return 0;
+}
+
+static int request_authentication(int index, bdaddr_t *bdaddr)
+{
+       struct dev_info *dev = &devs[index];
+       auth_requested_cp cp;
+       uint16_t handle;
+       int err;
+
+       DBG("hci%d", index);
+
+       err = get_handle(index, bdaddr, &handle);
+       if (err < 0)
+               return err;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = htobs(handle);
+
+       if (hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_AUTH_REQUESTED,
+                                       AUTH_REQUESTED_CP_SIZE, &cp) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static void bonding_connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+       struct bt_conn *conn = user_data;
+       struct dev_info *dev = conn->dev;
+
+       if (!conn->io) {
+               if (!err)
+                       g_io_channel_shutdown(io, TRUE, NULL);
+               return;
+       }
+
+       if (err)
+               /* Wait proper error to be propagated by bonding complete */
+               return;
+
+       if (request_authentication(dev->id, &conn->bdaddr) < 0)
+               goto failed;
+
+       return;
+
+failed:
+       bonding_complete(dev, conn, HCI_UNSPECIFIED_ERROR);
+}
+
+static int hciops_create_bonding(int index, bdaddr_t *bdaddr, uint8_t io_cap)
+{
+       struct dev_info *dev = &devs[index];
+       BtIOSecLevel sec_level;
+       struct bt_conn *conn;
+       GError *err = NULL;
+
+       conn = get_connection(dev, bdaddr);
+
+       if (conn->io != NULL)
+               return -EBUSY;
+
+       conn->loc_cap = io_cap;
+
+       /* If our IO capability is NoInputNoOutput use medium security
+        * level (i.e. don't require MITM protection) else use high
+        * security level */
+       if (io_cap == 0x03)
+               sec_level = BT_IO_SEC_MEDIUM;
+       else
+               sec_level = BT_IO_SEC_HIGH;
+
+       conn->io = bt_io_connect(BT_IO_L2RAW, bonding_connect_cb, conn,
+                                       NULL, &err,
+                                       BT_IO_OPT_SOURCE_BDADDR, &dev->bdaddr,
+                                       BT_IO_OPT_DEST_BDADDR, bdaddr,
+                                       BT_IO_OPT_SEC_LEVEL, sec_level,
+                                       BT_IO_OPT_INVALID);
+       if (conn->io == NULL) {
+               error("bt_io_connect: %s", err->message);
+               g_error_free(err);
+               return -EIO;
+       }
+
+       conn->bonding_initiator = TRUE;
+
+       return 0;
+}
+
+static int hciops_cancel_bonding(int index, bdaddr_t *bdaddr)
+{
+       struct dev_info *dev = &devs[index];
+       struct bt_conn *conn;
+
+       DBG("hci%d", index);
+
+       conn = find_connection(dev, bdaddr);
+       if (conn == NULL || conn->io == NULL)
+               return -ENOTCONN;
+
+       g_io_channel_shutdown(conn->io, TRUE, NULL);
+       g_io_channel_unref(conn->io);
+       conn->io = NULL;
+
+       return 0;
+}
+
+static struct btd_adapter_ops hci_ops = {
+       .setup = hciops_setup,
+       .cleanup = hciops_cleanup,
+       .set_powered = hciops_set_powered,
+       .set_discoverable = hciops_set_discoverable,
+       .set_pairable = hciops_set_pairable,
+       .set_limited_discoverable = hciops_set_limited_discoverable,
+       .start_inquiry = hciops_start_inquiry,
+       .stop_inquiry = hciops_stop_inquiry,
+       .start_scanning = hciops_start_scanning,
+       .stop_scanning = hciops_stop_scanning,
+       .resolve_name = hciops_resolve_name,
+       .cancel_resolve_name = hciops_cancel_resolve_name,
+       .set_name = hciops_set_name,
+       .set_dev_class = hciops_set_dev_class,
+       .set_fast_connectable = hciops_fast_connectable,
+       .read_clock = hciops_read_clock,
+       .read_bdaddr = hciops_read_bdaddr,
+       .block_device = hciops_block_device,
+       .unblock_device = hciops_unblock_device,
+       .get_conn_list = hciops_get_conn_list,
+       .read_local_version = hciops_read_local_version,
+       .read_local_features = hciops_read_local_features,
+       .disconnect = hciops_disconnect,
+       .remove_bonding = hciops_remove_bonding,
+       .pincode_reply = hciops_pincode_reply,
+       .confirm_reply = hciops_confirm_reply,
+       .passkey_reply = hciops_passkey_reply,
+       .enable_le = hciops_enable_le,
+       .encrypt_link = hciops_encrypt_link,
+       .set_did = hciops_set_did,
+       .add_uuid = hciops_add_uuid,
+       .remove_uuid = hciops_remove_uuid,
+       .disable_cod_cache = hciops_disable_cod_cache,
+       .restore_powered = hciops_restore_powered,
+       .load_keys = hciops_load_keys,
+       .set_io_capability = hciops_set_io_capability,
+       .create_bonding = hciops_create_bonding,
+       .cancel_bonding = hciops_cancel_bonding,
+};
+
+static int hciops_init(void)
+{
+       DBG("");
+       return btd_register_adapter_ops(&hci_ops, FALSE);
+}
+
+static void hciops_exit(void)
+{
+       DBG("");
+       btd_adapter_cleanup_ops(&hci_ops);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(hciops, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_LOW, hciops_init, hciops_exit)
diff --git a/plugins/maemo6.c b/plugins/maemo6.c
new file mode 100644 (file)
index 0000000..56f2664
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "adapter.h"
+#include "plugin.h"
+#include "log.h"
+#include "gdbus.h"
+
+/* from mce/mode-names.h */
+#define MCE_RADIO_STATE_BLUETOOTH      (1 << 3)
+
+/* from mce/dbus-names.h */
+#define MCE_SERVICE                    "com.nokia.mce"
+#define MCE_REQUEST_IF                 "com.nokia.mce.request"
+#define MCE_SIGNAL_IF                  "com.nokia.mce.signal"
+#define MCE_REQUEST_PATH               "/com/nokia/mce/request"
+#define MCE_SIGNAL_PATH                        "/com/nokia/mce/signal"
+#define MCE_RADIO_STATES_CHANGE_REQ    "req_radio_states_change"
+#define MCE_RADIO_STATES_GET           "get_radio_states"
+#define MCE_RADIO_STATES_SIG           "radio_states_ind"
+
+static guint watch_id;
+static DBusConnection *conn = NULL;
+static gboolean mce_bt_set = FALSE;
+static gboolean collision = FALSE;
+
+static gboolean mce_signal_callback(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
+{
+       DBusMessageIter args;
+       uint32_t sigvalue;
+       struct btd_adapter *adapter = user_data;
+
+       DBG("received mce signal");
+
+       if (!dbus_message_iter_init(message, &args))
+               error("message has no arguments");
+       else if (DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type(&args))
+               error("argument is not uint32");
+       else {
+               dbus_message_iter_get_basic(&args, &sigvalue);
+               DBG("got signal with value %u", sigvalue);
+
+               /* set the adapter according to the mce signal
+                  and remember the value */
+               mce_bt_set = sigvalue & MCE_RADIO_STATE_BLUETOOTH ?
+                                                               TRUE : FALSE;
+
+               if (mce_bt_set)
+                       btd_adapter_switch_online(adapter);
+               else
+                       btd_adapter_switch_offline(adapter);
+       }
+
+       return TRUE;
+}
+
+static void read_radio_states_cb(DBusPendingCall *call, void *user_data)
+{
+       DBusError err;
+       DBusMessage *reply;
+       dbus_uint32_t radio_states;
+       struct btd_adapter *adapter = user_data;
+
+       reply = dbus_pending_call_steal_reply(call);
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, reply)) {
+               error("mce replied with an error: %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_UINT32, &radio_states,
+                               DBUS_TYPE_INVALID) == FALSE) {
+               error("unable to parse get_radio_states reply: %s, %s",
+                                                       err.name, err.message);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       DBG("radio_states: %d", radio_states);
+
+       mce_bt_set = radio_states & MCE_RADIO_STATE_BLUETOOTH ? TRUE : FALSE;
+
+       /* check if the adapter has not completed the initial power
+        * cycle, if so delay action to mce_notify_powered */
+       collision = mce_bt_set && adapter_powering_down(adapter);
+
+       if (collision)
+               goto done;
+
+       if (mce_bt_set)
+               btd_adapter_switch_online(adapter);
+       else
+               btd_adapter_switch_offline(adapter);
+
+done:
+       dbus_message_unref(reply);
+}
+
+static void adapter_powered(struct btd_adapter *adapter, gboolean powered)
+{
+       DBusMessage *msg;
+       dbus_uint32_t radio_states = 0;
+       dbus_uint32_t radio_mask = MCE_RADIO_STATE_BLUETOOTH;
+       static gboolean startup = TRUE;
+
+       DBG("adapter_powered called with %d", powered);
+
+       if (startup) {
+               startup = FALSE;
+               return;
+       }
+
+       /* check if the plugin got the get_radio_states reply from the
+        * mce when the adapter was not yet down during the power
+        * cycling when bluetoothd is started */
+       if (collision) {
+               error("maemo6: powered state collision");
+               collision = FALSE;
+
+               if (mce_bt_set)
+                       btd_adapter_switch_online(adapter);
+
+               return;
+       }
+
+       /* nothing to do if the states match */
+       if (mce_bt_set == powered)
+               return;
+
+       /* set the mce value according to the state of the adapter */
+       msg = dbus_message_new_method_call(MCE_SERVICE, MCE_REQUEST_PATH,
+                               MCE_REQUEST_IF, MCE_RADIO_STATES_CHANGE_REQ);
+
+       if (powered)
+               radio_states = MCE_RADIO_STATE_BLUETOOTH;
+
+       dbus_message_append_args(msg, DBUS_TYPE_UINT32, &radio_states,
+                               DBUS_TYPE_UINT32, &radio_mask,
+                               DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send(conn, msg, NULL))
+               mce_bt_set = powered;
+       else
+               error("calling %s failed", MCE_RADIO_STATES_CHANGE_REQ);
+
+       dbus_message_unref(msg);
+}
+
+static int mce_probe(struct btd_adapter *adapter)
+{
+       DBusMessage *msg;
+       DBusPendingCall *call;
+
+       DBG("path %s", adapter_get_path(adapter));
+
+       msg = dbus_message_new_method_call(MCE_SERVICE, MCE_REQUEST_PATH,
+                                       MCE_REQUEST_IF, MCE_RADIO_STATES_GET);
+
+       if (!dbus_connection_send_with_reply(conn, msg, &call, -1)) {
+               error("calling %s failed", MCE_RADIO_STATES_GET);
+               dbus_message_unref(msg);
+               return -1;
+       }
+
+       dbus_pending_call_set_notify(call, read_radio_states_cb, adapter, NULL);
+       dbus_pending_call_unref(call);
+       dbus_message_unref(msg);
+
+       watch_id = g_dbus_add_signal_watch(conn, NULL, MCE_SIGNAL_PATH,
+                                       MCE_SIGNAL_IF, MCE_RADIO_STATES_SIG,
+                                       mce_signal_callback, adapter, NULL);
+
+       btd_adapter_register_powered_callback(adapter, adapter_powered);
+
+       return 0;
+}
+
+static void mce_remove(struct btd_adapter *adapter)
+{
+       DBG("path %s", adapter_get_path(adapter));
+
+       if (watch_id > 0)
+               g_dbus_remove_watch(conn, watch_id);
+
+       btd_adapter_unregister_powered_callback(adapter, adapter_powered);
+}
+
+static struct btd_adapter_driver mce_driver = {
+       .name   = "mce",
+       .probe  = mce_probe,
+       .remove = mce_remove,
+};
+
+static int maemo6_init(void)
+{
+       DBG("init maemo6 plugin");
+
+       conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (conn == NULL) {
+               error("Unable to connect to D-Bus");
+               return -1;
+       }
+
+       return btd_register_adapter_driver(&mce_driver);
+}
+
+static void maemo6_exit(void)
+{
+       DBG("exit maemo6 plugin");
+
+       if (conn != NULL)
+               dbus_connection_unref(conn);
+
+       btd_unregister_adapter_driver(&mce_driver);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(maemo6, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, maemo6_init, maemo6_exit)
diff --git a/plugins/mgmtops.c b/plugins/mgmtops.c
new file mode 100644 (file)
index 0000000..0c376f7
--- /dev/null
@@ -0,0 +1,1803 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/mgmt.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "adapter.h"
+#include "manager.h"
+#include "device.h"
+#include "event.h"
+
+#define MGMT_BUF_SIZE 1024
+
+static int max_index = -1;
+static struct controller_info {
+       gboolean valid;
+       gboolean notified;
+       uint8_t type;
+       bdaddr_t bdaddr;
+       uint8_t features[8];
+       uint8_t dev_class[3];
+       uint16_t manufacturer;
+       uint8_t hci_ver;
+       uint16_t hci_rev;
+       gboolean enabled;
+       gboolean connectable;
+       gboolean discoverable;
+       gboolean pairable;
+       uint8_t sec_mode;
+       GSList *connections;
+} *controllers = NULL;
+
+static int mgmt_sock = -1;
+static guint mgmt_watch = 0;
+
+static uint8_t mgmt_version = 0;
+static uint16_t mgmt_revision = 0;
+
+static void read_version_complete(int sk, void *buf, size_t len)
+{
+       struct mgmt_hdr hdr;
+       struct mgmt_rp_read_version *rp = buf;
+
+       if (len < sizeof(*rp)) {
+               error("Too small read version complete event");
+               return;
+       }
+
+       mgmt_revision = btohs(bt_get_unaligned(&rp->revision));
+       mgmt_version = rp->version;
+
+       DBG("version %u revision %u", mgmt_version, mgmt_revision);
+
+       memset(&hdr, 0, sizeof(hdr));
+       hdr.opcode = htobs(MGMT_OP_READ_INDEX_LIST);
+       hdr.index = htobs(MGMT_INDEX_NONE);
+       if (write(sk, &hdr, sizeof(hdr)) < 0)
+               error("Unable to read controller index list: %s (%d)",
+                                               strerror(errno), errno);
+}
+
+static void add_controller(uint16_t index)
+{
+       if (index > max_index) {
+               size_t size = sizeof(struct controller_info) * (index + 1);
+               max_index = index;
+               controllers = g_realloc(controllers, size);
+       }
+
+       memset(&controllers[index], 0, sizeof(struct controller_info));
+
+       controllers[index].valid = TRUE;
+
+       DBG("Added controller %u", index);
+}
+
+static void read_info(int sk, uint16_t index)
+{
+       struct mgmt_hdr hdr;
+
+       memset(&hdr, 0, sizeof(hdr));
+       hdr.opcode = htobs(MGMT_OP_READ_INFO);
+       hdr.index = htobs(index);
+
+       if (write(sk, &hdr, sizeof(hdr)) < 0)
+               error("Unable to send read_info command: %s (%d)",
+                                               strerror(errno), errno);
+}
+
+static void get_connections(int sk, uint16_t index)
+{
+       struct mgmt_hdr hdr;
+
+       memset(&hdr, 0, sizeof(hdr));
+       hdr.opcode = htobs(MGMT_OP_GET_CONNECTIONS);
+       hdr.index = htobs(index);
+
+       if (write(sk, &hdr, sizeof(hdr)) < 0)
+               error("Unable to send get_connections command: %s (%d)",
+                                               strerror(errno), errno);
+}
+
+static void mgmt_index_added(int sk, uint16_t index)
+{
+       add_controller(index);
+       read_info(sk, index);
+}
+
+static void remove_controller(uint16_t index)
+{
+       if (index > max_index)
+               return;
+
+       if (!controllers[index].valid)
+               return;
+
+       btd_manager_unregister_adapter(index);
+
+       memset(&controllers[index], 0, sizeof(struct controller_info));
+
+       DBG("Removed controller %u", index);
+}
+
+static void mgmt_index_removed(int sk, uint16_t index)
+{
+       remove_controller(index);
+}
+
+static int mgmt_set_mode(int index, uint16_t opcode, uint8_t val)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_mode)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_mode *cp = (void *) &buf[sizeof(*hdr)];
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(opcode);
+       hdr->index = htobs(index);
+       hdr->len = htobs(sizeof(*cp));
+
+       cp->val = val;
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int mgmt_set_connectable(int index, gboolean connectable)
+{
+       DBG("index %d connectable %d", index, connectable);
+       return mgmt_set_mode(index, MGMT_OP_SET_CONNECTABLE, connectable);
+}
+
+static int mgmt_set_discoverable(int index, gboolean discoverable)
+{
+       DBG("index %d discoverable %d", index, discoverable);
+       return mgmt_set_mode(index, MGMT_OP_SET_DISCOVERABLE, discoverable);
+}
+
+static int mgmt_set_pairable(int index, gboolean pairable)
+{
+       DBG("index %d pairable %d", index, pairable);
+       return mgmt_set_mode(index, MGMT_OP_SET_PAIRABLE, pairable);
+}
+
+static int mgmt_update_powered(int index, uint8_t powered)
+{
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+       gboolean pairable, discoverable;
+       uint8_t on_mode;
+
+       if (index > max_index) {
+               error("Unexpected index %u", index);
+               return -ENODEV;
+       }
+
+       info = &controllers[index];
+
+       info->enabled = powered;
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (adapter == NULL) {
+               DBG("Adapter not found");
+               return -ENODEV;
+       }
+
+       if (!powered) {
+               info->connectable = FALSE;
+               info->pairable = FALSE;
+               info->discoverable = FALSE;
+
+               btd_adapter_stop(adapter);
+               return 0;
+       }
+
+       btd_adapter_start(adapter);
+
+       btd_adapter_get_mode(adapter, NULL, &on_mode, &pairable);
+
+       discoverable = (on_mode == MODE_DISCOVERABLE);
+
+       if (on_mode == MODE_DISCOVERABLE && !info->discoverable)
+               mgmt_set_discoverable(index, TRUE);
+       else if (on_mode == MODE_CONNECTABLE && !info->connectable)
+               mgmt_set_connectable(index, TRUE);
+       else {
+               uint8_t mode = 0;
+
+               if (info->connectable)
+                       mode |= SCAN_PAGE;
+               if (info->discoverable)
+                       mode |= SCAN_INQUIRY;
+
+               adapter_mode_changed(adapter, mode);
+       }
+
+       if (info->pairable != pairable)
+               mgmt_set_pairable(index, pairable);
+
+       return 0;
+}
+
+static void mgmt_powered(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_mode *ev = buf;
+
+       if (len < sizeof(*ev)) {
+               error("Too small powered event");
+               return;
+       }
+
+       DBG("Controller %u powered %u", index, ev->val);
+
+       mgmt_update_powered(index, ev->val);
+}
+
+static void mgmt_discoverable(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_mode *ev = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+       uint8_t mode;
+
+       if (len < sizeof(*ev)) {
+               error("Too small discoverable event");
+               return;
+       }
+
+       DBG("Controller %u discoverable %u", index, ev->val);
+
+       if (index > max_index) {
+               error("Unexpected index %u in discoverable event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       info->discoverable = ev->val ? TRUE : FALSE;
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (!adapter)
+               return;
+
+       if (info->connectable)
+               mode = SCAN_PAGE;
+       else
+               mode = 0;
+
+       if (info->discoverable)
+               mode |= SCAN_INQUIRY;
+
+       adapter_mode_changed(adapter, mode);
+}
+
+static void mgmt_connectable(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_mode *ev = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+       uint8_t mode;
+
+       if (len < sizeof(*ev)) {
+               error("Too small connectable event");
+               return;
+       }
+
+       DBG("Controller %u connectable %u", index, ev->val);
+
+       if (index > max_index) {
+               error("Unexpected index %u in connectable event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       info->connectable = ev->val ? TRUE : FALSE;
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (!adapter)
+               return;
+
+       if (info->discoverable)
+               mode = SCAN_INQUIRY;
+       else
+               mode = 0;
+
+       if (info->connectable)
+               mode |= SCAN_PAGE;
+
+       adapter_mode_changed(adapter, mode);
+}
+
+static void mgmt_pairable(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_mode *ev = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+
+       if (len < sizeof(*ev)) {
+               error("Too small pairable event");
+               return;
+       }
+
+       DBG("Controller %u pairable %u", index, ev->val);
+
+       if (index > max_index) {
+               error("Unexpected index %u in pairable event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       info->pairable = ev->val ? TRUE : FALSE;
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (!adapter)
+               return;
+
+       btd_adapter_pairable_changed(adapter, info->pairable);
+}
+
+static void mgmt_new_key(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_ev_new_key *ev = buf;
+       struct controller_info *info;
+
+       if (len != sizeof(*ev)) {
+               error("new_key event size mismatch (%zu != %zu)",
+                                                       len, sizeof(*ev));
+               return;
+       }
+
+       DBG("Controller %u new key of type %u pin_len %u", index,
+                                       ev->key.type, ev->key.pin_len);
+
+       if (index > max_index) {
+               error("Unexpected index %u in new_key event", index);
+               return;
+       }
+
+       if (ev->key.pin_len > 16) {
+               error("Invalid PIN length (%u) in new_key event",
+                                                       ev->key.pin_len);
+               return;
+       }
+
+       info = &controllers[index];
+
+       btd_event_link_key_notify(&info->bdaddr, &ev->key.bdaddr,
+                                       ev->key.val, ev->key.type,
+                                       ev->key.pin_len);
+
+       btd_event_bonding_complete(&info->bdaddr, &ev->key.bdaddr, 0);
+}
+
+static void mgmt_device_connected(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_ev_device_connected *ev = buf;
+       struct controller_info *info;
+       char addr[18];
+
+       if (len < sizeof(*ev)) {
+               error("Too small device_connected event");
+               return;
+       }
+
+       ba2str(&ev->bdaddr, addr);
+
+       DBG("hci%u device %s connected", index, addr);
+
+       if (index > max_index) {
+               error("Unexpected index %u in device_connected event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       btd_event_conn_complete(&info->bdaddr, &ev->bdaddr);
+}
+
+static void mgmt_device_disconnected(int sk, uint16_t index, void *buf,
+                                                               size_t len)
+{
+       struct mgmt_ev_device_disconnected *ev = buf;
+       struct controller_info *info;
+       char addr[18];
+
+       if (len < sizeof(*ev)) {
+               error("Too small device_disconnected event");
+               return;
+       }
+
+       ba2str(&ev->bdaddr, addr);
+
+       DBG("hci%u device %s disconnected", index, addr);
+
+       if (index > max_index) {
+               error("Unexpected index %u in device_disconnected event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       btd_event_disconn_complete(&info->bdaddr, &ev->bdaddr);
+}
+
+static void mgmt_connect_failed(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_ev_connect_failed *ev = buf;
+       struct controller_info *info;
+       char addr[18];
+
+       if (len < sizeof(*ev)) {
+               error("Too small connect_failed event");
+               return;
+       }
+
+       ba2str(&ev->bdaddr, addr);
+
+       DBG("hci%u %s status %u", index, addr, ev->status);
+
+       if (index > max_index) {
+               error("Unexpected index %u in connect_failed event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       btd_event_conn_failed(&info->bdaddr, &ev->bdaddr, ev->status);
+
+       /* In the case of security mode 3 devices */
+       btd_event_bonding_complete(&info->bdaddr, &ev->bdaddr, ev->status);
+}
+
+static int mgmt_pincode_reply(int index, bdaddr_t *bdaddr, const char *pin)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_pin_code_reply)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       size_t buf_len;
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s pin %s", index, addr, pin ? pin : "<none>");
+
+       memset(buf, 0, sizeof(buf));
+
+       if (pin == NULL) {
+               struct mgmt_cp_pin_code_neg_reply *cp;
+
+               hdr->opcode = htobs(MGMT_OP_PIN_CODE_NEG_REPLY);
+               hdr->len = htobs(sizeof(*cp));
+               hdr->index = htobs(index);
+
+               cp = (void *) &buf[sizeof(*hdr)];
+               bacpy(&cp->bdaddr, bdaddr);
+
+               buf_len = sizeof(*hdr) + sizeof(*cp);
+       } else {
+               struct mgmt_cp_pin_code_reply *cp;
+               size_t pin_len;
+
+               pin_len = strlen(pin);
+               if (pin_len > 16)
+                       return -EINVAL;
+
+               hdr->opcode = htobs(MGMT_OP_PIN_CODE_REPLY);
+               hdr->len = htobs(sizeof(*cp));
+               hdr->index = htobs(index);
+
+               cp = (void *) &buf[sizeof(*hdr)];
+               bacpy(&cp->bdaddr, bdaddr);
+               cp->pin_len = pin_len;
+               memcpy(cp->pin_code, pin, pin_len);
+
+               buf_len = sizeof(*hdr) + sizeof(*cp);
+       }
+
+       if (write(mgmt_sock, buf, buf_len) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static void mgmt_pin_code_request(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_ev_pin_code_request *ev = buf;
+       struct controller_info *info;
+       char addr[18];
+       int err;
+
+       if (len < sizeof(*ev)) {
+               error("Too small pin_code_request event");
+               return;
+       }
+
+       ba2str(&ev->bdaddr, addr);
+
+       DBG("hci%u %s", index, addr);
+
+       if (index > max_index) {
+               error("Unexpected index %u in pin_code_request event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       err = btd_event_request_pin(&info->bdaddr, &ev->bdaddr);
+       if (err < 0) {
+               error("btd_event_request_pin: %s", strerror(-err));
+               mgmt_pincode_reply(index, &ev->bdaddr, NULL);
+       }
+}
+
+static int mgmt_confirm_reply(int index, bdaddr_t *bdaddr, gboolean success)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_user_confirm_reply)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_user_confirm_reply *cp;
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s success %d", index, addr, success);
+
+       memset(buf, 0, sizeof(buf));
+
+       if (success)
+               hdr->opcode = htobs(MGMT_OP_USER_CONFIRM_REPLY);
+       else
+               hdr->opcode = htobs(MGMT_OP_USER_CONFIRM_NEG_REPLY);
+
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       cp = (void *) &buf[sizeof(*hdr)];
+       bacpy(&cp->bdaddr, bdaddr);
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static void mgmt_user_confirm_request(int sk, uint16_t index, void *buf,
+                                                               size_t len)
+{
+       struct mgmt_ev_user_confirm_request *ev = buf;
+       struct controller_info *info;
+       char addr[18];
+       int err;
+
+       if (len < sizeof(*ev)) {
+               error("Too small user_confirm_request event");
+               return;
+       }
+
+       ba2str(&ev->bdaddr, addr);
+
+       DBG("hci%u %s", index, addr);
+
+       if (index > max_index) {
+               error("Unexpected index %u in user_confirm_request event",
+                                                                       index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       err = btd_event_user_confirm(&info->bdaddr, &ev->bdaddr,
+                                                       btohl(ev->value));
+       if (err < 0) {
+               error("btd_event_user_confirm: %s", strerror(-err));
+               mgmt_confirm_reply(index, &ev->bdaddr, FALSE);
+       }
+}
+
+static void uuid_to_uuid128(uuid_t *uuid128, const uuid_t *uuid)
+{
+       if (uuid->type == SDP_UUID16)
+               sdp_uuid16_to_uuid128(uuid128, uuid);
+       else if (uuid->type == SDP_UUID32)
+               sdp_uuid32_to_uuid128(uuid128, uuid);
+       else
+               memcpy(uuid128, uuid, sizeof(*uuid));
+}
+
+static int mgmt_add_uuid(int index, uuid_t *uuid, uint8_t svc_hint)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_add_uuid)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_add_uuid *cp = (void *) &buf[sizeof(*hdr)];
+       uuid_t uuid128;
+
+       DBG("index %d", index);
+
+       uuid_to_uuid128(&uuid128, uuid);
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(MGMT_OP_ADD_UUID);
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       memcpy(cp->uuid, uuid128.value.uuid128.data, 16);
+       cp->svc_hint = svc_hint;
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int mgmt_remove_uuid(int index, uuid_t *uuid)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_remove_uuid)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_remove_uuid *cp = (void *) &buf[sizeof(*hdr)];
+       uuid_t uuid128;
+
+       DBG("index %d", index);
+
+       uuid_to_uuid128(&uuid128, uuid);
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(MGMT_OP_REMOVE_UUID);
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       memcpy(cp->uuid, uuid128.value.uuid128.data, 16);
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int clear_uuids(int index)
+{
+       uuid_t uuid_any;
+
+       memset(&uuid_any, 0, sizeof(uuid_any));
+       uuid_any.type = SDP_UUID128;
+
+       return mgmt_remove_uuid(index, &uuid_any);
+}
+
+static void read_index_list_complete(int sk, void *buf, size_t len)
+{
+       struct mgmt_rp_read_index_list *rp = buf;
+       uint16_t num;
+       int i;
+
+       if (len < sizeof(*rp)) {
+               error("Too small read index list complete event");
+               return;
+       }
+
+       num = btohs(bt_get_unaligned(&rp->num_controllers));
+
+       if (num * sizeof(uint16_t) + sizeof(*rp) != len) {
+               error("Incorrect packet size for index list event");
+               return;
+       }
+
+       for (i = 0; i < num; i++) {
+               uint16_t index;
+
+               index = btohs(bt_get_unaligned(&rp->index[i]));
+
+               add_controller(index);
+               get_connections(sk, index);
+               clear_uuids(index);
+       }
+}
+
+static int mgmt_set_powered(int index, gboolean powered)
+{
+       DBG("index %d powered %d", index, powered);
+       return mgmt_set_mode(index, MGMT_OP_SET_POWERED, powered);
+}
+
+static void read_info_complete(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_rp_read_info *rp = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+       uint8_t mode;
+       char addr[18];
+
+       if (len < sizeof(*rp)) {
+               error("Too small read info complete event");
+               return;
+       }
+
+       if (index > max_index) {
+               error("Unexpected index %u in read info complete", index);
+               return;
+       }
+
+       mgmt_set_mode(index, MGMT_OP_SET_SERVICE_CACHE, 1);
+
+       info = &controllers[index];
+       info->type = rp->type;
+       info->enabled = rp->powered;
+       info->connectable = rp->connectable;
+       info->discoverable = rp->discoverable;
+       info->pairable = rp->pairable;
+       info->sec_mode = rp->sec_mode;
+       bacpy(&info->bdaddr, &rp->bdaddr);
+       memcpy(info->dev_class, rp->dev_class, 3);
+       memcpy(info->features, rp->features, 8);
+       info->manufacturer = btohs(bt_get_unaligned(&rp->manufacturer));
+       info->hci_ver = rp->hci_ver;
+       info->hci_rev = btohs(bt_get_unaligned(&rp->hci_rev));
+
+       ba2str(&info->bdaddr, addr);
+       DBG("hci%u type %u addr %s", index, info->type, addr);
+       DBG("hci%u class 0x%02x%02x%02x", index,
+               info->dev_class[2], info->dev_class[1], info->dev_class[0]);
+       DBG("hci%u manufacturer %d HCI ver %d:%d", index, info->manufacturer,
+                                               info->hci_ver, info->hci_rev);
+       DBG("hci%u enabled %u discoverable %u pairable %u sec_mode %u", index,
+                                       info->enabled, info->discoverable,
+                                       info->pairable, info->sec_mode);
+       DBG("hci%u name %s", index, (char *) rp->name);
+
+       adapter = btd_manager_register_adapter(index);
+       if (adapter == NULL) {
+               error("mgmtops: unable to register adapter");
+               return;
+       }
+
+       btd_adapter_get_mode(adapter, &mode, NULL, NULL);
+       if (mode == MODE_OFF) {
+               mgmt_set_powered(index, FALSE);
+               return;
+       }
+
+       if (info->enabled)
+               mgmt_update_powered(index, TRUE);
+       else
+               mgmt_set_powered(index, TRUE);
+
+       adapter_update_local_name(adapter, (char *) rp->name);
+
+       btd_adapter_unref(adapter);
+}
+
+static void set_powered_complete(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_mode *rp = buf;
+
+       if (len < sizeof(*rp)) {
+               error("Too small set powered complete event");
+               return;
+       }
+
+       DBG("hci%d powered %u", index, rp->val);
+
+       mgmt_update_powered(index, rp->val);
+}
+
+static void set_discoverable_complete(int sk, uint16_t index, void *buf,
+                                                               size_t len)
+{
+       struct mgmt_mode *rp = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+       uint8_t mode;
+
+       if (len < sizeof(*rp)) {
+               error("Too small set discoverable complete event");
+               return;
+       }
+
+       DBG("hci%d discoverable %u", index, rp->val);
+
+       if (index > max_index) {
+               error("Unexpected index %u in discoverable complete", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       info->discoverable = rp->val ? TRUE : FALSE;
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (!adapter)
+               return;
+
+       /* set_discoverable will always also change page scanning */
+       mode = SCAN_PAGE;
+
+       if (info->discoverable)
+               mode |= SCAN_INQUIRY;
+
+       adapter_mode_changed(adapter, mode);
+}
+
+static void set_connectable_complete(int sk, uint16_t index, void *buf,
+                                                               size_t len)
+{
+       struct mgmt_mode *rp = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+
+       if (len < sizeof(*rp)) {
+               error("Too small set connectable complete event");
+               return;
+       }
+
+       DBG("hci%d connectable %u", index, rp->val);
+
+       if (index > max_index) {
+               error("Unexpected index %u in connectable complete", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       info->connectable = rp->val ? TRUE : FALSE;
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (adapter)
+               adapter_mode_changed(adapter, rp->val ? SCAN_PAGE : 0);
+}
+
+static void set_pairable_complete(int sk, uint16_t index, void *buf,
+                                                               size_t len)
+{
+       struct mgmt_mode *rp = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+
+       if (len < sizeof(*rp)) {
+               error("Too small set pairable complete event");
+               return;
+       }
+
+       DBG("hci%d pairable %u", index, rp->val);
+
+       if (index > max_index) {
+               error("Unexpected index %u in pairable complete", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       info->pairable = rp->val ? TRUE : FALSE;
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (!adapter)
+               return;
+
+       btd_adapter_pairable_changed(adapter, info->pairable);
+}
+
+static void disconnect_complete(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_rp_disconnect *rp = buf;
+       struct controller_info *info;
+       char addr[18];
+
+       if (len < sizeof(*rp)) {
+               error("Too small disconnect complete event");
+               return;
+       }
+
+       ba2str(&rp->bdaddr, addr);
+
+       DBG("hci%d %s disconnected", index, addr);
+
+       if (index > max_index) {
+               error("Unexpected index %u in disconnect complete", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       btd_event_disconn_complete(&info->bdaddr, &rp->bdaddr);
+
+       btd_event_bonding_complete(&info->bdaddr, &rp->bdaddr,
+                                               HCI_CONNECTION_TERMINATED);
+}
+
+static void pair_device_complete(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_rp_pair_device *rp = buf;
+       struct controller_info *info;
+       char addr[18];
+
+       if (len < sizeof(*rp)) {
+               error("Too small pair_device complete event");
+               return;
+       }
+
+       ba2str(&rp->bdaddr, addr);
+
+       DBG("hci%d %s pairing complete status %u", index, addr, rp->status);
+
+       if (index > max_index) {
+               error("Unexpected index %u in pair_device complete", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       btd_event_bonding_complete(&info->bdaddr, &rp->bdaddr, rp->status);
+}
+
+static void get_connections_complete(int sk, uint16_t index, void *buf,
+                                                               size_t len)
+{
+       struct mgmt_rp_get_connections *rp = buf;
+       struct controller_info *info;
+       int i;
+
+       if (len < sizeof(*rp)) {
+               error("Too small get_connections complete event");
+               return;
+       }
+
+       if (len < (sizeof(*rp) + (rp->conn_count * sizeof(bdaddr_t)))) {
+               error("Too small get_connections complete event");
+               return;
+       }
+
+       if (index > max_index) {
+               error("Unexpected index %u in get_connections complete",
+                                                               index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       for (i = 0; i < rp->conn_count; i++) {
+               bdaddr_t *bdaddr = g_memdup(&rp->conn[i], sizeof(bdaddr_t));
+               info->connections = g_slist_append(info->connections, bdaddr);
+       }
+
+       read_info(sk, index);
+}
+
+static void set_local_name_complete(int sk, uint16_t index, void *buf,
+                                                               size_t len)
+{
+       struct mgmt_cp_set_local_name *rp = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+
+       if (len < sizeof(*rp)) {
+               error("Too small set_local_name complete event");
+               return;
+       }
+
+       DBG("hci%d name %s", index, (char *) rp->name);
+
+       if (index > max_index) {
+               error("Unexpected index %u in set_local_name complete", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (adapter == NULL) {
+               DBG("Adapter not found");
+               return;
+       }
+
+       adapter_update_local_name(adapter, (char *) rp->name);
+}
+
+static void mgmt_cmd_complete(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_ev_cmd_complete *ev = buf;
+       uint16_t opcode;
+
+       DBG("");
+
+       if (len < sizeof(*ev)) {
+               error("Too small management command complete event packet");
+               return;
+       }
+
+       opcode = btohs(bt_get_unaligned(&ev->opcode));
+
+       len -= sizeof(*ev);
+
+       switch (opcode) {
+       case MGMT_OP_READ_VERSION:
+               read_version_complete(sk, ev->data, len);
+               break;
+       case MGMT_OP_READ_INDEX_LIST:
+               read_index_list_complete(sk, ev->data, len);
+               break;
+       case MGMT_OP_READ_INFO:
+               read_info_complete(sk, index, ev->data, len);
+               break;
+       case MGMT_OP_SET_POWERED:
+               set_powered_complete(sk, index, ev->data, len);
+               break;
+       case MGMT_OP_SET_DISCOVERABLE:
+               set_discoverable_complete(sk, index, ev->data, len);
+               break;
+       case MGMT_OP_SET_CONNECTABLE:
+               set_connectable_complete(sk, index, ev->data, len);
+               break;
+       case MGMT_OP_SET_PAIRABLE:
+               set_pairable_complete(sk, index, ev->data, len);
+               break;
+       case MGMT_OP_ADD_UUID:
+               DBG("add_uuid complete");
+               break;
+       case MGMT_OP_REMOVE_UUID:
+               DBG("remove_uuid complete");
+               break;
+       case MGMT_OP_SET_DEV_CLASS:
+               DBG("set_dev_class complete");
+               break;
+       case MGMT_OP_SET_SERVICE_CACHE:
+               DBG("set_service_cache complete");
+               break;
+       case MGMT_OP_LOAD_KEYS:
+               DBG("load_keys complete");
+               break;
+       case MGMT_OP_REMOVE_KEY:
+               DBG("remove_key complete");
+               break;
+       case MGMT_OP_DISCONNECT:
+               DBG("disconnect complete");
+               disconnect_complete(sk, index, ev->data, len);
+               break;
+       case MGMT_OP_GET_CONNECTIONS:
+               get_connections_complete(sk, index, ev->data, len);
+               break;
+       case MGMT_OP_PIN_CODE_REPLY:
+               DBG("pin_code_reply complete");
+               break;
+       case MGMT_OP_PIN_CODE_NEG_REPLY:
+               DBG("pin_code_neg_reply complete");
+               break;
+       case MGMT_OP_SET_IO_CAPABILITY:
+               DBG("set_io_capability complete");
+               break;
+       case MGMT_OP_PAIR_DEVICE:
+               pair_device_complete(sk, index, ev->data, len);
+               break;
+       case MGMT_OP_USER_CONFIRM_REPLY:
+               DBG("user_confirm_reply complete");
+               break;
+       case MGMT_OP_USER_CONFIRM_NEG_REPLY:
+               DBG("user_confirm_net_reply complete");
+               break;
+       case MGMT_OP_SET_LOCAL_NAME:
+               set_local_name_complete(sk, index, ev->data, len);
+               break;
+       default:
+               error("Unknown command complete for opcode %u", opcode);
+               break;
+       }
+}
+
+static void mgmt_cmd_status(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_ev_cmd_status *ev = buf;
+       uint16_t opcode;
+
+       if (len < sizeof(*ev)) {
+               error("Too small management command status event packet");
+               return;
+       }
+
+       opcode = btohs(bt_get_unaligned(&ev->opcode));
+
+       DBG("status %u opcode %u (index %u)", ev->status, opcode, index);
+}
+
+static void mgmt_controller_error(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_ev_controller_error *ev = buf;
+
+       if (len < sizeof(*ev)) {
+               error("Too small management controller error event packet");
+               return;
+       }
+
+       DBG("index %u error_code %u", index, ev->error_code);
+}
+
+static void mgmt_auth_failed(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct controller_info *info;
+       struct mgmt_ev_auth_failed *ev = buf;
+
+       if (len < sizeof(*ev)) {
+               error("Too small mgmt_auth_failed event packet");
+               return;
+       }
+
+       DBG("hci%u auth failed status %u", index, ev->status);
+
+       if (index > max_index) {
+               error("Unexpected index %u in auth_failed event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       btd_event_bonding_complete(&info->bdaddr, &ev->bdaddr, ev->status);
+}
+
+static void mgmt_local_name_changed(int sk, uint16_t index, void *buf, size_t len)
+{
+       struct mgmt_cp_set_local_name *ev = buf;
+       struct controller_info *info;
+       struct btd_adapter *adapter;
+
+       if (len < sizeof(*ev)) {
+               error("Too small mgmt_local_name_changed event packet");
+               return;
+       }
+
+       DBG("hci%u local name changed: %s", index, (char *) ev->name);
+
+       if (index > max_index) {
+               error("Unexpected index %u in name_changed event", index);
+               return;
+       }
+
+       info = &controllers[index];
+
+       adapter = manager_find_adapter(&info->bdaddr);
+       if (adapter)
+               adapter_update_local_name(adapter, (char *) ev->name);
+}
+
+static gboolean mgmt_event(GIOChannel *io, GIOCondition cond, gpointer user_data)
+{
+       char buf[MGMT_BUF_SIZE];
+       struct mgmt_hdr *hdr = (void *) buf;
+       int sk;
+       ssize_t ret;
+       uint16_t len, opcode, index;
+
+       DBG("cond %d", cond);
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       sk = g_io_channel_unix_get_fd(io);
+
+       if (cond & (G_IO_ERR | G_IO_HUP)) {
+               error("Error on management socket");
+               return FALSE;
+       }
+
+       ret = read(sk, buf, sizeof(buf));
+       if (ret < 0) {
+               error("Unable to read from management socket: %s (%d)",
+                                               strerror(errno), errno);
+               return TRUE;
+       }
+
+       DBG("Received %zd bytes from management socket", ret);
+
+       if (ret < MGMT_HDR_SIZE) {
+               error("Too small Management packet");
+               return TRUE;
+       }
+
+       opcode = btohs(bt_get_unaligned(&hdr->opcode));
+       len = btohs(bt_get_unaligned(&hdr->len));
+       index = btohs(bt_get_unaligned(&hdr->index));
+
+       if (ret != MGMT_HDR_SIZE + len) {
+               error("Packet length mismatch. ret %zd len %u", ret, len);
+               return TRUE;
+       }
+
+       switch (opcode) {
+       case MGMT_EV_CMD_COMPLETE:
+               mgmt_cmd_complete(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_CMD_STATUS:
+               mgmt_cmd_status(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_CONTROLLER_ERROR:
+               mgmt_controller_error(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_INDEX_ADDED:
+               mgmt_index_added(sk, index);
+               break;
+       case MGMT_EV_INDEX_REMOVED:
+               mgmt_index_removed(sk, index);
+               break;
+       case MGMT_EV_POWERED:
+               mgmt_powered(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_DISCOVERABLE:
+               mgmt_discoverable(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_CONNECTABLE:
+               mgmt_connectable(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_PAIRABLE:
+               mgmt_pairable(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_NEW_KEY:
+               mgmt_new_key(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_DEVICE_CONNECTED:
+               mgmt_device_connected(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_DEVICE_DISCONNECTED:
+               mgmt_device_disconnected(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_CONNECT_FAILED:
+               mgmt_connect_failed(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_PIN_CODE_REQUEST:
+               mgmt_pin_code_request(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_USER_CONFIRM_REQUEST:
+               mgmt_user_confirm_request(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_AUTH_FAILED:
+               mgmt_auth_failed(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       case MGMT_EV_LOCAL_NAME_CHANGED:
+               mgmt_local_name_changed(sk, index, buf + MGMT_HDR_SIZE, len);
+               break;
+       default:
+               error("Unknown Management opcode %u (index %u)", opcode, index);
+               break;
+       }
+
+       return TRUE;
+}
+
+static int mgmt_setup(void)
+{
+       struct mgmt_hdr hdr;
+       struct sockaddr_hci addr;
+       GIOChannel *io;
+       GIOCondition condition;
+       int dd, err;
+
+       dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+       if (dd < 0)
+               return -errno;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.hci_family = AF_BLUETOOTH;
+       addr.hci_dev = HCI_DEV_NONE;
+       addr.hci_channel = HCI_CHANNEL_CONTROL;
+
+       if (bind(dd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               err = -errno;
+               goto fail;
+       }
+
+       memset(&hdr, 0, sizeof(hdr));
+       hdr.opcode = htobs(MGMT_OP_READ_VERSION);
+       hdr.index = htobs(MGMT_INDEX_NONE);
+       if (write(dd, &hdr, sizeof(hdr)) < 0) {
+               err = -errno;
+               goto fail;
+       }
+
+       io = g_io_channel_unix_new(dd);
+       condition = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+       mgmt_watch = g_io_add_watch(io, condition, mgmt_event, NULL);
+       g_io_channel_unref(io);
+
+       mgmt_sock = dd;
+
+       info("Bluetooth Management interface initialized");
+
+       return 0;
+
+fail:
+       close(dd);
+       return err;
+}
+
+static void mgmt_cleanup(void)
+{
+       g_free(controllers);
+       controllers = NULL;
+       max_index = -1;
+
+       if (mgmt_sock >= 0) {
+               close(mgmt_sock);
+               mgmt_sock = -1;
+       }
+
+       if (mgmt_watch > 0) {
+               g_source_remove(mgmt_watch);
+               mgmt_watch = 0;
+       }
+}
+
+static int mgmt_set_dev_class(int index, uint8_t major, uint8_t minor)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_set_dev_class)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_set_dev_class *cp = (void *) &buf[sizeof(*hdr)];
+
+       DBG("index %d major %u minor %u", index, major, minor);
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(MGMT_OP_SET_DEV_CLASS);
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       cp->major = major;
+       cp->minor = minor;
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int mgmt_set_limited_discoverable(int index, gboolean limited)
+{
+       DBG("index %d limited %d", index, limited);
+       return -ENOSYS;
+}
+
+static int mgmt_start_inquiry(int index, uint8_t length, gboolean periodic)
+{
+       DBG("index %d length %u periodic %d", index, length, periodic);
+       return -ENOSYS;
+}
+
+static int mgmt_stop_inquiry(int index)
+{
+       DBG("index %d", index);
+       return -ENOSYS;
+}
+
+static int mgmt_start_scanning(int index)
+{
+       DBG("index %d", index);
+       return -ENOSYS;
+}
+
+static int mgmt_stop_scanning(int index)
+{
+       DBG("index %d", index);
+       return -ENOSYS;
+}
+
+static int mgmt_resolve_name(int index, bdaddr_t *bdaddr)
+{
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s", index, addr);
+
+       return -ENOSYS;
+}
+
+static int mgmt_set_name(int index, const char *name)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_set_local_name)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_set_local_name *cp = (void *) &buf[sizeof(*hdr)];
+
+       DBG("index %d, name %s", index, name);
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(MGMT_OP_SET_LOCAL_NAME);
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       strncpy((char *) cp->name, name, sizeof(cp->name) - 1);
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int mgmt_cancel_resolve_name(int index, bdaddr_t *bdaddr)
+{
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s", index, addr);
+
+       return -ENOSYS;
+}
+
+static int mgmt_fast_connectable(int index, gboolean enable)
+{
+       DBG("index %d enable %d", index, enable);
+       return -ENOSYS;
+}
+
+static int mgmt_read_clock(int index, bdaddr_t *bdaddr, int which, int timeout,
+                                       uint32_t *clock, uint16_t *accuracy)
+{
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s which %d timeout %d", index, addr, which,
+                                                               timeout);
+
+       return -ENOSYS;
+}
+
+static int mgmt_read_bdaddr(int index, bdaddr_t *bdaddr)
+{
+       char addr[18];
+       struct controller_info *info = &controllers[index];
+
+       ba2str(&info->bdaddr, addr);
+       DBG("index %d addr %s", index, addr);
+
+       if (!info->valid)
+               return -ENODEV;
+
+       bacpy(bdaddr, &info->bdaddr);
+
+       return 0;
+}
+
+static int mgmt_block_device(int index, bdaddr_t *bdaddr)
+{
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s", index, addr);
+
+       return -ENOSYS;
+}
+
+static int mgmt_unblock_device(int index, bdaddr_t *bdaddr)
+{
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s", index, addr);
+
+       return -ENOSYS;
+}
+
+static int mgmt_get_conn_list(int index, GSList **conns)
+{
+       struct controller_info *info = &controllers[index];
+
+       DBG("index %d", index);
+
+       *conns = info->connections;
+       info->connections = NULL;
+
+       return 0;
+}
+
+static int mgmt_read_local_version(int index, struct hci_version *ver)
+{
+       struct controller_info *info = &controllers[index];
+
+       DBG("index %d", index);
+
+       if (!info->valid)
+               return -ENODEV;
+
+       memset(ver, 0, sizeof(*ver));
+       ver->manufacturer = info->manufacturer;
+       ver->hci_ver = info->hci_ver;
+       ver->hci_rev = info->hci_rev;
+
+       return 0;
+}
+
+static int mgmt_read_local_features(int index, uint8_t *features)
+{
+       struct controller_info *info = &controllers[index];
+
+       DBG("index %d", index);
+
+       if (!info->valid)
+               return -ENODEV;
+
+       memcpy(features, info->features, 8);
+
+       return 0;
+}
+
+static int mgmt_disconnect(int index, bdaddr_t *bdaddr)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_disconnect)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_disconnect *cp = (void *) &buf[sizeof(*hdr)];
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d %s", index, addr);
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(MGMT_OP_DISCONNECT);
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       bacpy(&cp->bdaddr, bdaddr);
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               error("write: %s (%d)", strerror(errno), errno);
+
+       return 0;
+}
+
+static int mgmt_remove_bonding(int index, bdaddr_t *bdaddr)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_remove_key)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_remove_key *cp = (void *) &buf[sizeof(*hdr)];
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s", index, addr);
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(MGMT_OP_REMOVE_KEY);
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       bacpy(&cp->bdaddr, bdaddr);
+       cp->disconnect = 1;
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int mgmt_passkey_reply(int index, bdaddr_t *bdaddr, uint32_t passkey)
+{
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("index %d addr %s passkey %06u", index, addr, passkey);
+
+       return -ENOSYS;
+}
+
+static int mgmt_enable_le(int index)
+{
+       DBG("index %d", index);
+       return -ENOSYS;
+}
+
+static int mgmt_encrypt_link(int index, bdaddr_t *dst, bt_hci_result_t cb,
+                                                       gpointer user_data)
+{
+       char addr[18];
+
+       ba2str(dst, addr);
+       DBG("index %d addr %s", index, addr);
+
+       return -ENOSYS;
+}
+
+static int mgmt_set_did(int index, uint16_t vendor, uint16_t product,
+                                                       uint16_t version)
+{
+       DBG("index %d vendor %u product %u version %u",
+                                       index, vendor, product, version);
+       return -ENOSYS;
+}
+
+static int mgmt_disable_cod_cache(int index)
+{
+       DBG("index %d", index);
+       return mgmt_set_mode(index, MGMT_OP_SET_SERVICE_CACHE, 0);
+}
+
+static int mgmt_restore_powered(int index)
+{
+       DBG("index %d", index);
+       return -ENOSYS;
+}
+
+static int mgmt_load_keys(int index, GSList *keys, gboolean debug_keys)
+{
+       char *buf;
+       struct mgmt_hdr *hdr;
+       struct mgmt_cp_load_keys *cp;
+       struct mgmt_key_info *key;
+       size_t key_count, cp_size;
+       GSList *l;
+       int err;
+
+       key_count = g_slist_length(keys);
+
+       DBG("index %d keys %zu debug_keys %d", index, key_count, debug_keys);
+
+       cp_size = sizeof(*cp) + (key_count * sizeof(*key));
+
+       buf = g_try_malloc0(sizeof(*hdr) + cp_size);
+       if (buf == NULL)
+               return -ENOMEM;
+
+       memset(buf, 0, sizeof(buf));
+
+       hdr = (void *) buf;
+       hdr->opcode = htobs(MGMT_OP_LOAD_KEYS);
+       hdr->len = htobs(cp_size);
+       hdr->index = htobs(index);
+
+       cp = (void *) (buf + sizeof(*hdr));
+       cp->debug_keys = debug_keys;
+       cp->key_count = htobs(key_count);
+
+       for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) {
+               struct link_key_info *info = l->data;
+
+               bacpy(&key->bdaddr, &info->bdaddr);
+               key->type = info->type;
+               memcpy(key->val, info->key, 16);
+               key->pin_len = info->pin_len;
+       }
+
+       if (write(mgmt_sock, buf, sizeof(*hdr) + cp_size) < 0)
+               err = -errno;
+       else
+               err = 0;
+
+       g_free(buf);
+
+       return err;
+}
+
+static int mgmt_set_io_capability(int index, uint8_t io_capability)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_set_io_capability)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_set_io_capability *cp = (void *) &buf[sizeof(*hdr)];
+
+       DBG("hci%d io_capability 0x%02x", index, io_capability);
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(MGMT_OP_SET_IO_CAPABILITY);
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       cp->io_capability = io_capability;
+
+       if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int mgmt_create_bonding(int index, bdaddr_t *bdaddr, uint8_t io_cap)
+{
+       char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_pair_device)];
+       struct mgmt_hdr *hdr = (void *) buf;
+       struct mgmt_cp_pair_device *cp = (void *) &buf[sizeof(*hdr)];
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d bdaddr %s io_cap 0x%02x", index, addr, io_cap);
+
+       memset(buf, 0, sizeof(buf));
+       hdr->opcode = htobs(MGMT_OP_PAIR_DEVICE);
+       hdr->len = htobs(sizeof(*cp));
+       hdr->index = htobs(index);
+
+       bacpy(&cp->bdaddr, bdaddr);
+       cp->io_cap = io_cap;
+
+       if (write(mgmt_sock, &buf, sizeof(buf)) < 0)
+               return -errno;
+
+       return 0;
+}
+
+static int mgmt_cancel_bonding(int index, bdaddr_t *bdaddr)
+{
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+       DBG("hci%d bdaddr %s", index, addr);
+
+       return -ENOSYS;
+}
+
+static struct btd_adapter_ops mgmt_ops = {
+       .setup = mgmt_setup,
+       .cleanup = mgmt_cleanup,
+       .set_powered = mgmt_set_powered,
+       .set_discoverable = mgmt_set_discoverable,
+       .set_pairable = mgmt_set_pairable,
+       .set_limited_discoverable = mgmt_set_limited_discoverable,
+       .start_inquiry = mgmt_start_inquiry,
+       .stop_inquiry = mgmt_stop_inquiry,
+       .start_scanning = mgmt_start_scanning,
+       .stop_scanning = mgmt_stop_scanning,
+       .resolve_name = mgmt_resolve_name,
+       .cancel_resolve_name = mgmt_cancel_resolve_name,
+       .set_name = mgmt_set_name,
+       .set_dev_class = mgmt_set_dev_class,
+       .set_fast_connectable = mgmt_fast_connectable,
+       .read_clock = mgmt_read_clock,
+       .read_bdaddr = mgmt_read_bdaddr,
+       .block_device = mgmt_block_device,
+       .unblock_device = mgmt_unblock_device,
+       .get_conn_list = mgmt_get_conn_list,
+       .read_local_version = mgmt_read_local_version,
+       .read_local_features = mgmt_read_local_features,
+       .disconnect = mgmt_disconnect,
+       .remove_bonding = mgmt_remove_bonding,
+       .pincode_reply = mgmt_pincode_reply,
+       .confirm_reply = mgmt_confirm_reply,
+       .passkey_reply = mgmt_passkey_reply,
+       .enable_le = mgmt_enable_le,
+       .encrypt_link = mgmt_encrypt_link,
+       .set_did = mgmt_set_did,
+       .add_uuid = mgmt_add_uuid,
+       .remove_uuid = mgmt_remove_uuid,
+       .disable_cod_cache = mgmt_disable_cod_cache,
+       .restore_powered = mgmt_restore_powered,
+       .load_keys = mgmt_load_keys,
+       .set_io_capability = mgmt_set_io_capability,
+       .create_bonding = mgmt_create_bonding,
+       .cancel_bonding = mgmt_cancel_bonding,
+};
+
+static int mgmt_init(void)
+{
+       return btd_register_adapter_ops(&mgmt_ops, TRUE);
+}
+
+static void mgmt_exit(void)
+{
+       btd_adapter_cleanup_ops(&mgmt_ops);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(mgmtops, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_LOW, mgmt_init, mgmt_exit)
diff --git a/plugins/pnat.c b/plugins/pnat.c
new file mode 100644 (file)
index 0000000..2d73910
--- /dev/null
@@ -0,0 +1,526 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "sdpd.h"
+#include "btio.h"
+#include "adapter.h"
+#include "log.h"
+
+/* FIXME: This location should be build-time configurable */
+#define PNATD "/usr/bin/phonet-at"
+
+#define DUN_CHANNEL 1
+#define DUN_UUID "00001103-0000-1000-8000-00805F9B34FB"
+
+#define TTY_TIMEOUT 100
+#define TTY_TRIES 10
+
+struct dun_client {
+       bdaddr_t bda;
+
+       GIOChannel *io; /* Client socket */
+       guint io_watch; /* Client IO watch id */
+
+       guint tty_timer;
+       int tty_tries;
+       gboolean tty_open;
+       int tty_id;
+       char tty_name[PATH_MAX];
+
+       GPid pnatd_pid;
+};
+
+struct dun_server {
+       bdaddr_t bda;           /* Local adapter address */
+
+       uint32_t record_handle; /* Local SDP record handle */
+       GIOChannel *server;     /* Server socket */
+
+       int rfcomm_ctl;
+
+       struct dun_client client;
+};
+
+static GSList *servers = NULL;
+
+static void disconnect(struct dun_server *server)
+{
+       struct dun_client *client = &server->client;
+
+       if (!client->io)
+               return;
+
+       if (client->io_watch > 0) {
+               g_source_remove(client->io_watch);
+               client->io_watch = 0;
+       }
+
+       g_io_channel_shutdown(client->io, TRUE, NULL);
+       g_io_channel_unref(client->io);
+       client->io = NULL;
+
+       if (client->pnatd_pid > 0) {
+               kill(client->pnatd_pid, SIGTERM);
+               client->pnatd_pid = 0;
+       }
+
+       if (client->tty_timer > 0) {
+               g_source_remove(client->tty_timer);
+               client->tty_timer = 0;
+       }
+
+       if (client->tty_id >= 0) {
+               struct rfcomm_dev_req req;
+
+               memset(&req, 0, sizeof(req));
+               req.dev_id = client->tty_id;
+               req.flags = (1 << RFCOMM_HANGUP_NOW);
+               ioctl(server->rfcomm_ctl, RFCOMMRELEASEDEV, &req);
+
+               client->tty_name[0] = '\0';
+               client->tty_open = FALSE;
+               client->tty_id = -1;
+       }
+}
+
+static gboolean client_event(GIOChannel *chan,
+                                       GIOCondition cond, gpointer data)
+{
+       struct dun_server *server = data;
+       struct dun_client *client = &server->client;
+       char addr[18];
+
+       ba2str(&client->bda, addr);
+
+       DBG("Disconnected DUN from %s (%s)", addr, client->tty_name);
+
+       client->io_watch = 0;
+       disconnect(server);
+
+       return FALSE;
+}
+
+static void pnatd_exit(GPid pid, gint status, gpointer user_data)
+{
+       struct dun_server *server = user_data;
+       struct dun_client *client = &server->client;
+
+        if (WIFEXITED(status))
+                DBG("pnatd (%d) exited with status %d", pid,
+                                                       WEXITSTATUS(status));
+        else
+                DBG("pnatd (%d) was killed by signal %d", pid,
+                                                       WTERMSIG(status));
+       g_spawn_close_pid(pid);
+
+       if (pid != client->pnatd_pid)
+               return;
+
+       /* So disconnect() doesn't send SIGTERM to a non-existing process */
+       client->pnatd_pid = 0;
+
+       disconnect(server);
+}
+
+static gboolean start_pnatd(struct dun_server *server)
+{
+       struct dun_client *client = &server->client;
+       GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH;
+       char *argv[] = { PNATD, client->tty_name, NULL };
+       GError *err = NULL;
+       GPid pid;
+
+       g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, &pid, &err);
+       if (err != NULL) {
+               error("Unable to spawn pnatd: %s", err->message);
+               g_error_free(err);
+               return FALSE;
+       }
+
+       DBG("pnatd started for %s with pid %d", client->tty_name, pid);
+
+       client->pnatd_pid = pid;
+
+       /* We do not store the GSource id since g_remove_source doesn't
+        * make sense for a child watch. If the callback gets removed
+        * waitpid won't be called and the child remains as a zombie)
+        */
+       g_child_watch_add(pid, pnatd_exit, server);
+
+       return TRUE;
+}
+
+static gboolean tty_try_open(gpointer user_data)
+{
+       struct dun_server *server = user_data;
+       struct dun_client *client = &server->client;
+       int tty_fd;
+
+       tty_fd = open(client->tty_name, O_RDONLY | O_NOCTTY);
+       if (tty_fd < 0) {
+               if (errno == EACCES)
+                       goto disconnect;
+
+               client->tty_tries--;
+
+               if (client->tty_tries <= 0)
+                       goto disconnect;
+
+               return TRUE;
+       }
+
+       DBG("%s created for DUN", client->tty_name);
+
+       client->tty_open = TRUE;
+       client->tty_timer = 0;
+
+       g_io_channel_unref(client->io);
+       g_source_remove(client->io_watch);
+
+       client->io = g_io_channel_unix_new(tty_fd);
+       client->io_watch = g_io_add_watch(client->io,
+                                       G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                                       client_event, server);
+
+       if (!start_pnatd(server))
+               goto disconnect;
+
+       return FALSE;
+
+disconnect:
+       client->tty_timer = 0;
+       disconnect(server);
+       return FALSE;
+}
+
+static gboolean create_tty(struct dun_server *server)
+{
+       struct dun_client *client = &server->client;
+       struct rfcomm_dev_req req;
+       int sk = g_io_channel_unix_get_fd(client->io);
+
+       memset(&req, 0, sizeof(req));
+       req.dev_id = -1;
+       req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP);
+
+       bacpy(&req.src, &server->bda);
+       bacpy(&req.dst, &client->bda);
+
+       bt_io_get(client->io, BT_IO_RFCOMM, NULL,
+                       BT_IO_OPT_DEST_CHANNEL, &req.channel,
+                       BT_IO_OPT_INVALID);
+
+       client->tty_id = ioctl(sk, RFCOMMCREATEDEV, &req);
+       if (client->tty_id < 0) {
+               error("Can't create RFCOMM TTY: %s", strerror(errno));
+               return FALSE;
+       }
+
+       snprintf(client->tty_name, PATH_MAX - 1, "/dev/rfcomm%d",
+                                                       client->tty_id);
+
+       client->tty_tries = TTY_TRIES;
+
+       tty_try_open(server);
+       if (!client->tty_open && client->tty_tries > 0)
+               client->tty_timer = g_timeout_add(TTY_TIMEOUT,
+                                                       tty_try_open, server);
+
+       return TRUE;
+}
+
+static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+       struct dun_server *server = user_data;
+
+       if (err) {
+               error("Accepting DUN connection failed: %s", err->message);
+               disconnect(server);
+               return;
+       }
+
+       if (!create_tty(server)) {
+               error("Device creation failed");
+               disconnect(server);
+       }
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+       struct dun_server *server = user_data;
+       struct dun_client *client = &server->client;
+       GError *err = NULL;
+
+       if (derr && dbus_error_is_set(derr)) {
+               error("DUN access denied: %s", derr->message);
+               goto drop;
+       }
+
+       if (!bt_io_accept(client->io, connect_cb, server, NULL, &err)) {
+               error("bt_io_accept: %s", err->message);
+               g_error_free(err);
+               goto drop;
+       }
+
+       return;
+
+drop:
+       disconnect(server);
+}
+
+static gboolean auth_watch(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       struct dun_server *server = data;
+       struct dun_client *client = &server->client;
+
+       error("DUN client disconnected while waiting for authorization");
+
+       btd_cancel_authorization(&server->bda, &client->bda);
+
+       disconnect(server);
+
+       return FALSE;
+}
+
+static void confirm_cb(GIOChannel *io, gpointer user_data)
+{
+       struct dun_server *server = user_data;
+       struct dun_client *client = &server->client;
+       GError *err = NULL;
+
+       if (client->io) {
+               error("Rejecting DUN connection since one already exists");
+               return;
+       }
+
+       bt_io_get(io, BT_IO_RFCOMM, &err,
+                       BT_IO_OPT_DEST_BDADDR, &client->bda,
+                       BT_IO_OPT_INVALID);
+       if (err != NULL) {
+               error("Unable to get DUN source and dest address: %s",
+                                                               err->message);
+               g_error_free(err);
+               return;
+       }
+
+       if (btd_request_authorization(&server->bda, &client->bda, DUN_UUID,
+                                               auth_cb, user_data) < 0) {
+               error("Requesting DUN authorization failed");
+               return;
+       }
+
+       client->io_watch = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                               (GIOFunc) auth_watch, server);
+       client->io = g_io_channel_ref(io);
+}
+
+static sdp_record_t *dun_record(uint8_t ch)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root, *aproto;
+       uuid_t root_uuid, dun, gn, l2cap, rfcomm;
+       sdp_profile_desc_t profile;
+       sdp_list_t *proto[2];
+       sdp_record_t *record;
+       sdp_data_t *channel;
+
+       record = sdp_record_alloc();
+       if (!record)
+               return NULL;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(record, root);
+
+       sdp_uuid16_create(&dun, DIALUP_NET_SVCLASS_ID);
+       svclass_id = sdp_list_append(NULL, &dun);
+       sdp_uuid16_create(&gn,  GENERIC_NETWORKING_SVCLASS_ID);
+       svclass_id = sdp_list_append(svclass_id, &gn);
+       sdp_set_service_classes(record, svclass_id);
+
+       sdp_uuid16_create(&profile.uuid, DIALUP_NET_PROFILE_ID);
+       profile.version = 0x0100;
+       pfseq = sdp_list_append(NULL, &profile);
+       sdp_set_profile_descs(record, pfseq);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap);
+       apseq = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
+       proto[1] = sdp_list_append(NULL, &rfcomm);
+       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, "Dial-Up Networking", 0, 0);
+
+       sdp_data_free(channel);
+       sdp_list_free(root, NULL);
+       sdp_list_free(svclass_id, NULL);
+       sdp_list_free(proto[0], NULL);
+       sdp_list_free(proto[1], NULL);
+       sdp_list_free(pfseq, NULL);
+       sdp_list_free(apseq, NULL);
+       sdp_list_free(aproto, NULL);
+
+       return record;
+}
+
+static gint server_cmp(gconstpointer a, gconstpointer b)
+{
+       const struct dun_server *server = a;
+       const bdaddr_t *src = b;
+
+       return bacmp(src, &server->bda);
+}
+
+static int pnat_probe(struct btd_adapter *adapter)
+{
+       struct dun_server *server;
+       GIOChannel *io;
+       GError *err = NULL;
+       sdp_record_t *record;
+       bdaddr_t src;
+
+       adapter_get_address(adapter, &src);
+
+       server = g_new0(struct dun_server, 1);
+
+       io = bt_io_listen(BT_IO_RFCOMM, NULL, confirm_cb, server, NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, &src,
+                               BT_IO_OPT_CHANNEL, DUN_CHANNEL,
+                               BT_IO_OPT_INVALID);
+       if (err != NULL) {
+               error("Failed to start DUN server: %s", err->message);
+               g_error_free(err);
+               goto fail;
+       }
+
+       record = dun_record(DUN_CHANNEL);
+       if (!record) {
+               error("Unable to allocate new service record");
+               goto fail;
+       }
+
+       if (add_record_to_server(&src, record) < 0) {
+               error("Unable to register DUN service record");
+               goto fail;
+       }
+
+       server->rfcomm_ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM);
+       if (server->rfcomm_ctl < 0) {
+               error("Unable to create RFCOMM control socket: %s (%d)",
+                                               strerror(errno), errno);
+               goto fail;
+       }
+
+       server->server = io;
+       server->record_handle = record->handle;
+       bacpy(&server->bda, &src);
+
+       servers = g_slist_append(servers, server);
+
+       return 0;
+
+fail:
+       if (io != NULL)
+               g_io_channel_unref(io);
+       g_free(server);
+       return -EIO;
+}
+
+static void pnat_remove(struct btd_adapter *adapter)
+{
+       struct dun_server *server;
+       GSList *match;
+       bdaddr_t src;
+
+       adapter_get_address(adapter, &src);
+
+       match = g_slist_find_custom(servers, &src, server_cmp);
+       if (match == NULL)
+               return;
+
+       server = match->data;
+
+       servers = g_slist_delete_link(servers, match);
+
+       disconnect(server);
+
+       remove_record_from_server(server->record_handle);
+       close(server->rfcomm_ctl);
+       g_io_channel_shutdown(server->server, TRUE, NULL);
+       g_io_channel_unref(server->server);
+       g_free(server);
+}
+
+static struct btd_adapter_driver pnat_server = {
+       .name   = "pnat-server",
+       .probe  = pnat_probe,
+       .remove = pnat_remove,
+};
+
+static int pnat_init(void)
+{
+       DBG("Setup Phonet AT (DUN) plugin");
+
+       return btd_register_adapter_driver(&pnat_server);
+}
+
+static void pnat_exit(void)
+{
+       DBG("Cleanup Phonet AT (DUN) plugin");
+
+       btd_unregister_adapter_driver(&pnat_server);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(pnat, VERSION,
+                       BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+                       pnat_init, pnat_exit)
diff --git a/plugins/service.c b/plugins/service.c
new file mode 100644 (file)
index 0000000..d3414f5
--- /dev/null
@@ -0,0 +1,1725 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <gdbus.h>
+
+#include "sdpd.h"
+#include "sdp-xml.h"
+#include "plugin.h"
+#include "adapter.h"
+#include "error.h"
+#include "log.h"
+
+#define SERVICE_INTERFACE "org.bluez.Service"
+
+static DBusConnection *connection;
+
+struct record_data {
+       uint32_t handle;
+       char *sender;
+       guint listener_id;
+       struct service_adapter *serv_adapter;
+};
+
+struct context_data {
+       sdp_record_t *record;
+       sdp_data_t attr_data;
+       struct sdp_xml_data *stack_head;
+       uint16_t attr_id;
+};
+
+struct pending_auth {
+       DBusConnection *conn;
+       DBusMessage *msg;
+       char *sender;
+       bdaddr_t dst;
+       char uuid[MAX_LEN_UUID_STR];
+};
+
+struct service_adapter {
+       struct btd_adapter *adapter;
+       GSList *pending_list;
+       GSList *records;
+};
+
+static struct service_adapter *serv_adapter_any = NULL;
+
+static int compute_seq_size(sdp_data_t *data)
+{
+       int unit_size = data->unitSize;
+       sdp_data_t *seq = data->val.dataseq;
+
+       for (; seq; seq = seq->next)
+               unit_size += seq->unitSize;
+
+       return unit_size;
+}
+
+static void element_start(GMarkupParseContext *context,
+               const gchar *element_name, const gchar **attribute_names,
+               const gchar **attribute_values, gpointer user_data, GError **err)
+{
+       struct context_data *ctx_data = user_data;
+
+       if (!strcmp(element_name, "record"))
+               return;
+
+       if (!strcmp(element_name, "attribute")) {
+               int i;
+               for (i = 0; attribute_names[i]; i++) {
+                       if (!strcmp(attribute_names[i], "id")) {
+                               ctx_data->attr_id = strtol(attribute_values[i], 0, 0);
+                               break;
+                       }
+               }
+               DBG("New attribute 0x%04x", ctx_data->attr_id);
+               return;
+       }
+
+       if (ctx_data->stack_head) {
+               struct sdp_xml_data *newelem = sdp_xml_data_alloc();
+               newelem->next = ctx_data->stack_head;
+               ctx_data->stack_head = newelem;
+       } else {
+               ctx_data->stack_head = sdp_xml_data_alloc();
+               ctx_data->stack_head->next = NULL;
+       }
+
+       if (!strcmp(element_name, "sequence"))
+               ctx_data->stack_head->data = sdp_data_alloc(SDP_SEQ8, NULL);
+       else if (!strcmp(element_name, "alternate"))
+               ctx_data->stack_head->data = sdp_data_alloc(SDP_ALT8, NULL);
+       else {
+               int i;
+               /* Parse value, name, encoding */
+               for (i = 0; attribute_names[i]; i++) {
+                       if (!strcmp(attribute_names[i], "value")) {
+                               int curlen = strlen(ctx_data->stack_head->text);
+                               int attrlen = strlen(attribute_values[i]);
+
+                               /* Ensure we're big enough */
+                               while ((curlen + 1 + attrlen) > ctx_data->stack_head->size) {
+                                       sdp_xml_data_expand(ctx_data->stack_head);
+                               }
+
+                               memcpy(ctx_data->stack_head->text + curlen,
+                                               attribute_values[i], attrlen);
+                               ctx_data->stack_head->text[curlen + attrlen] = '\0';
+                       }
+
+                       if (!strcmp(attribute_names[i], "encoding")) {
+                               if (!strcmp(attribute_values[i], "hex"))
+                                       ctx_data->stack_head->type = 1;
+                       }
+
+                       if (!strcmp(attribute_names[i], "name")) {
+                               ctx_data->stack_head->name = strdup(attribute_values[i]);
+                       }
+               }
+
+               ctx_data->stack_head->data = sdp_xml_parse_datatype(element_name,
+                               ctx_data->stack_head, ctx_data->record);
+
+               if (ctx_data->stack_head->data == NULL)
+                       error("Can't parse element %s", element_name);
+       }
+}
+
+static void element_end(GMarkupParseContext *context,
+               const gchar *element_name, gpointer user_data, GError **err)
+{
+       struct context_data *ctx_data = user_data;
+       struct sdp_xml_data *elem;
+
+       if (!strcmp(element_name, "record"))
+               return;
+
+       if (!strcmp(element_name, "attribute")) {
+               if (ctx_data->stack_head && ctx_data->stack_head->data) {
+                       int ret = sdp_attr_add(ctx_data->record, ctx_data->attr_id,
+                                                       ctx_data->stack_head->data);
+                       if (ret == -1)
+                               DBG("Could not add attribute 0x%04x",
+                                                       ctx_data->attr_id);
+
+                       ctx_data->stack_head->data = NULL;
+                       sdp_xml_data_free(ctx_data->stack_head);
+                       ctx_data->stack_head = NULL;
+               } else {
+                       DBG("No data for attribute 0x%04x", ctx_data->attr_id);
+               }
+               return;
+       }
+
+       if (!strcmp(element_name, "sequence")) {
+               ctx_data->stack_head->data->unitSize = compute_seq_size(ctx_data->stack_head->data);
+
+               if (ctx_data->stack_head->data->unitSize > USHRT_MAX) {
+                       ctx_data->stack_head->data->unitSize += sizeof(uint32_t);
+                       ctx_data->stack_head->data->dtd = SDP_SEQ32;
+               } else if (ctx_data->stack_head->data->unitSize > UCHAR_MAX) {
+                       ctx_data->stack_head->data->unitSize += sizeof(uint16_t);
+                       ctx_data->stack_head->data->dtd = SDP_SEQ16;
+               } else {
+                       ctx_data->stack_head->data->unitSize += sizeof(uint8_t);
+               }
+       } else if (!strcmp(element_name, "alternate")) {
+               ctx_data->stack_head->data->unitSize = compute_seq_size(ctx_data->stack_head->data);
+
+               if (ctx_data->stack_head->data->unitSize > USHRT_MAX) {
+                       ctx_data->stack_head->data->unitSize += sizeof(uint32_t);
+                       ctx_data->stack_head->data->dtd = SDP_ALT32;
+               } else if (ctx_data->stack_head->data->unitSize > UCHAR_MAX) {
+                       ctx_data->stack_head->data->unitSize += sizeof(uint16_t);
+                       ctx_data->stack_head->data->dtd = SDP_ALT16;
+               } else {
+                       ctx_data->stack_head->data->unitSize += sizeof(uint8_t);
+               }
+       }
+
+       if (ctx_data->stack_head->next && ctx_data->stack_head->data &&
+                                       ctx_data->stack_head->next->data) {
+               switch (ctx_data->stack_head->next->data->dtd) {
+               case SDP_SEQ8:
+               case SDP_SEQ16:
+               case SDP_SEQ32:
+               case SDP_ALT8:
+               case SDP_ALT16:
+               case SDP_ALT32:
+                       ctx_data->stack_head->next->data->val.dataseq =
+                               sdp_seq_append(ctx_data->stack_head->next->data->val.dataseq,
+                                                               ctx_data->stack_head->data);
+                       ctx_data->stack_head->data = NULL;
+                       break;
+               }
+
+               elem = ctx_data->stack_head;
+               ctx_data->stack_head = ctx_data->stack_head->next;
+
+               sdp_xml_data_free(elem);
+       }
+}
+
+static GMarkupParser parser = {
+       element_start, element_end, NULL, NULL, NULL
+};
+
+static sdp_record_t *sdp_xml_parse_record(const char *data, int size)
+{
+       GMarkupParseContext *ctx;
+       struct context_data *ctx_data;
+       sdp_record_t *record;
+
+       ctx_data = malloc(sizeof(*ctx_data));
+       if (!ctx_data)
+               return NULL;
+
+       record = sdp_record_alloc();
+       if (!record) {
+               free(ctx_data);
+               return NULL;
+       }
+
+       memset(ctx_data, 0, sizeof(*ctx_data));
+       ctx_data->record = record;
+
+       ctx = g_markup_parse_context_new(&parser, 0, ctx_data, NULL);
+
+       if (g_markup_parse_context_parse(ctx, data, size, NULL) == FALSE) {
+               error("XML parsing error");
+               g_markup_parse_context_free(ctx);
+               sdp_record_free(record);
+               free(ctx_data);
+               return NULL;
+       }
+
+       g_markup_parse_context_free(ctx);
+
+       free(ctx_data);
+
+       return record;
+}
+
+static struct record_data *find_record(struct service_adapter *serv_adapter,
+                                       uint32_t handle, const char *sender)
+{
+       GSList *list;
+
+       for (list = serv_adapter->records; list; list = list->next) {
+               struct record_data *data = list->data;
+               if (handle == data->handle && !strcmp(sender, data->sender))
+                       return data;
+       }
+
+       return NULL;
+}
+
+static struct pending_auth *next_pending(struct service_adapter *serv_adapter)
+{
+       GSList *l = serv_adapter->pending_list;
+
+       if (l) {
+               struct pending_auth *auth = l->data;
+               return auth;
+       }
+
+       return NULL;
+}
+
+static struct pending_auth *find_pending_by_sender(
+                       struct service_adapter *serv_adapter,
+                       const char *sender)
+{
+       GSList *l = serv_adapter->pending_list;
+
+       for (; l; l = l->next) {
+               struct pending_auth *auth = l->data;
+               if (g_str_equal(auth->sender, sender))
+                       return auth;
+       }
+
+       return NULL;
+}
+
+static void exit_callback(DBusConnection *conn, void *user_data)
+{
+       struct record_data *user_record = user_data;
+       struct service_adapter *serv_adapter = user_record->serv_adapter;
+       struct pending_auth *auth;
+
+       DBG("remove record");
+
+       serv_adapter->records = g_slist_remove(serv_adapter->records,
+                                               user_record);
+
+       auth = find_pending_by_sender(serv_adapter, user_record->sender);
+       if (auth) {
+               serv_adapter->pending_list = g_slist_remove(serv_adapter->pending_list,
+                                                       auth);
+               g_free(auth);
+       }
+
+       remove_record_from_server(user_record->handle);
+
+       g_free(user_record->sender);
+       g_free(user_record);
+}
+
+static int add_xml_record(DBusConnection *conn, const char *sender,
+                       struct service_adapter *serv_adapter,
+                       const char *record, dbus_uint32_t *handle)
+{
+       struct record_data *user_record;
+       sdp_record_t *sdp_record;
+       bdaddr_t src;
+
+       sdp_record = sdp_xml_parse_record(record, strlen(record));
+       if (!sdp_record) {
+               error("Parsing of XML service record failed");
+               return -EIO;
+       }
+
+       if (serv_adapter->adapter)
+               adapter_get_address(serv_adapter->adapter, &src);
+       else
+               bacpy(&src, BDADDR_ANY);
+
+       if (add_record_to_server(&src, sdp_record) < 0) {
+               error("Failed to register service record");
+               sdp_record_free(sdp_record);
+               return -EIO;
+       }
+
+       user_record = g_new0(struct record_data, 1);
+       user_record->handle = sdp_record->handle;
+       user_record->sender = g_strdup(sender);
+       user_record->serv_adapter = serv_adapter;
+       user_record->listener_id = g_dbus_add_disconnect_watch(conn, sender,
+                                       exit_callback, user_record, NULL);
+
+       serv_adapter->records = g_slist_append(serv_adapter->records,
+                                                               user_record);
+
+       DBG("listener_id %d", user_record->listener_id);
+
+       *handle = user_record->handle;
+
+       return 0;
+}
+
+static DBusMessage *update_record(DBusConnection *conn, DBusMessage *msg,
+               struct service_adapter *serv_adapter,
+               dbus_uint32_t handle, sdp_record_t *sdp_record)
+{
+       bdaddr_t src;
+       int err;
+
+       if (remove_record_from_server(handle) < 0) {
+               sdp_record_free(sdp_record);
+               return btd_error_not_available(msg);
+       }
+
+       if (serv_adapter->adapter)
+               adapter_get_address(serv_adapter->adapter, &src);
+       else
+               bacpy(&src, BDADDR_ANY);
+
+       sdp_record->handle = handle;
+       err = add_record_to_server(&src, sdp_record);
+       if (err < 0) {
+               sdp_record_free(sdp_record);
+               error("Failed to update the service record");
+               return btd_error_failed(msg, strerror(-err));
+       }
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *update_xml_record(DBusConnection *conn,
+                               DBusMessage *msg,
+                               struct service_adapter *serv_adapter)
+{
+       struct record_data *user_record;
+       sdp_record_t *sdp_record;
+       const char *record;
+       dbus_uint32_t handle;
+       int len;
+
+       if (dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_UINT32, &handle,
+                               DBUS_TYPE_STRING, &record,
+                               DBUS_TYPE_INVALID) == FALSE)
+               return NULL;
+
+       len = (record ? strlen(record) : 0);
+       if (len == 0)
+               return btd_error_invalid_args(msg);
+
+       user_record = find_record(serv_adapter, handle,
+                               dbus_message_get_sender(msg));
+       if (!user_record)
+               return btd_error_not_available(msg);
+
+       sdp_record = sdp_xml_parse_record(record, len);
+       if (!sdp_record) {
+               error("Parsing of XML service record failed");
+               sdp_record_free(sdp_record);
+               return btd_error_failed(msg,
+                                       "Parsing of XML service record failed");
+       }
+
+       return update_record(conn, msg, serv_adapter, handle, sdp_record);
+}
+
+static int remove_record(DBusConnection *conn, const char *sender,
+                       struct service_adapter *serv_adapter,
+                       dbus_uint32_t handle)
+{
+       struct record_data *user_record;
+
+       DBG("remove record 0x%x", handle);
+
+       user_record = find_record(serv_adapter, handle, sender);
+       if (!user_record)
+               return -1;
+
+       DBG("listner_id %d", user_record->listener_id);
+
+       g_dbus_remove_watch(conn, user_record->listener_id);
+
+       exit_callback(conn, user_record);
+
+       return 0;
+}
+
+static DBusMessage *add_service_record(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct service_adapter *serv_adapter = data;
+       DBusMessage *reply;
+       const char *sender, *record;
+       dbus_uint32_t handle;
+       int err;
+
+       if (dbus_message_get_args(msg, NULL,
+                       DBUS_TYPE_STRING, &record, DBUS_TYPE_INVALID) == FALSE)
+               return NULL;
+
+       sender = dbus_message_get_sender(msg);
+       err = add_xml_record(conn, sender, serv_adapter, record, &handle);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       dbus_message_append_args(reply, DBUS_TYPE_UINT32, &handle,
+                                                       DBUS_TYPE_INVALID);
+
+       return reply;
+}
+
+static DBusMessage *update_service_record(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct service_adapter *serv_adapter = data;
+
+       return update_xml_record(conn, msg, serv_adapter);
+}
+
+static DBusMessage *remove_service_record(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct service_adapter *serv_adapter = data;
+       dbus_uint32_t handle;
+       const char *sender;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &handle,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return NULL;
+
+       sender = dbus_message_get_sender(msg);
+
+       if (remove_record(conn, sender, serv_adapter, handle) < 0)
+               return btd_error_not_available(msg);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+       struct service_adapter *serv_adapter = user_data;
+       DBusMessage *reply;
+       struct pending_auth *auth;
+       bdaddr_t src;
+
+       auth = next_pending(serv_adapter);
+       if (auth == NULL) {
+               info("Authorization cancelled: Client exited");
+               return;
+       }
+
+       if (derr) {
+               error("Access denied: %s", derr->message);
+
+               reply = btd_error_not_authorized(auth->msg);
+               dbus_message_unref(auth->msg);
+               g_dbus_send_message(auth->conn, reply);
+               goto done;
+       }
+
+       g_dbus_send_reply(auth->conn, auth->msg,
+                       DBUS_TYPE_INVALID);
+
+done:
+       dbus_connection_unref(auth->conn);
+
+       serv_adapter->pending_list = g_slist_remove(serv_adapter->pending_list,
+                                                                       auth);
+       g_free(auth);
+
+       auth = next_pending(serv_adapter);
+       if (auth == NULL)
+               return;
+
+       if (serv_adapter->adapter)
+               adapter_get_address(serv_adapter->adapter, &src);
+       else
+               bacpy(&src, BDADDR_ANY);
+
+       btd_request_authorization(&src, &auth->dst,
+                                       auth->uuid, auth_cb, serv_adapter);
+}
+
+static DBusMessage *request_authorization(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct record_data *user_record;
+       struct service_adapter *serv_adapter = data;
+       sdp_record_t *record;
+       sdp_list_t *services;
+       const char *sender;
+       dbus_uint32_t handle;
+       const char *address;
+       struct pending_auth *auth;
+       char uuid_str[MAX_LEN_UUID_STR];
+       uuid_t *uuid, *uuid128;
+       bdaddr_t src;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
+                                       DBUS_TYPE_UINT32, &handle,
+                                       DBUS_TYPE_INVALID) == FALSE)
+               return NULL;
+
+       sender = dbus_message_get_sender(msg);
+       if (find_pending_by_sender(serv_adapter, sender))
+               return btd_error_does_not_exist(msg);
+#ifdef __TIZEN_PATCH__
+
+
+       info("handle %x\n", handle);
+       if(1 != handle)
+       {
+
+       user_record = find_record(serv_adapter, handle, sender);
+       if (!user_record) {
+               user_record = find_record(serv_adapter_any, handle, sender);
+               if (!user_record)
+                       return btd_error_not_authorized(msg);
+       }
+
+       record = sdp_record_find(user_record->handle);
+       if (record == NULL)
+               return btd_error_not_authorized(msg);
+
+       if (sdp_get_service_classes(record, &services) < 0) {
+               sdp_record_free(record);
+               return btd_error_not_authorized(msg);
+       }
+
+       if (services == NULL)
+               return btd_error_not_authorized(msg);
+
+       uuid = services->data;
+       uuid128 = sdp_uuid_to_uuid128(uuid);
+
+       sdp_list_free(services, bt_free);
+
+       if (sdp_uuid2strn(uuid128, uuid_str, MAX_LEN_UUID_STR) < 0) {
+               bt_free(uuid128);
+               return btd_error_not_authorized(msg);
+       }
+       bt_free(uuid128);
+       }
+#else
+       user_record = find_record(serv_adapter, handle, sender);
+       if (!user_record) {
+               user_record = find_record(serv_adapter_any, handle, sender);
+               if (!user_record)
+                       return not_authorized(msg);
+       }
+
+       record = sdp_record_find(user_record->handle);
+       if (record == NULL)
+               return not_authorized(msg);
+
+       if (sdp_get_service_classes(record, &services) < 0) {
+               sdp_record_free(record);
+               return not_authorized(msg);
+       }
+
+       if (services == NULL)
+               return not_authorized(msg);
+
+       uuid = services->data;
+       uuid128 = sdp_uuid_to_uuid128(uuid);
+
+       sdp_list_free(services, bt_free);
+
+       if (sdp_uuid2strn(uuid128, uuid_str, MAX_LEN_UUID_STR) < 0) {
+               bt_free(uuid128);
+               return not_authorized(msg);
+       }
+       bt_free(uuid128);
+#endif
+       auth = g_new0(struct pending_auth, 1);
+       auth->msg = dbus_message_ref(msg);
+       auth->conn = dbus_connection_ref(connection);
+#ifdef __TIZEN_PATCH__
+       if(1 != handle)
+       {
+               auth->sender = user_record->sender;
+               memcpy(auth->uuid, uuid_str, MAX_LEN_UUID_STR);
+       }
+       else
+       {
+               auth->sender = (char *)sender;
+               char* uuid_l2cap = "00000100-0000-1000-8000-00805f9b34fb";
+               memset(auth->uuid, 0, MAX_LEN_UUID_STR);
+               memcpy(auth->uuid, uuid_l2cap, strlen(uuid_l2cap));
+       }
+#else
+       auth->sender = user_record->sender;
+       memcpy(auth->uuid, uuid_str, MAX_LEN_UUID_STR);
+
+#endif
+       str2ba(address, &auth->dst);
+
+       serv_adapter->pending_list = g_slist_append(serv_adapter->pending_list,
+                                                                       auth);
+
+       auth = next_pending(serv_adapter);
+       if (auth == NULL)
+               return btd_error_does_not_exist(msg);
+
+       if (serv_adapter->adapter)
+               adapter_get_address(serv_adapter->adapter, &src);
+       else
+               bacpy(&src, BDADDR_ANY);
+
+       if (btd_request_authorization(&src, &auth->dst, auth->uuid, auth_cb,
+                                                       serv_adapter) < 0) {
+               serv_adapter->pending_list = g_slist_remove(serv_adapter->pending_list,
+                                                                       auth);
+               g_free(auth);
+               return btd_error_not_authorized(msg);
+       }
+
+       return NULL;
+}
+
+static DBusMessage *cancel_authorization(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+       struct service_adapter *serv_adapter = data;
+       struct pending_auth *auth;
+       const gchar *sender;
+       bdaddr_t src;
+
+       sender = dbus_message_get_sender(msg);
+
+       auth = find_pending_by_sender(serv_adapter, sender);
+       if (auth == NULL)
+               return btd_error_does_not_exist(msg);
+
+       if (serv_adapter->adapter)
+               adapter_get_address(serv_adapter->adapter, &src);
+       else
+               bacpy(&src, BDADDR_ANY);
+
+       btd_cancel_authorization(&src, &auth->dst);
+
+       reply = btd_error_not_authorized(auth->msg);
+       dbus_message_unref(auth->msg);
+       g_dbus_send_message(auth->conn, reply);
+
+       dbus_connection_unref(auth->conn);
+
+       serv_adapter->pending_list = g_slist_remove(serv_adapter->pending_list,
+                                                                       auth);
+       g_free(auth);
+
+       auth = next_pending(serv_adapter);
+       if (auth == NULL)
+               goto done;
+
+       if (serv_adapter->adapter)
+               adapter_get_address(serv_adapter->adapter, &src);
+       else
+               bacpy(&src, BDADDR_ANY);
+
+       btd_request_authorization(&src, &auth->dst,
+                                       auth->uuid, auth_cb, serv_adapter);
+
+done:
+       return dbus_message_new_method_return(msg);
+}
+
+#ifdef __TIZEN_PATCH__
+extern sdp_session_t *g_cached_session;
+
+static DBusMessage *siso_add_service_record_pdu(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       info("siso_add_service_record_pdu() +\n");
+       DBusMessage *reply;
+       const char *sender, *record;
+       uint32_t len;
+       dbus_uint32_t handle;
+       int err;
+       sdp_record_t *sdp_record;
+       int scanned;
+       int ret = -1;
+       DBusMessageIter iter, array;
+       dbus_message_iter_init(msg, &iter);
+       dbus_message_iter_recurse(&iter, &array);
+       dbus_message_iter_get_fixed_array(&array, &record, &len);
+       if (len <= 0) {
+               info("Error!!!! ... Invalid args....\n");
+               return btd_error_invalid_args(msg);
+       }
+       info("\n");
+       sdp_record = (sdp_record_t *)sdp_extract_pdu_safe(record, len, &scanned);
+       if (!sdp_record) {
+               info("Error!!! ---------Parsing of service record failed--------\n");
+               return btd_error_invalid_args(msg);
+       }
+       if (scanned != len) {
+               info("Warning!!!! Size mismatch of service record, scanned = %d, len = %d\n",
+                                                       scanned, len);
+               /*The JSRapp service record seesm to be wrong!!!!.
+                * Hence its returning the value 49 and 35.
+                * Its a missmatch. Once its is corrected uncomment below code.*/
+               /* sdp_record_free(sdp_record);
+                  return -1;
+                */
+       }
+
+       if (sdp_record->handle < 0x10000) {
+               DBG("Invalid record handle 0x%05x", sdp_record->handle);
+               sdp_record_free(sdp_record);
+               return btd_error_invalid_args(msg);
+       }
+
+       if (add_record_to_server(BDADDR_ANY, sdp_record) < 0) {
+               info("Error !!!!!!!!      add_record_to_server() \n");
+               info("Failed to register service record for handle 0x%x\n", sdp_record->handle);
+               sdp_record_free(sdp_record);
+               return btd_error_invalid_args(msg);
+       }
+
+       handle = sdp_record->handle;
+
+       info("Received handler = 0x%x\n", handle);
+
+
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply) {
+               info("Error in reply\n");
+               return btd_error_invalid_args(msg);
+       }
+
+       dbus_message_append_args(reply, DBUS_TYPE_UINT32, &handle,
+                                                       DBUS_TYPE_INVALID);
+       info("-\n");
+
+       return reply;
+
+}
+
+
+static DBusMessage *siso_remove_service_record(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       info("\n\n\n\n++++++++++++++++++ (RX) siso_remove_service_record +++++++++++++++++++++++\n\n\n\n");
+       struct service_adapter *serv_adapter = data;
+       dbus_uint32_t handle;
+       sdp_record_t *rec;
+       DBusMessage *reply;
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &handle,
+                                               DBUS_TYPE_INVALID) == FALSE)
+       {
+               info("\nError!!!! ---------------- Invalid arguments ................................\n");
+               return btd_error_invalid_args(msg);
+       }
+       DBG("RRemoving record with handle 0x%05x", handle);
+
+       rec = sdp_record_find(handle);
+       if (!rec)
+       {
+               info("\n---------------- Record Not found for the handler 0x%x-----------\n", handle);
+               return btd_error_invalid_args(msg);
+       }
+       if (sdp_record_remove(handle) == 0) {
+               update_db_timestamp();
+               update_svclass_list(BDADDR_ANY);
+       }
+
+       sdp_record_free(rec);
+       info("\nFreed SDP record\n");
+
+//     return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+       {
+               info("Error in reply\n");
+               return btd_error_invalid_args(msg);
+       }
+       info("-\n");
+
+       return reply;
+
+}
+
+static volatile sig_atomic_t __io_finished;
+
+static void __dbus_sdp_callback_multiple_handle_complete__cb(uint8_t type, uint16_t status,
+                               uint8_t *rsp, size_t size, void *udata)
+{
+       unsigned int i;
+       int err = -1;
+       int tsrc, csrc, tsrc_count, index_count = 0;
+       uint32_t s_handle;
+       DBusConnection  *conn;
+       //DBusMessage *reply;
+       //DBusMessageIter iter, array_iter;
+       service_dbus_ctxt_t* ctxt = (service_dbus_ctxt_t*)udata;
+
+       info("__dbus_sdp_callback_multiple_handle_complete__cb.. +\n");
+#if 0 //Service search
+      if( SDP_ERROR_RSP == status)
+       {
+               info(" sdp timed out \n");
+               __io_finished = 1;
+               return failed_strerror(ctxt->msg, err);
+       }
+       printf("\n");
+
+       if (type == SDP_ERROR_RSP) {
+               __io_finished = 1; /* We have to come of the loop when type == SDP_ERROR_RSP */
+               return failed_strerror(ctxt->msg, err);
+       }
+
+       info(" after  SDP_ERROR_RSP check \n");
+
+       if(NULL == udata)
+       {
+                    info(" sdp search is over \n");
+
+       }
+      else
+       {
+               info(" sdp search continues \n");
+       reply = dbus_message_new_method_return(ctxt->msg);
+               info( " sdp got a reply \n");
+
+               if(reply)
+               {
+       dbus_message_iter_init_append(reply, &iter);
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+                               DBUS_TYPE_UINT32_AS_STRING, &array_iter);
+               }
+               else
+               {
+                       info(" reply is NULL \n");
+               }
+       }
+
+       info(" after  iteration \n");
+#else
+       info("type!!!!!!!!!    %d\n",type);
+       printf("\n");
+       if(type == 0 )
+       {
+               info("timed outttttttt\n");
+
+          __io_finished =1;
+              return;
+       }
+
+       if (type == SDP_ERROR_RSP) {
+               __io_finished = 1;
+        err = -EIO;
+               info("Dbus Failed err =%d\n", err);
+               return;
+       }
+
+#endif
+       uint8_t *pdata = rsp;
+       tsrc = ntohs(bt_get_unaligned((uint16_t *) pdata));
+       if ( tsrc <= 0)
+       {
+               info("tsrc not found");
+//             s_handle = 0xFE000000; /*Record not found*/
+               goto done;
+       }
+       else
+       {
+
+
+               pdata += sizeof(uint16_t);
+               csrc = ntohs(bt_get_unaligned((uint16_t *) pdata));
+               if ( csrc <= 0)
+               {
+                       info("csrc not found");
+//                     s_handle = 0xFE000000; /*Record not found*/
+                       goto done;
+
+               }
+               else
+               {
+                       info("Total service record found = %d, CSR = %d", tsrc, tsrc);
+                       pdata += sizeof(uint16_t);
+                       tsrc_count = tsrc;
+                       index_count = 0;
+                       do
+                       {
+                               s_handle = ntohl(bt_get_unaligned((uint32_t*)pdata));
+                               pdata += sizeof(uint32_t);
+#if 0 //Service search
+                               dbus_message_iter_append_basic(&array_iter,
+                                                               DBUS_TYPE_UINT32, &s_handle);
+#else
+                               dbus_message_iter_append_basic(ctxt->array_iter,
+                                                               DBUS_TYPE_UINT32, &s_handle);
+#endif
+                               ++index_count;
+                               DBG("got handle 0x%x count %d", s_handle, index_count);
+                               if(index_count >= MAX_REMOTE_SERVICES) break;
+                       }while(--tsrc_count);
+
+               }
+       }
+
+
+
+       info( "********Received handles = 0X%X ***********, total rx size = %d\n", index_count, size);
+
+
+done:
+       __io_finished = 1;
+#if 0 //Service search
+       dbus_message_iter_close_container(&iter, &array_iter);
+       dbus_connection_send(ctxt->conn, reply, NULL);
+       dbus_message_unref(reply);
+
+       if(ctxt != NULL)
+       {
+               //dbus_connection_unref(ctxt->conn);
+               dbus_message_unref(ctxt->msg);
+               free(ctxt);
+               ctxt = NULL;
+
+       }
+#endif
+
+       info( "__socket_sdp_callback_multiple_handle_complete__cb -\n");
+       return;
+}
+
+
+static void get_remote_service_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+       sdp_list_t *seq;
+       int ii = 0, index_count=0;
+       int struct_len;
+       DBusMessage *reply;
+       DBusMessageIter iter, array_iter;
+
+       service_dbus_ctxt_t* service_search_ctxt = (service_dbus_ctxt_t*) user_data;
+
+       info("get_remote_service_cb+");
+
+       if (err < 0) {
+               error("Unable to get service record: %s (%d)", strerror(-err), -err);
+               goto fail;
+       }
+
+       if (!recs || !recs->data) {
+               info("No records found\n");
+               //error("No records found");
+               //goto fail;
+       }
+
+       reply = dbus_message_new_method_return(service_search_ctxt->msg);
+
+       if(reply)
+       {
+               dbus_message_iter_init_append(reply, &iter);
+               dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32_AS_STRING, &array_iter);
+       }
+       else
+       {
+               info(" reply is NULL \n");
+               goto fail;
+       }
+
+       for (seq = recs; seq; seq = seq->next)
+       {
+               sdp_record_t *rec = (sdp_record_t *) seq->data;
+               GString *result;
+
+               if (!rec)
+                       break;
+
+               dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_UINT32, &rec->handle);
+
+               ++index_count;
+       }
+
+
+       info( "********Received handles = 0X%X ***********\n", index_count);
+
+
+done:
+       dbus_message_iter_close_container(&iter, &array_iter);
+       dbus_connection_send(service_search_ctxt->conn, reply, NULL);
+       dbus_message_unref(reply);
+
+fail:
+       if(service_search_ctxt != NULL)
+       {
+               //dbus_connection_unref(service_search_ctxt->conn);
+               dbus_message_unref(service_search_ctxt->msg);
+               free(service_search_ctxt);
+               service_search_ctxt = NULL;
+       }
+
+       info("get_remote_service_cb-");
+       return;
+
+}
+
+
+static DBusMessage *get_remote_service_handles(DBusConnection *conn, DBusMessage *msg, void *data)
+{
+       int err = 0;
+       uuid_t uuid;
+       char * addr, *match;
+       bdaddr_t bdadd;
+       service_dbus_ctxt_t* service_search_ctxt = NULL;
+
+
+       info("get_remote_service_handles");
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &addr, DBUS_TYPE_STRING, &match,
+                                               DBUS_TYPE_INVALID) == FALSE)
+       {
+               info("\n---------------- Invalid arguments ................................\n");
+               return btd_error_invalid_args(msg);
+       }
+       info( "Addr: %s\n", addr);
+       info( "UUID: %s\n", match);
+       str2ba(addr, &bdadd);
+
+       if (strlen(match) > 0)
+       {
+               if (bt_string2uuid(&uuid, match) < 0)
+               {
+                       error("Invalid service class name");
+                       return btd_error_invalid_args(msg);
+               }
+               sdp_uuid128_to_uuid(&uuid);
+       }
+
+       service_search_ctxt = g_try_malloc0(sizeof(service_dbus_ctxt_t));
+       if (!service_search_ctxt) {
+               info("malloc() failed");
+               return btd_error_failed(msg, "No Memory");
+       }
+       service_search_ctxt->conn = conn;
+       service_search_ctxt->msg = dbus_message_ref(msg);
+
+       err = bt_search_service(BDADDR_ANY, &bdadd, &uuid, get_remote_service_cb, service_search_ctxt, NULL);
+       if (err < 0)
+       {
+                       error("Invalid service class name");
+                       return btd_error_failed(msg, "Invalid service class name");
+       }
+       return NULL;
+}
+
+static DBusMessage *siso_get_remote_service_handles(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       info("\n\n\n\n++++++++++++++++++ (RX) siso_get_remote_service_handles +++++++++++++++++++++++\n\n\n\n");
+       struct service_adapter *serv_adapter = data;
+       dbus_uint32_t handle;
+       sdp_record_t *rec;
+       DBusMessage *reply;
+
+       char * addr, *match;
+       bdaddr_t bdadd;
+       sdp_list_t *search, *attrids;
+       uint32_t range = 0x0000ffff;
+       uuid_t uuid;
+       service_dbus_ctxt_t* ctxt = NULL;
+       DBusMessageIter iter, array_iter;
+
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &addr, DBUS_TYPE_STRING, &match,
+                                               DBUS_TYPE_INVALID) == FALSE)
+       {
+               info("\nError!!!! ---------------- Invalid arguments ................................\n");
+               return btd_error_invalid_args(msg);
+       }
+       info( "Before sdp_connect with bdaddr %s\n", addr);
+       str2ba(addr, &bdadd);
+       if (strlen(match) > 0)
+       {
+               if (bt_string2uuid(&uuid, match) < 0)
+               {
+                       error("Invalid service class name");
+                       return btd_error_invalid_args(msg);
+               }
+       }
+
+       if(NULL == g_cached_session)
+       {
+               g_cached_session = sdp_connect(BDADDR_ANY, &bdadd, 0);
+               if (!g_cached_session)
+               {
+                       info("Can't connect to SDP service\n");
+                       goto error_done;
+                       //break;
+               }
+       }
+
+       info( "Before sdp_set_notify\n");
+       __io_finished = 0;
+       ctxt = malloc(sizeof(service_dbus_ctxt_t));
+
+       if(NULL == ctxt)
+       {
+               info("!!!!!Memory allocation failed for ctxt\n");
+
+               goto error_done;
+       }
+
+       ctxt->conn = conn;
+       ctxt->msg = dbus_message_ref(msg);
+
+       reply = dbus_message_new_method_return(ctxt->msg);
+       dbus_message_iter_init_append(reply, &iter);
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+                               DBUS_TYPE_UINT32_AS_STRING, &array_iter);
+
+       ctxt->array_iter = &array_iter;
+
+       sdp_set_notify(g_cached_session, __dbus_sdp_callback_multiple_handle_complete__cb, ctxt);
+
+
+       search = sdp_list_append(NULL, &uuid);
+
+       info( "Before sdp_list_append\n");
+
+       attrids = sdp_list_append(NULL, &range);
+
+       info( "Before sdp_service_search_async\n");
+
+       if(0 != sdp_service_search_async(g_cached_session, search, 0xffff))
+       {
+               error("Error : sdp_service_search_async()");
+               goto error_done;
+       }
+
+       info( "Before sdp_list_free\n");
+
+       sdp_list_free(attrids, NULL);
+
+       info( "Before sdp_list_free\n");
+
+       sdp_list_free(search, NULL);
+
+       while (!__io_finished)
+       {
+               printf(". ");
+               info(" calling sdp_process [ enter] : %d\n", __io_finished);
+                if (sdp_process(g_cached_session) == -1)
+                {
+                       info( "Search Completed : error\n");
+
+                }
+
+                info(" calling sdp_process [leave]: %d\n", __io_finished);
+       }
+       dbus_message_iter_close_container(&iter, &array_iter);
+
+       free(ctxt);
+
+       return reply;
+error_done:
+       info( "Error done .. before ctx free\n");
+
+       if(ctxt != NULL)
+       {
+               free(ctxt);
+               ctxt = NULL;
+       }
+
+       info( "Error done .. After ctx free\n");
+
+       info("-\n");
+
+       return btd_error_invalid_args(msg);
+}
+
+static void __sdp_callback_xml_complete_dbus__cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t size, void *udata)
+{
+       info("__sdp_callback_xml_complete_dbus__cb() +\n");
+       service_dbus_ctxt_t *ctxt = (service_dbus_ctxt_t*) udata;
+       sdp_record_t *rec;
+       int scanned;
+       GString *result;
+       DBusMessage *reply, *msg;
+       DBusMessageIter iter, array_iter;
+
+       reply = dbus_message_new_method_return(ctxt->msg);
+
+       if (err == 0xffff)
+       {
+               error("Invalid session!");
+               goto failed;
+       }
+       if (type == SDP_ERROR_RSP)
+       {
+               error("SDP_ERROR_RSP!");
+               goto failed;
+       }
+
+       /* check response PDU ID */
+       if (type != SDP_SVC_ATTR_RSP)
+       {
+               error("SDP_SVC_ATTR_RSP!");
+               goto failed;
+       }
+
+       rec = (sdp_record_t *)sdp_extract_pdu_safe(rsp, size, &scanned);
+       if (rec == NULL || size != scanned)
+       {
+               error("Invalid service record!");
+               goto failed;
+       }
+
+       result = g_string_new(NULL);
+
+       DBG("size %d\n", size);
+
+       convert_sdp_record_to_xml(rec, result, (void *) g_string_append);
+
+       sdp_record_free(rec);
+
+       info( "XML Converted buffer length %d\n", result->len);
+
+       dbus_message_append_args(reply,
+                       DBUS_TYPE_STRING, result->str,
+                       DBUS_TYPE_INVALID);
+       g_string_free(result, TRUE);
+
+       dbus_connection_send(ctxt->conn, reply, NULL);
+       dbus_message_unref(reply);
+
+       if(NULL != ctxt)
+       {
+               free(ctxt);
+               ctxt = NULL;
+       }
+       __io_finished = 1;
+       info("__sdp_callback_xml_complete_dbus__cb-\n");
+       return;
+failed:
+
+       dbus_connection_send(ctxt->conn, reply, NULL);
+       dbus_message_unref(reply);
+
+       if(ctxt != NULL)
+       {
+               dbus_connection_unref(ctxt->conn);
+               dbus_message_unref(ctxt->msg);
+               free(ctxt);
+               ctxt = NULL;
+
+       }
+}
+
+static DBusMessage * siso_adapter_get_remote_svc_xml(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       info( "siso_adapter_get_remote_svc_xml() +\n");
+       sdp_session_t *session;
+       sdp_list_t *attrids;
+       uint32_t range = 0x0000ffff;
+       const char *dst;
+       uint32_t handle;
+       int err;
+       bdaddr_t  bdadd;
+       DBusMessage *reply;
+       service_dbus_ctxt_t* ctxt = NULL;
+
+       if (!dbus_message_get_args(msg, NULL,
+                       DBUS_TYPE_STRING, &dst,
+                       DBUS_TYPE_UINT32, &handle,
+                       DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       str2ba(dst, &bdadd);
+
+       session = sdp_connect(BDADDR_ANY, &bdadd, 0);
+       if (!session)
+       {
+                   info("Can't connect to SDP service\n");
+                   goto error_done;
+       }
+       ctxt = malloc(sizeof(service_dbus_ctxt_t));
+
+       if(NULL == ctxt)
+       {
+               info("!!!!!Memory allocation failed for ctxt\n");
+               goto error_done;
+       }
+
+       ctxt->conn = conn;
+       ctxt->msg = dbus_message_ref(msg);
+
+       info( "Before sdp_set_notify +++\n");
+       if (sdp_set_notify(session, __sdp_callback_xml_complete_dbus__cb, ctxt) < 0)
+       {
+                sdp_close(session);
+               goto error_done;
+       }
+
+       attrids = sdp_list_append(NULL, &range);
+       if (sdp_service_attr_async(session, handle, SDP_ATTR_REQ_RANGE, attrids) < 0)
+       {
+               sdp_list_free(attrids, NULL);
+                sdp_close(session);
+               goto error_done;
+       }
+       sdp_list_free(attrids, NULL);
+       __io_finished = 0;
+           while (!__io_finished)
+           {
+                       printf(". ");
+                       sdp_process(session);
+           }
+
+           sdp_close(session);
+           return 0;
+
+error_done:
+       if(ctxt != NULL)
+       {
+               free(ctxt);
+               ctxt = NULL;
+       }
+       info("-\n");
+
+       return btd_error_invalid_args(msg);
+
+}
+
+static void sdp_get_pdu_by_handle_dbus_rsp(uint8_t type, uint16_t err,
+                       uint8_t *rsp, size_t size, void *udata)
+{
+       sdp_record_t *rec;
+       int scanned;
+       DBusMessage *reply, *msg;
+       DBusMessageIter iter, array_iter;
+       service_dbus_ctxt_t* ctxt = NULL;
+       info("sdp_get_pdu_by_handle_rsp+\n");
+
+       if (err == 0xffff)
+       {
+               error("Invalid session!");
+               goto failed;
+       }
+
+       if (type == SDP_ERROR_RSP)
+       {
+               error("SDP_ERROR_RSP!");
+               goto failed;
+       }
+
+       /* check response PDU ID */
+       if (type != SDP_SVC_ATTR_RSP)
+       {
+               error("SDP_SVC_ATTR_RSP!");
+               goto failed;
+       }
+
+       ctxt = (service_dbus_ctxt_t*)udata;
+       rec = (sdp_record_t *)sdp_extract_pdu_safe(rsp, size, &scanned);
+       if (rec == NULL || size != scanned) {
+               error("Invalid service record!");
+               goto failed;
+       }
+
+       //sdp_store_record(ctxt->src, ctxt->dst, rec->handle, rsp, size);
+
+       sdp_record_free(rec);
+
+       DBG("size %d", size);
+       dbus_message_iter_append_fixed_array(ctxt->array_iter,
+                       DBUS_TYPE_BYTE, &rsp, size);
+
+       __io_finished = 1;
+       info("sdp_get_pdu_by_handle_rsp -\n");
+
+       return;
+
+failed:
+       info("Failed to get the service record");
+
+       __io_finished = 1;
+       return;
+}
+
+static DBusMessage *adapter_get_remote_svc(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+
+       //sdp_session_t *session;
+       sdp_list_t *attrids;
+       uint32_t range = 0x0000ffff;
+       const char *dst;
+       uint32_t handle;
+       bdaddr_t bdadd;
+       int struct_len;
+       service_dbus_ctxt_t* ctxt = NULL;
+#if 0 //Service search
+#else
+       DBusMessage *reply;
+       DBusMessageIter iter, array_iter;
+#endif
+       info("...__get_sdp_record_by_handle_send_response....\n");
+
+       if (!dbus_message_get_args(msg, NULL,
+                       DBUS_TYPE_STRING, &dst,
+                       DBUS_TYPE_UINT32, &handle,
+                       DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       info("address %s, handle %d", dst, handle);
+
+       str2ba(dst, &bdadd);
+
+       if(NULL == g_cached_session)
+       {
+               g_cached_session = sdp_connect(BDADDR_ANY, &bdadd, 0);
+               if (!g_cached_session)
+               {
+                       info("Can't connect to SDP service\n");
+                       goto error_done;
+                       //break;
+               }
+       }
+       info( "Before sdp_set_notify,,,,,......\n");
+
+       ctxt = malloc(sizeof(service_dbus_ctxt_t));
+
+       if(NULL == ctxt)
+       {
+               goto error_done;
+       }
+       __io_finished = 0;
+       ctxt->conn = conn;
+       ctxt->msg = dbus_message_ref(msg);
+       reply = dbus_message_new_method_return(ctxt->msg);
+       dbus_message_iter_init_append(reply, &iter);
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+                       DBUS_TYPE_BYTE_AS_STRING, &array_iter);
+
+       ctxt->array_iter = &array_iter;
+       if (sdp_set_notify(g_cached_session, sdp_get_pdu_by_handle_dbus_rsp, ctxt) < 0)
+               goto error_done;
+
+
+       attrids = sdp_list_append(NULL, &range);
+
+       if (sdp_service_attr_async(g_cached_session, handle,
+                               SDP_ATTR_REQ_RANGE, attrids) < 0) {
+               sdp_list_free(attrids, NULL);
+               goto error_done;
+       }
+
+       sdp_list_free(attrids, NULL);
+
+       while (!__io_finished)
+       {
+               printf(". ");
+               sdp_process(g_cached_session);
+       }
+       dbus_message_iter_close_container(&iter, &array_iter);
+
+       free(ctxt);
+       return reply;
+
+
+error_done:
+       if(ctxt != NULL)
+       {
+               free(ctxt);
+               ctxt =  NULL;
+       }
+       info("__get_sdp_record_by_handle_send_response failed");
+
+       return btd_error_invalid_args(msg);
+
+}
+
+static DBusMessage *close_sdp_session(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       info("close_sdp_session");
+       if(NULL != g_cached_session)
+       {
+               info("closing sdp cached session");
+               sdp_close(g_cached_session);
+               g_cached_session = NULL;
+       }
+
+       return dbus_message_new_method_return(msg);
+}
+
+
+static GDBusMethodTable service_methods[] = {
+       { "AddRecord",          "s",    "u",    add_service_record      },
+       { "AddServiceRecord",           "ay",   "u",    siso_add_service_record_pdu     },
+       { "RemoveServiceRecord",        "u",    "",     siso_remove_service_record      },
+       {"GetRemoteServiceHandles", "ss",    "ay",      siso_get_remote_service_handles, G_DBUS_METHOD_FLAG_ASYNC},
+//     {"GetRemoteServiceHandles", "ss",    "ay",      get_remote_service_handles, G_DBUS_METHOD_FLAG_ASYNC},
+       { "GetRemoteServiceRecord",             "su",   "ay", adapter_get_remote_svc, G_DBUS_METHOD_FLAG_ASYNC},
+       { "CloseSdpSession",    "",     "",     close_sdp_session},
+       { "GetRemoteServiceRecordAsXML",        "su",   "s",    siso_adapter_get_remote_svc_xml,        G_DBUS_METHOD_FLAG_ASYNC        },
+       { "UpdateRecord",       "us",   "",     update_service_record   },
+       { "RemoveRecord",       "u",    "",     remove_service_record   },
+       { "RequestAuthorization","su",  "",     request_authorization,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "CancelAuthorization", "",    "",     cancel_authorization    },
+       { }
+};
+#else
+static GDBusMethodTable service_methods[] = {
+       { "AddRecord",          "s",    "u",    add_service_record      },
+       { "UpdateRecord",       "us",   "",     update_service_record   },
+       { "RemoveRecord",       "u",    "",     remove_service_record   },
+       { "RequestAuthorization","su",  "",     request_authorization,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "CancelAuthorization", "",    "",     cancel_authorization    },
+       { }
+};
+
+#endif
+static void path_unregister(void *data)
+{
+       struct service_adapter *serv_adapter = data;
+       GSList *l, *next = NULL;
+
+       for (l = serv_adapter->records; l != NULL; l = next) {
+               struct record_data *user_record = l->data;
+
+               next = l->next;
+
+               g_dbus_remove_watch(connection, user_record->listener_id);
+               exit_callback(connection, user_record);
+       }
+
+       g_free(serv_adapter);
+}
+
+static int register_interface(const char *path, struct btd_adapter *adapter)
+{
+       info("\nEntering register_interface()... +\n");
+       struct service_adapter *serv_adapter;
+
+       DBG("path %s", path);
+
+       serv_adapter = g_try_new0(struct service_adapter, 1);
+       if (serv_adapter == NULL)
+               return -ENOMEM;
+
+       serv_adapter->adapter = adapter;
+       serv_adapter->pending_list = NULL;
+
+       if (g_dbus_register_interface(connection, path, SERVICE_INTERFACE,
+                               service_methods, NULL, NULL, serv_adapter,
+                                               path_unregister) == FALSE) {
+               error("D-Bus failed to register %s interface",
+                                                       SERVICE_INTERFACE);
+               g_free(serv_adapter);
+               return -EIO;
+       }
+
+       DBG("Registered interface %s on path %s", SERVICE_INTERFACE, path);
+
+       if (serv_adapter->adapter == NULL)
+               serv_adapter_any = serv_adapter;
+
+       return 0;
+}
+
+static void unregister_interface(const char *path)
+{
+       DBG("path %s", path);
+
+       g_dbus_unregister_interface(connection, path, SERVICE_INTERFACE);
+}
+
+static int service_probe(struct btd_adapter *adapter)
+{
+       register_interface(adapter_get_path(adapter), adapter);
+
+       return 0;
+}
+
+static void service_remove(struct btd_adapter *adapter)
+{
+       unregister_interface(adapter_get_path(adapter));
+}
+
+static struct btd_adapter_driver service_driver = {
+       .name   = "service",
+       .probe  = service_probe,
+       .remove = service_remove,
+};
+
+static const char *any_path;
+
+static int service_init(void)
+{
+       int err;
+
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (connection == NULL)
+               return -EIO;
+
+       any_path = btd_adapter_any_request_path();
+       if (any_path != NULL) {
+               if (register_interface(any_path, NULL) < 0) {
+                       btd_adapter_any_release_path();
+                       any_path = NULL;
+               }
+       }
+
+       err = btd_register_adapter_driver(&service_driver);
+       if (err < 0) {
+               dbus_connection_unref(connection);
+               return err;
+       }
+
+       return 0;
+}
+
+static void service_exit(void)
+{
+       btd_unregister_adapter_driver(&service_driver);
+
+       if (any_path != NULL) {
+               unregister_interface(any_path);
+
+               btd_adapter_any_release_path();
+               any_path = NULL;
+       }
+
+       dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(service, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_HIGH, service_init, service_exit)
diff --git a/plugins/storage.c b/plugins/storage.c
new file mode 100644 (file)
index 0000000..04a02c7
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <bluetooth/bluetooth.h>
+
+#include "plugin.h"
+#include "log.h"
+
+static int storage_init(void)
+{
+       return 0;
+}
+
+static void storage_exit(void)
+{
+}
+
+BLUETOOTH_PLUGIN_DEFINE(storage, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, storage_init, storage_exit)
diff --git a/sap/main.c b/sap/main.c
new file mode 100644 (file)
index 0000000..c9c90bd
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <gdbus.h>
+#include "plugin.h"
+#include "manager.h"
+
+static DBusConnection *connection;
+
+static int sap_init(void)
+{
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+       if (!connection)
+               return -EIO;
+
+       if (sap_manager_init(connection) < 0) {
+               dbus_connection_unref(connection);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void sap_exit(void)
+{
+       sap_manager_exit();
+
+       dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(sap, VERSION,
+               BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, sap_init, sap_exit)
diff --git a/sap/manager.c b/sap/manager.c
new file mode 100644 (file)
index 0000000..a97f434
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <gdbus.h>
+
+#include "log.h"
+#include "adapter.h"
+#include "device.h"
+
+#include "manager.h"
+#include "server.h"
+
+static DBusConnection *connection = NULL;
+
+static int sap_server_probe(struct btd_adapter *adapter)
+{
+       const char *path = adapter_get_path(adapter);
+       bdaddr_t src;
+
+       DBG("path %s", path);
+
+       adapter_get_address(adapter, &src);
+
+       return sap_server_register(path, &src);
+}
+
+static void sap_server_remove(struct btd_adapter *adapter)
+{
+       const char *path = adapter_get_path(adapter);
+
+       DBG("path %s", path);
+
+       sap_server_unregister(path);
+}
+
+static struct btd_adapter_driver sap_server_driver = {
+       .name   = "sap-server",
+       .probe  = sap_server_probe,
+       .remove = sap_server_remove,
+};
+
+int sap_manager_init(DBusConnection *conn)
+{
+       connection = dbus_connection_ref(conn);
+
+       if (sap_server_init(connection) < 0) {
+               error("Can't init SAP server");
+               dbus_connection_unref(conn);
+               return -1;
+       }
+
+       btd_register_adapter_driver(&sap_server_driver);
+
+       return 0;
+}
+
+void sap_manager_exit(void)
+{
+       btd_unregister_adapter_driver(&sap_server_driver);
+
+       dbus_connection_unref(connection);
+       connection = NULL;
+
+       sap_server_exit();
+}
diff --git a/sap/manager.h b/sap/manager.h
new file mode 100644 (file)
index 0000000..e08c882
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT
+ *
+ *  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 sap_manager_init(DBusConnection *conn);
+void sap_manager_exit(void);
diff --git a/sap/sap-dummy.c b/sap/sap-dummy.c
new file mode 100644 (file)
index 0000000..b433ba3
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 ST-Ericsson SA
+ *
+ *  Author: Waldemar Rymarkiewicz <waldemar.rymarkiewicz@tieto.com>
+ *          for ST-Ericsson
+ *
+ *  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 "log.h"
+#include "sap.h"
+
+void sap_connect_req(void *sap_device, uint16_t maxmsgsize)
+{
+       sap_connect_rsp(sap_device, SAP_STATUS_OK, maxmsgsize);
+       sap_status_ind(sap_device, SAP_STATUS_CHANGE_CARD_RESET);
+}
+
+void sap_disconnect_req(void *sap_device, uint8_t linkloss)
+{
+       sap_disconnect_rsp(sap_device);
+}
+
+void sap_transfer_apdu_req(void *sap_device, struct sap_parameter *param)
+{
+       sap_transfer_apdu_rsp(sap_device, SAP_RESULT_OK, NULL, 0);
+}
+
+void sap_transfer_atr_req(void *sap_device)
+{
+       sap_transfer_atr_rsp(sap_device, SAP_RESULT_OK, NULL, 0);
+}
+
+void sap_power_sim_off_req(void *sap_device)
+{
+       sap_power_sim_off_rsp(sap_device, SAP_RESULT_OK);
+}
+
+void sap_power_sim_on_req(void *sap_device)
+{
+       sap_power_sim_on_rsp(sap_device, SAP_RESULT_OK);
+}
+
+void sap_reset_sim_req(void *sap_device)
+{
+       sap_reset_sim_rsp(sap_device, SAP_RESULT_OK);
+       sap_status_ind(sap_device, SAP_STATUS_CHANGE_CARD_RESET);
+}
+
+void sap_transfer_card_reader_status_req(void *sap_device)
+{
+       sap_transfer_card_reader_status_rsp(sap_device, SAP_RESULT_OK,
+                                               ICC_READER_CARD_POWERED_ON);
+}
+
+void sap_set_transport_protocol_req(void *sap_device,
+                                       struct sap_parameter *param)
+{
+       sap_transport_protocol_rsp(sap_device, SAP_RESULT_NOT_SUPPORTED);
+}
+
+int sap_init(void)
+{
+       DBG("SAP driver init.");
+       return 0;
+}
+
+void sap_exit(void)
+{
+       DBG("SAP driver exit.");
+}
diff --git a/sap/sap.h b/sap/sap.h
new file mode 100644 (file)
index 0000000..bd0f06d
--- /dev/null
+++ b/sap/sap.h
@@ -0,0 +1,186 @@
+/*
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT
+ *  Copyright (C) 2010 ST-Ericsson SA
+ *
+ *  Author: Marek Skowron <marek.skowron@tieto.com> for ST-Ericsson.
+ *  Author: Waldemar Rymarkiewicz <waldemar.rymarkiewicz@tieto.com>
+ *          for ST-Ericsson.
+ *
+ *  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 <stdint.h>
+#include <glib.h>
+
+#define SAP_VERSION 0x0101
+
+/* Connection Status - SAP v1.1 section 5.2.2 */
+enum sap_status {
+       SAP_STATUS_OK                           = 0x00,
+       SAP_STATUS_CONNECTION_FAILED            = 0x01,
+       SAP_STATUS_MAX_MSG_SIZE_NOT_SUPPORTED   = 0x02,
+       SAP_STATUS_MAX_MSG_SIZE_TOO_SMALL       = 0x03,
+       SAP_STATUS_OK_ONGOING_CALL              = 0x04
+};
+
+/* Disconnection Type - SAP v1.1 section 5.2.3 */
+enum sap_disconnection_type {
+       SAP_DISCONNECTION_TYPE_GRACEFUL         = 0x00,
+       SAP_DISCONNECTION_TYPE_IMMEDIATE        = 0x01,
+       SAP_DISCONNECTION_TYPE_CLIENT           = 0xFF
+};
+
+/* Result codes - SAP v1.1 section 5.2.4 */
+enum sap_result {
+       SAP_RESULT_OK                   = 0x00,
+       SAP_RESULT_ERROR_NO_REASON      = 0x01,
+       SAP_RESULT_ERROR_NOT_ACCESSIBLE = 0x02,
+       SAP_RESULT_ERROR_POWERED_OFF    = 0x03,
+       SAP_RESULT_ERROR_CARD_REMOVED   = 0x04,
+       SAP_RESULT_ERROR_POWERED_ON     = 0x05,
+       SAP_RESULT_ERROR_NO_DATA        = 0x06,
+       SAP_RESULT_NOT_SUPPORTED        = 0x07
+};
+
+/* Status Change - SAP v1.1 section 5.2.8 */
+enum sap_status_change {
+       SAP_STATUS_CHANGE_UNKNOWN_ERROR         = 0x00,
+       SAP_STATUS_CHANGE_CARD_RESET            = 0x01,
+       SAP_STATUS_CHANGE_CARD_NOT_ACCESSIBLE   = 0x02,
+       SAP_STATUS_CHANGE_CARD_REMOVED          = 0x03,
+       SAP_STATUS_CHANGE_CARD_INSERTED         = 0x04,
+       SAP_STATUS_CHANGE_CARD_RECOVERED        = 0x05
+};
+
+/* Message format - SAP v1.1 section 5.1 */
+struct sap_parameter {
+       uint8_t id;
+       uint8_t reserved;
+       uint16_t len;
+       uint8_t val[0];
+       /*
+        * Padding bytes 0-3 bytes
+        */
+} __attribute__((packed));
+
+struct sap_message {
+       uint8_t id;
+       uint8_t nparam;
+       uint16_t reserved;
+       struct sap_parameter param[0];
+} __attribute__((packed));
+
+enum {
+       ICC_READER_UNSPECIFIED_ERROR, /* No further information available */
+       ICC_READER_NOT_PRESENT,       /* Card Reader removed or not present */
+       ICC_READER_BUSY,              /* Card Reader in use */
+       ICC_READER_CARD_POWERED_ON,   /* Card in reader and is powered on */
+       ICC_READER_DEACTIVATED,       /* Card Reader deactivated */
+       ICC_READER_CARD_POWERED_OFF,  /* Card in reader, but powered off */
+       ICC_READER_NO_CARD,           /* No card in reader */
+       ICC_READER_LAST
+};
+
+#define SAP_BUF_SIZE           512
+#define SAP_MSG_HEADER_SIZE    4
+
+enum sap_protocol {
+       SAP_CONNECT_REQ         = 0x00,
+       SAP_CONNECT_RESP        = 0x01,
+       SAP_DISCONNECT_REQ      = 0x02,
+       SAP_DISCONNECT_RESP     = 0x03,
+       SAP_DISCONNECT_IND      = 0x04,
+       SAP_TRANSFER_APDU_REQ   = 0x05,
+       SAP_TRANSFER_APDU_RESP  = 0x06,
+       SAP_TRANSFER_ATR_REQ    = 0x07,
+       SAP_TRANSFER_ATR_RESP   = 0x08,
+       SAP_POWER_SIM_OFF_REQ   = 0x09,
+       SAP_POWER_SIM_OFF_RESP  = 0x0A,
+       SAP_POWER_SIM_ON_REQ    = 0x0B,
+       SAP_POWER_SIM_ON_RESP   = 0x0C,
+       SAP_RESET_SIM_REQ       = 0x0D,
+       SAP_RESET_SIM_RESP      = 0x0E,
+       SAP_TRANSFER_CARD_READER_STATUS_REQ     = 0x0F,
+       SAP_TRANSFER_CARD_READER_STATUS_RESP    = 0x10,
+       SAP_STATUS_IND  = 0x11,
+       SAP_ERROR_RESP  = 0x12,
+       SAP_SET_TRANSPORT_PROTOCOL_REQ  = 0x13,
+       SAP_SET_TRANSPORT_PROTOCOL_RESP = 0x14
+};
+
+/* Parameters Ids - SAP 1.1 section 5.2 */
+enum sap_param_id {
+       SAP_PARAM_ID_MAX_MSG_SIZE       = 0x00,
+       SAP_PARAM_ID_CONN_STATUS        = 0x01,
+       SAP_PARAM_ID_RESULT_CODE        = 0x02,
+       SAP_PARAM_ID_DISCONNECT_IND     = 0x03,
+       SAP_PARAM_ID_COMMAND_APDU       = 0x04,
+       SAP_PARAM_ID_COMMAND_APDU7816   = 0x10,
+       SAP_PARAM_ID_RESPONSE_APDU      = 0x05,
+       SAP_PARAM_ID_ATR                = 0x06,
+       SAP_PARAM_ID_CARD_READER_STATUS = 0x07,
+       SAP_PARAM_ID_STATUS_CHANGE      = 0x08,
+       SAP_PARAM_ID_TRANSPORT_PROTOCOL = 0x09
+};
+
+#define SAP_PARAM_ID_MAX_MSG_SIZE_LEN          0x02
+#define SAP_PARAM_ID_CONN_STATUS_LEN           0x01
+#define SAP_PARAM_ID_RESULT_CODE_LEN           0x01
+#define SAP_PARAM_ID_DISCONNECT_IND_LEN                0x01
+#define SAP_PARAM_ID_CARD_READER_STATUS_LEN    0x01
+#define SAP_PARAM_ID_STATUS_CHANGE_LEN         0x01
+#define SAP_PARAM_ID_TRANSPORT_PROTOCOL_LEN    0x01
+
+/* Transport Protocol - SAP v1.1 section 5.2.9 */
+enum sap_transport_protocol {
+       SAP_TRANSPORT_PROTOCOL_T0 = 0x00,
+       SAP_TRANSPORT_PROTOCOL_T1 = 0x01
+};
+
+/*SAP driver init and exit routines. Implemented by sap-*.c */
+int sap_init(void);
+void sap_exit(void);
+
+/* SAP requests implemented by sap-*.c */
+void sap_connect_req(void *sap_device, uint16_t maxmsgsize);
+void sap_disconnect_req(void *sap_device, uint8_t linkloss);
+void sap_transfer_apdu_req(void *sap_device, struct sap_parameter *param);
+void sap_transfer_atr_req(void *sap_device);
+void sap_power_sim_off_req(void *sap_device);
+void sap_power_sim_on_req(void *sap_device);
+void sap_reset_sim_req(void *sap_device);
+void sap_transfer_card_reader_status_req(void *sap_device);
+void sap_set_transport_protocol_req(void *sap_device,
+                                       struct sap_parameter *param);
+
+/*SAP responses to SAP requests. Implemented by server.c */
+int sap_connect_rsp(void *sap_device, uint8_t status, uint16_t maxmsgsize);
+int sap_disconnect_rsp(void *sap_device);
+int sap_transfer_apdu_rsp(void *sap_device, uint8_t result,
+                               uint8_t *sap_apdu_resp, uint16_t length);
+int sap_transfer_atr_rsp(void *sap_device, uint8_t result,
+                               uint8_t *sap_atr, uint16_t length);
+int sap_power_sim_off_rsp(void *sap_device, uint8_t result);
+int sap_power_sim_on_rsp(void *sap_device, uint8_t result);
+int sap_reset_sim_rsp(void *sap_device, uint8_t result);
+int sap_transfer_card_reader_status_rsp(void *sap_device, uint8_t result,
+                                               uint8_t status);
+int sap_error_rsp(void *sap_device);
+int sap_transport_protocol_rsp(void *sap_device, uint8_t result);
+
+/* Event indication. Implemented by server.c*/
+int sap_status_ind(void *sap_device, uint8_t status_change);
diff --git a/sap/server.c b/sap/server.c
new file mode 100644 (file)
index 0000000..1c62a3e
--- /dev/null
@@ -0,0 +1,643 @@
+/*
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT
+ *  Copyright (C) 2010 ST-Ericsson SA
+ *  Copyright (C) 2011 Tieto Poland
+ *
+ *  Author: Marek Skowron <marek.skowron@tieto.com> for ST-Ericsson.
+ *  Author: Waldemar Rymarkiewicz <waldemar.rymarkiewicz@tieto.com>
+ *          for ST-Ericsson.
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <glib.h>
+#include <netinet/in.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "adapter.h"
+#include "btio.h"
+#include "sdpd.h"
+#include "log.h"
+#include "error.h"
+#include "dbus-common.h"
+#include "sap.h"
+#include "server.h"
+
+#define SAP_SERVER_INTERFACE   "org.bluez.SimAccess"
+#define SAP_UUID               "0000112D-0000-1000-8000-00805F9B34FB"
+#define SAP_SERVER_CHANNEL     8
+#define SAP_BUF_SIZE           512
+
+enum {
+       SAP_STATE_DISCONNECTED,
+       SAP_STATE_CONNECTED,
+};
+
+struct sap_connection {
+       GIOChannel *io;
+       uint32_t state;
+};
+
+struct sap_server {
+       bdaddr_t src;
+       char *path;
+       uint32_t record_id;
+       GIOChannel *listen_io;
+       struct sap_connection *conn;
+};
+
+static DBusConnection *connection;
+static struct sap_server *server;
+
+static sdp_record_t *create_sap_record(uint8_t channel)
+{
+       sdp_list_t *apseq, *aproto, *profiles, *proto[2], *root, *svclass_id;
+       uuid_t sap_uuid, gt_uuid, root_uuid, l2cap, rfcomm;
+       sdp_profile_desc_t profile;
+       sdp_record_t *record;
+       sdp_data_t *ch;
+
+       record = sdp_record_alloc();
+       if (!record)
+               return NULL;
+
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(record, root);
+       sdp_list_free(root, NULL);
+
+       sdp_uuid16_create(&sap_uuid, SAP_SVCLASS_ID);
+       svclass_id = sdp_list_append(NULL, &sap_uuid);
+       sdp_uuid16_create(&gt_uuid, GENERIC_TELEPHONY_SVCLASS_ID);
+       svclass_id = sdp_list_append(svclass_id, &gt_uuid);
+
+       sdp_set_service_classes(record, svclass_id);
+       sdp_list_free(svclass_id, NULL);
+
+       sdp_uuid16_create(&profile.uuid, SAP_PROFILE_ID);
+       profile.version = SAP_VERSION;
+       profiles = sdp_list_append(NULL, &profile);
+       sdp_set_profile_descs(record, profiles);
+       sdp_list_free(profiles, NULL);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap);
+       apseq = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
+       proto[1] = sdp_list_append(NULL, &rfcomm);
+       ch = sdp_data_alloc(SDP_UINT8, &channel);
+       proto[1] = sdp_list_append(proto[1], ch);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(NULL, apseq);
+       sdp_set_access_protos(record, aproto);
+
+       sdp_set_info_attr(record, "SIM Access Server",
+                       NULL, NULL);
+
+       sdp_data_free(ch);
+       sdp_list_free(proto[0], NULL);
+       sdp_list_free(proto[1], NULL);
+       sdp_list_free(apseq, NULL);
+       sdp_list_free(aproto, NULL);
+
+       return record;
+}
+
+static void connect_req(struct sap_connection *conn,
+                                       struct sap_parameter *param)
+{
+       DBG("SAP_CONNECT_REQUEST");
+}
+
+static int disconnect_req(struct sap_connection *conn, uint8_t disc_type)
+{
+       DBG("SAP_DISCONNECT_REQUEST");
+       return 0;
+}
+
+static void transfer_apdu_req(struct sap_connection *conn,
+                                       struct sap_parameter *param)
+{
+       DBG("SAP_APDU_REQUEST");
+}
+
+static void transfer_atr_req(struct sap_connection *conn)
+{
+       DBG("SAP_ATR_REQUEST");
+}
+
+static void power_sim_off_req(struct sap_connection *conn)
+{
+       DBG("SAP_SIM_OFF_REQUEST");
+}
+
+static void power_sim_on_req(struct sap_connection *conn)
+{
+       DBG("SAP_SIM_ON_REQUEST");
+}
+
+static void reset_sim_req(struct sap_connection *conn)
+{
+       DBG("SAP_RESET_SIM_REQUEST");
+}
+
+static void transfer_card_reader_status_req(struct sap_connection *conn)
+{
+       DBG("SAP_TRANSFER_CARD_READER_STATUS_REQUEST");
+}
+
+static void set_transport_protocol_req(struct sap_connection *conn,
+                                       struct sap_parameter *param)
+{
+       DBG("SAP_SET_TRANSPORT_PROTOCOL_REQUEST");
+}
+
+int sap_connect_rsp(void *sap_device, uint8_t status, uint16_t maxmsgsize)
+{
+       return 0;
+}
+
+int sap_disconnect_rsp(void *sap_device)
+{
+       return 0;
+}
+
+int sap_transfer_apdu_rsp(void *sap_device, uint8_t result, uint8_t *apdu,
+                                       uint16_t length)
+{
+       return 0;
+}
+
+int sap_transfer_atr_rsp(void *sap_device, uint8_t result, uint8_t *atr,
+                                       uint16_t length)
+{
+       return 0;
+}
+
+int sap_power_sim_off_rsp(void *sap_device, uint8_t result)
+{
+       return 0;
+}
+
+int sap_power_sim_on_rsp(void *sap_device, uint8_t result)
+{
+       return 0;
+}
+
+int sap_reset_sim_rsp(void *sap_device, uint8_t result)
+{
+       return 0;
+}
+
+int sap_transfer_card_reader_status_rsp(void *sap_device, uint8_t result,
+                                               uint8_t status)
+{
+       return 0;
+}
+
+int sap_transport_protocol_rsp(void *sap_device, uint8_t result)
+{
+       return 0;
+}
+
+int sap_error_rsp(void *sap_device)
+{
+       return 0;
+}
+
+int sap_status_ind(void *sap_device, uint8_t status_change)
+{
+       return 0;
+}
+
+static int handle_cmd(void *data, void *buf, size_t size)
+{
+       struct sap_message *msg = buf;
+       struct sap_connection *conn = data;
+
+       if (!conn)
+               return -EINVAL;
+
+       if (size < sizeof(struct sap_message))
+               return -EINVAL;
+
+       if (msg->nparam != 0 && size < (sizeof(struct sap_message) +
+                                       sizeof(struct sap_parameter) + 4))
+               return -EBADMSG;
+
+       switch (msg->id) {
+       case SAP_CONNECT_REQ:
+               connect_req(conn, msg->param);
+               return 0;
+       case SAP_DISCONNECT_REQ:
+               disconnect_req(conn, SAP_DISCONNECTION_TYPE_CLIENT);
+               return 0;
+       case SAP_TRANSFER_APDU_REQ:
+               transfer_apdu_req(conn, msg->param);
+               return 0;
+       case SAP_TRANSFER_ATR_REQ:
+               transfer_atr_req(conn);
+               return 0;
+       case SAP_POWER_SIM_OFF_REQ:
+               power_sim_off_req(conn);
+               return 0;
+       case SAP_POWER_SIM_ON_REQ:
+               power_sim_on_req(conn);
+               return 0;
+       case SAP_RESET_SIM_REQ:
+               reset_sim_req(conn);
+               return 0;
+       case SAP_TRANSFER_CARD_READER_STATUS_REQ:
+               transfer_card_reader_status_req(conn);
+               return 0;
+       case SAP_SET_TRANSPORT_PROTOCOL_REQ:
+               set_transport_protocol_req(conn, msg->param);
+               return 0;
+       default:
+               DBG("SAP unknown message.");
+               return -ENOMSG;
+       }
+
+       return -1;
+}
+
+static void sap_conn_remove(struct sap_connection *conn)
+{
+       DBG("conn %p", conn);
+
+       if (!conn)
+               return;
+
+       if (conn->io) {
+               g_io_channel_shutdown(conn->io, TRUE, NULL);
+               g_io_channel_unref(conn->io);
+       }
+
+       conn->io = NULL;
+       g_free(conn);
+       server->conn = NULL;
+}
+
+static gboolean sap_io_cb(GIOChannel *io, GIOCondition cond, gpointer data)
+{
+       char buf[SAP_BUF_SIZE];
+       size_t bytes_read = 0;
+       GError *gerr = NULL;
+       GIOStatus gstatus;
+
+       DBG("io %p", io);
+
+       if (cond & G_IO_NVAL) {
+               DBG("ERR (G_IO_NVAL) on rfcomm socket.");
+               return FALSE;
+       }
+
+       if (cond & G_IO_ERR) {
+               DBG("ERR (G_IO_ERR) on rfcomm socket.");
+               return FALSE;
+       }
+
+       if (cond & G_IO_HUP) {
+               DBG("HUP on rfcomm socket.");
+               return FALSE;
+       }
+
+       gstatus = g_io_channel_read_chars(io, buf, sizeof(buf) - 1,
+                               &bytes_read, &gerr);
+       if (gstatus != G_IO_STATUS_NORMAL) {
+               if (gerr)
+                       g_error_free(gerr);
+
+               return TRUE;
+       }
+
+       if (handle_cmd(data, buf, bytes_read) < 0)
+               error("Invalid SAP message.");
+
+       return TRUE;
+}
+
+static void sap_io_destroy(void *data)
+{
+       struct sap_connection *conn = data;
+
+       DBG("conn %p", conn);
+
+       if (conn && conn->io) {
+               conn->io = NULL;
+               sap_conn_remove(conn);
+       }
+}
+
+static void sap_connect_cb(GIOChannel *io, GError *gerr, gpointer data)
+{
+       struct sap_connection *conn = data;
+
+       DBG("io %p gerr %p data %p ", io, gerr, data);
+
+       if (!conn)
+               return;
+
+       g_io_add_watch_full(io, G_PRIORITY_DEFAULT,
+                       G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                       sap_io_cb, conn, sap_io_destroy);
+}
+
+static void connect_auth_cb(DBusError *derr, void *data)
+{
+       struct sap_connection *conn = data;
+       GError *gerr = NULL;
+
+       DBG("derr %p data %p ", derr, data);
+
+       if (!conn)
+               return;
+
+       if (derr && dbus_error_is_set(derr)) {
+               error("Access denied: %s", derr->message);
+               sap_conn_remove(conn);
+               return;
+       }
+
+       if (!bt_io_accept(conn->io, sap_connect_cb, conn, NULL, &gerr)) {
+               error("bt_io_accept: %s", gerr->message);
+               g_error_free(gerr);
+               sap_conn_remove(conn);
+               return;
+       }
+
+       DBG("Client has been authorized.");
+}
+
+static void connect_confirm_cb(GIOChannel *io, gpointer data)
+{
+       struct sap_connection *conn = server->conn;
+       GError *gerr = NULL;
+       bdaddr_t src, dst;
+       int err;
+
+       DBG("io %p data %p ", io, data);
+
+       if (!io)
+               return;
+
+       if (conn) {
+               g_io_channel_shutdown(io, TRUE, NULL);
+               return;
+       }
+
+       conn = g_try_new0(struct sap_connection, 1);
+       if (!conn) {
+               error("Can't allocate memory for incomming SAP connection.");
+               g_io_channel_shutdown(io, TRUE, NULL);
+               return;
+       }
+
+       g_io_channel_set_encoding(io, NULL, NULL);
+       g_io_channel_set_buffered(io, FALSE);
+
+       server->conn = conn;
+       conn->io = g_io_channel_ref(io);
+       conn->state = SAP_STATE_DISCONNECTED;
+
+       bt_io_get(io, BT_IO_RFCOMM, &gerr,
+                       BT_IO_OPT_SOURCE_BDADDR, &src,
+                       BT_IO_OPT_DEST_BDADDR, &dst,
+                       BT_IO_OPT_INVALID);
+       if (gerr) {
+               error("%s", gerr->message);
+               g_error_free(gerr);
+               sap_conn_remove(conn);
+               return;
+       }
+
+       err = btd_request_authorization(&src, &dst, SAP_UUID,
+                                       connect_auth_cb, conn);
+       if (err < 0) {
+               DBG("Authorization denied: %d %s", err,  strerror(err));
+               sap_conn_remove(conn);
+               return;
+       }
+
+       DBG("SAP incoming connection (sock %d) authorization.",
+                               g_io_channel_unix_get_fd(io));
+}
+
+static inline DBusMessage *message_failed(DBusMessage *msg,
+                                       const char *description)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+                               "%s", description);
+}
+
+static DBusMessage *disconnect(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct sap_server *server = data;
+
+       DBG("server %p", server);
+
+       if (!server)
+               return message_failed(msg, "Server internal error.");
+
+       DBG("conn %p", server->conn);
+
+       if (!server->conn)
+               return message_failed(msg, "Client already disconnected");
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *get_properties(DBusConnection *c,
+                               DBusMessage *msg, void *data)
+{
+       struct sap_connection *conn = data;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       DBusMessageIter dict;
+       dbus_bool_t connected;
+
+       if (!conn)
+               return message_failed(msg, "Server internal error.");
+
+       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 = (conn->state == SAP_STATE_CONNECTED);
+       dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static GDBusMethodTable server_methods[] = {
+       {"GetProperties", "", "a{sv}", get_properties},
+       {"Disconnect", "", "", disconnect},
+       { }
+};
+
+static GDBusSignalTable server_signals[] = {
+       { "PropertyChanged", "sv"},
+       { }
+};
+
+static void server_free(struct sap_server *server)
+{
+       if (!server)
+               return;
+
+       sap_conn_remove(server->conn);
+       g_free(server->path);
+       g_free(server);
+}
+
+static void destroy_sap_interface(void *data)
+{
+       struct sap_server *server = data;
+
+       DBG("Unregistered interface %s on path %s",
+                       SAP_SERVER_INTERFACE, server->path);
+
+       server_free(server);
+}
+
+int sap_server_register(const char *path, bdaddr_t *src)
+{
+       sdp_record_t *record = NULL;
+       GError *gerr = NULL;
+       GIOChannel *io;
+
+       if (sap_init() < 0) {
+               error("Sap driver initialization failed.");
+               return -1;
+       }
+
+       server = g_try_new0(struct sap_server, 1);
+       if (!server) {
+               sap_exit();
+               return -ENOMEM;
+       }
+
+       bacpy(&server->src, src);
+       server->path = g_strdup(path);
+
+       record = create_sap_record(SAP_SERVER_CHANNEL);
+       if (!record) {
+               error("Creating SAP SDP record failed.");
+               goto sdp_err;
+       }
+
+       if (add_record_to_server(&server->src, record) < 0) {
+               error("Adding SAP SDP record to the SDP server failed.");
+               sdp_record_free(record);
+               goto sdp_err;
+       }
+
+       server->record_id = record->handle;
+
+       io = bt_io_listen(BT_IO_RFCOMM, NULL, connect_confirm_cb, server,
+                       NULL, &gerr,
+                       BT_IO_OPT_SOURCE_BDADDR, &server->src,
+                       BT_IO_OPT_CHANNEL, SAP_SERVER_CHANNEL,
+                       BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH,
+                       BT_IO_OPT_MASTER, TRUE,
+                       BT_IO_OPT_INVALID);
+       if (!io) {
+               error("Can't listen at channel %d.", SAP_SERVER_CHANNEL);
+               g_error_free(gerr);
+               goto server_err;
+       }
+
+       DBG("Listen socket 0x%02x", g_io_channel_unix_get_fd(io));
+
+       server->listen_io = io;
+       server->conn = NULL;
+
+       if (!g_dbus_register_interface(connection, path, SAP_SERVER_INTERFACE,
+                               server_methods, server_signals, NULL,
+                               server, destroy_sap_interface)) {
+               error("D-Bus failed to register %s interface",
+                                               SAP_SERVER_INTERFACE);
+               goto server_err;
+       }
+
+       return 0;
+
+server_err:
+       remove_record_from_server(server->record_id);
+sdp_err:
+       server_free(server);
+       server = NULL;
+       sap_exit();
+
+       return -1;
+}
+
+int sap_server_unregister(const char *path)
+{
+       if (!server)
+               return -EINVAL;
+
+       remove_record_from_server(server->record_id);
+
+       if (server->conn)
+               sap_conn_remove(server->conn);
+
+       if (server->listen_io) {
+               g_io_channel_shutdown(server->listen_io, TRUE, NULL);
+               g_io_channel_unref(server->listen_io);
+               server->listen_io = NULL;
+       }
+
+       g_dbus_unregister_interface(connection, path, SAP_SERVER_INTERFACE);
+
+       server_free(server);
+       server = NULL;
+       sap_exit();
+
+       return 0;
+}
+
+int sap_server_init(DBusConnection *conn)
+{
+       connection = dbus_connection_ref(conn);
+       return 0;
+}
+
+void sap_server_exit(void)
+{
+       dbus_connection_unref(connection);
+       connection = NULL;
+}
diff --git a/sap/server.h b/sap/server.h
new file mode 100644 (file)
index 0000000..ef2b7b8
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010 ST-Ericsson SA
+ *
+ *  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 <gdbus.h>
+
+int sap_server_init(DBusConnection *conn);
+void sap_server_exit(void);
+int sap_server_register(const char *path, bdaddr_t *src);
+int sap_server_unregister(const char *path);
diff --git a/sbc/formats.h b/sbc/formats.h
new file mode 100644 (file)
index 0000000..3050b25
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <byteswap.h>
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define COMPOSE_ID(a,b,c,d)    ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
+#define LE_SHORT(v)            (v)
+#define LE_INT(v)              (v)
+#define BE_SHORT(v)            bswap_16(v)
+#define BE_INT(v)              bswap_32(v)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define COMPOSE_ID(a,b,c,d)    ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
+#define LE_SHORT(v)            bswap_16(v)
+#define LE_INT(v)              bswap_32(v)
+#define BE_SHORT(v)            (v)
+#define BE_INT(v)              (v)
+#else
+#error "Wrong endian"
+#endif
+
+#define AU_MAGIC               COMPOSE_ID('.','s','n','d')
+
+#define AU_FMT_ULAW            1
+#define AU_FMT_LIN8            2
+#define AU_FMT_LIN16           3
+
+struct au_header {
+       uint32_t magic;         /* '.snd' */
+       uint32_t hdr_size;      /* size of header (min 24) */
+       uint32_t data_size;     /* size of data */
+       uint32_t encoding;      /* see to AU_FMT_XXXX */
+       uint32_t sample_rate;   /* sample rate */
+       uint32_t channels;      /* number of channels (voices) */
+};
diff --git a/sbc/sbc.c b/sbc/sbc.c
new file mode 100644 (file)
index 0000000..77fcc5d
--- /dev/null
+++ b/sbc/sbc.c
@@ -0,0 +1,1234 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2008  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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
+ *
+ */
+
+/* todo items:
+
+  use a log2 table for byte integer scale factors calculation (sum log2 results
+  for high and low bytes) fill bitpool by 16 bits instead of one at a time in
+  bits allocation/bitpool generation port to the dsp
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <limits.h>
+
+#include "sbc_math.h"
+#include "sbc_tables.h"
+
+#include "sbc.h"
+#include "sbc_primitives.h"
+
+#define SBC_SYNCWORD   0x9C
+
+/* This structure contains an unpacked SBC frame.
+   Yes, there is probably quite some unused space herein */
+struct sbc_frame {
+       uint8_t frequency;
+       uint8_t block_mode;
+       uint8_t blocks;
+       enum {
+               MONO            = SBC_MODE_MONO,
+               DUAL_CHANNEL    = SBC_MODE_DUAL_CHANNEL,
+               STEREO          = SBC_MODE_STEREO,
+               JOINT_STEREO    = SBC_MODE_JOINT_STEREO
+       } mode;
+       uint8_t channels;
+       enum {
+               LOUDNESS        = SBC_AM_LOUDNESS,
+               SNR             = SBC_AM_SNR
+       } allocation;
+       uint8_t subband_mode;
+       uint8_t subbands;
+       uint8_t bitpool;
+       uint16_t codesize;
+       uint8_t length;
+
+       /* bit number x set means joint stereo has been used in subband x */
+       uint8_t joint;
+
+       /* only the lower 4 bits of every element are to be used */
+       uint32_t SBC_ALIGNED scale_factor[2][8];
+
+       /* raw integer subband samples in the frame */
+       int32_t SBC_ALIGNED sb_sample_f[16][2][8];
+
+       /* modified subband samples */
+       int32_t SBC_ALIGNED sb_sample[16][2][8];
+
+       /* original pcm audio samples */
+       int16_t SBC_ALIGNED pcm_sample[2][16*8];
+};
+
+struct sbc_decoder_state {
+       int subbands;
+       int32_t V[2][170];
+       int offset[2][16];
+};
+
+/*
+ * Calculates the CRC-8 of the first len bits in data
+ */
+static const uint8_t crc_table[256] = {
+       0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53,
+       0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB,
+       0xCD, 0xD0, 0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E,
+       0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76,
+       0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE, 0xC9, 0xD4,
+       0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C,
+       0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19,
+       0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1,
+       0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40,
+       0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8,
+       0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D,
+       0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65,
+       0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7,
+       0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F,
+       0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A,
+       0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2,
+       0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75,
+       0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D,
+       0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8,
+       0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50,
+       0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2,
+       0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A,
+       0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F,
+       0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7,
+       0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66,
+       0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E,
+       0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB,
+       0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43,
+       0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1,
+       0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09,
+       0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C,
+       0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4
+};
+
+static uint8_t sbc_crc8(const uint8_t *data, size_t len)
+{
+       uint8_t crc = 0x0f;
+       size_t i;
+       uint8_t octet;
+
+       for (i = 0; i < len / 8; i++)
+               crc = crc_table[crc ^ data[i]];
+
+       octet = data[i];
+       for (i = 0; i < len % 8; i++) {
+               char bit = ((octet ^ crc) & 0x80) >> 7;
+
+               crc = ((crc & 0x7f) << 1) ^ (bit ? 0x1d : 0);
+
+               octet = octet << 1;
+       }
+
+       return crc;
+}
+
+/*
+ * Code straight from the spec to calculate the bits array
+ * Takes a pointer to the frame in question, a pointer to the bits array and
+ * the sampling frequency (as 2 bit integer)
+ */
+static SBC_ALWAYS_INLINE void sbc_calculate_bits_internal(
+               const struct sbc_frame *frame, int (*bits)[8], int subbands)
+{
+       uint8_t sf = frame->frequency;
+
+       if (frame->mode == MONO || frame->mode == DUAL_CHANNEL) {
+               int bitneed[2][8], loudness, max_bitneed, bitcount, slicecount, bitslice;
+               int ch, sb;
+
+               for (ch = 0; ch < frame->channels; ch++) {
+                       max_bitneed = 0;
+                       if (frame->allocation == SNR) {
+                               for (sb = 0; sb < subbands; sb++) {
+                                       bitneed[ch][sb] = frame->scale_factor[ch][sb];
+                                       if (bitneed[ch][sb] > max_bitneed)
+                                               max_bitneed = bitneed[ch][sb];
+                               }
+                       } else {
+                               for (sb = 0; sb < subbands; sb++) {
+                                       if (frame->scale_factor[ch][sb] == 0)
+                                               bitneed[ch][sb] = -5;
+                                       else {
+                                               if (subbands == 4)
+                                                       loudness = frame->scale_factor[ch][sb] - sbc_offset4[sf][sb];
+                                               else
+                                                       loudness = frame->scale_factor[ch][sb] - sbc_offset8[sf][sb];
+                                               if (loudness > 0)
+                                                       bitneed[ch][sb] = loudness / 2;
+                                               else
+                                                       bitneed[ch][sb] = loudness;
+                                       }
+                                       if (bitneed[ch][sb] > max_bitneed)
+                                               max_bitneed = bitneed[ch][sb];
+                               }
+                       }
+
+                       bitcount = 0;
+                       slicecount = 0;
+                       bitslice = max_bitneed + 1;
+                       do {
+                               bitslice--;
+                               bitcount += slicecount;
+                               slicecount = 0;
+                               for (sb = 0; sb < subbands; sb++) {
+                                       if ((bitneed[ch][sb] > bitslice + 1) && (bitneed[ch][sb] < bitslice + 16))
+                                               slicecount++;
+                                       else if (bitneed[ch][sb] == bitslice + 1)
+                                               slicecount += 2;
+                               }
+                       } while (bitcount + slicecount < frame->bitpool);
+
+                       if (bitcount + slicecount == frame->bitpool) {
+                               bitcount += slicecount;
+                               bitslice--;
+                       }
+
+                       for (sb = 0; sb < subbands; sb++) {
+                               if (bitneed[ch][sb] < bitslice + 2)
+                                       bits[ch][sb] = 0;
+                               else {
+                                       bits[ch][sb] = bitneed[ch][sb] - bitslice;
+                                       if (bits[ch][sb] > 16)
+                                               bits[ch][sb] = 16;
+                               }
+                       }
+
+                       for (sb = 0; bitcount < frame->bitpool &&
+                                                       sb < subbands; sb++) {
+                               if ((bits[ch][sb] >= 2) && (bits[ch][sb] < 16)) {
+                                       bits[ch][sb]++;
+                                       bitcount++;
+                               } else if ((bitneed[ch][sb] == bitslice + 1) && (frame->bitpool > bitcount + 1)) {
+                                       bits[ch][sb] = 2;
+                                       bitcount += 2;
+                               }
+                       }
+
+                       for (sb = 0; bitcount < frame->bitpool &&
+                                                       sb < subbands; sb++) {
+                               if (bits[ch][sb] < 16) {
+                                       bits[ch][sb]++;
+                                       bitcount++;
+                               }
+                       }
+
+               }
+
+       } else if (frame->mode == STEREO || frame->mode == JOINT_STEREO) {
+               int bitneed[2][8], loudness, max_bitneed, bitcount, slicecount, bitslice;
+               int ch, sb;
+
+               max_bitneed = 0;
+               if (frame->allocation == SNR) {
+                       for (ch = 0; ch < 2; ch++) {
+                               for (sb = 0; sb < subbands; sb++) {
+                                       bitneed[ch][sb] = frame->scale_factor[ch][sb];
+                                       if (bitneed[ch][sb] > max_bitneed)
+                                               max_bitneed = bitneed[ch][sb];
+                               }
+                       }
+               } else {
+                       for (ch = 0; ch < 2; ch++) {
+                               for (sb = 0; sb < subbands; sb++) {
+                                       if (frame->scale_factor[ch][sb] == 0)
+                                               bitneed[ch][sb] = -5;
+                                       else {
+                                               if (subbands == 4)
+                                                       loudness = frame->scale_factor[ch][sb] - sbc_offset4[sf][sb];
+                                               else
+                                                       loudness = frame->scale_factor[ch][sb] - sbc_offset8[sf][sb];
+                                               if (loudness > 0)
+                                                       bitneed[ch][sb] = loudness / 2;
+                                               else
+                                                       bitneed[ch][sb] = loudness;
+                                       }
+                                       if (bitneed[ch][sb] > max_bitneed)
+                                               max_bitneed = bitneed[ch][sb];
+                               }
+                       }
+               }
+
+               bitcount = 0;
+               slicecount = 0;
+               bitslice = max_bitneed + 1;
+               do {
+                       bitslice--;
+                       bitcount += slicecount;
+                       slicecount = 0;
+                       for (ch = 0; ch < 2; ch++) {
+                               for (sb = 0; sb < subbands; sb++) {
+                                       if ((bitneed[ch][sb] > bitslice + 1) && (bitneed[ch][sb] < bitslice + 16))
+                                               slicecount++;
+                                       else if (bitneed[ch][sb] == bitslice + 1)
+                                               slicecount += 2;
+                               }
+                       }
+               } while (bitcount + slicecount < frame->bitpool);
+
+               if (bitcount + slicecount == frame->bitpool) {
+                       bitcount += slicecount;
+                       bitslice--;
+               }
+
+               for (ch = 0; ch < 2; ch++) {
+                       for (sb = 0; sb < subbands; sb++) {
+                               if (bitneed[ch][sb] < bitslice + 2) {
+                                       bits[ch][sb] = 0;
+                               } else {
+                                       bits[ch][sb] = bitneed[ch][sb] - bitslice;
+                                       if (bits[ch][sb] > 16)
+                                               bits[ch][sb] = 16;
+                               }
+                       }
+               }
+
+               ch = 0;
+               sb = 0;
+               while (bitcount < frame->bitpool) {
+                       if ((bits[ch][sb] >= 2) && (bits[ch][sb] < 16)) {
+                               bits[ch][sb]++;
+                               bitcount++;
+                       } else if ((bitneed[ch][sb] == bitslice + 1) && (frame->bitpool > bitcount + 1)) {
+                               bits[ch][sb] = 2;
+                               bitcount += 2;
+                       }
+                       if (ch == 1) {
+                               ch = 0;
+                               sb++;
+                               if (sb >= subbands)
+                                       break;
+                       } else
+                               ch = 1;
+               }
+
+               ch = 0;
+               sb = 0;
+               while (bitcount < frame->bitpool) {
+                       if (bits[ch][sb] < 16) {
+                               bits[ch][sb]++;
+                               bitcount++;
+                       }
+                       if (ch == 1) {
+                               ch = 0;
+                               sb++;
+                               if (sb >= subbands)
+                                       break;
+                       } else
+                               ch = 1;
+               }
+
+       }
+
+}
+
+static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8])
+{
+       if (frame->subbands == 4)
+               sbc_calculate_bits_internal(frame, bits, 4);
+       else
+               sbc_calculate_bits_internal(frame, bits, 8);
+}
+
+/*
+ * Unpacks a SBC frame at the beginning of the stream in data,
+ * which has at most len bytes into frame.
+ * Returns the length in bytes of the packed frame, or a negative
+ * value on error. The error codes are:
+ *
+ *  -1   Data stream too short
+ *  -2   Sync byte incorrect
+ *  -3   CRC8 incorrect
+ *  -4   Bitpool value out of bounds
+ */
+static int sbc_unpack_frame(const uint8_t *data, struct sbc_frame *frame,
+                                                               size_t len)
+{
+       unsigned int consumed;
+       /* Will copy the parts of the header that are relevant to crc
+        * calculation here */
+       uint8_t crc_header[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+       int crc_pos = 0;
+       int32_t temp;
+
+       int audio_sample;
+       int ch, sb, blk, bit;   /* channel, subband, block and bit standard
+                                  counters */
+       int bits[2][8];         /* bits distribution */
+       uint32_t levels[2][8];  /* levels derived from that */
+
+       if (len < 4)
+               return -1;
+
+       if (data[0] != SBC_SYNCWORD)
+               return -2;
+
+       frame->frequency = (data[1] >> 6) & 0x03;
+
+       frame->block_mode = (data[1] >> 4) & 0x03;
+       switch (frame->block_mode) {
+       case SBC_BLK_4:
+               frame->blocks = 4;
+               break;
+       case SBC_BLK_8:
+               frame->blocks = 8;
+               break;
+       case SBC_BLK_12:
+               frame->blocks = 12;
+               break;
+       case SBC_BLK_16:
+               frame->blocks = 16;
+               break;
+       }
+
+       frame->mode = (data[1] >> 2) & 0x03;
+       switch (frame->mode) {
+       case MONO:
+               frame->channels = 1;
+               break;
+       case DUAL_CHANNEL:      /* fall-through */
+       case STEREO:
+       case JOINT_STEREO:
+               frame->channels = 2;
+               break;
+       }
+
+       frame->allocation = (data[1] >> 1) & 0x01;
+
+       frame->subband_mode = (data[1] & 0x01);
+       frame->subbands = frame->subband_mode ? 8 : 4;
+
+       frame->bitpool = data[2];
+
+       if ((frame->mode == MONO || frame->mode == DUAL_CHANNEL) &&
+                       frame->bitpool > 16 * frame->subbands)
+               return -4;
+
+       if ((frame->mode == STEREO || frame->mode == JOINT_STEREO) &&
+                       frame->bitpool > 32 * frame->subbands)
+               return -4;
+
+       /* data[3] is crc, we're checking it later */
+
+       consumed = 32;
+
+       crc_header[0] = data[1];
+       crc_header[1] = data[2];
+       crc_pos = 16;
+
+       if (frame->mode == JOINT_STEREO) {
+               if (len * 8 < consumed + frame->subbands)
+                       return -1;
+
+               frame->joint = 0x00;
+               for (sb = 0; sb < frame->subbands - 1; sb++)
+                       frame->joint |= ((data[4] >> (7 - sb)) & 0x01) << sb;
+               if (frame->subbands == 4)
+                       crc_header[crc_pos / 8] = data[4] & 0xf0;
+               else
+                       crc_header[crc_pos / 8] = data[4];
+
+               consumed += frame->subbands;
+               crc_pos += frame->subbands;
+       }
+
+       if (len * 8 < consumed + (4 * frame->subbands * frame->channels))
+               return -1;
+
+       for (ch = 0; ch < frame->channels; ch++) {
+               for (sb = 0; sb < frame->subbands; sb++) {
+                       /* FIXME assert(consumed % 4 == 0); */
+                       frame->scale_factor[ch][sb] =
+                               (data[consumed >> 3] >> (4 - (consumed & 0x7))) & 0x0F;
+                       crc_header[crc_pos >> 3] |=
+                               frame->scale_factor[ch][sb] << (4 - (crc_pos & 0x7));
+
+                       consumed += 4;
+                       crc_pos += 4;
+               }
+       }
+
+       if (data[3] != sbc_crc8(crc_header, crc_pos))
+               return -3;
+
+       sbc_calculate_bits(frame, bits);
+
+       for (ch = 0; ch < frame->channels; ch++) {
+               for (sb = 0; sb < frame->subbands; sb++)
+                       levels[ch][sb] = (1 << bits[ch][sb]) - 1;
+       }
+
+       for (blk = 0; blk < frame->blocks; blk++) {
+               for (ch = 0; ch < frame->channels; ch++) {
+                       for (sb = 0; sb < frame->subbands; sb++) {
+                               if (levels[ch][sb] > 0) {
+                                       audio_sample = 0;
+                                       for (bit = 0; bit < bits[ch][sb]; bit++) {
+                                               if (consumed > len * 8)
+                                                       return -1;
+
+                                               if ((data[consumed >> 3] >> (7 - (consumed & 0x7))) & 0x01)
+                                                       audio_sample |= 1 << (bits[ch][sb] - bit - 1);
+
+                                               consumed++;
+                                       }
+
+                                       frame->sb_sample[blk][ch][sb] =
+                                               (((audio_sample << 1) | 1) << frame->scale_factor[ch][sb]) /
+                                               levels[ch][sb] - (1 << frame->scale_factor[ch][sb]);
+                               } else
+                                       frame->sb_sample[blk][ch][sb] = 0;
+                       }
+               }
+       }
+
+       if (frame->mode == JOINT_STEREO) {
+               for (blk = 0; blk < frame->blocks; blk++) {
+                       for (sb = 0; sb < frame->subbands; sb++) {
+                               if (frame->joint & (0x01 << sb)) {
+                                       temp = frame->sb_sample[blk][0][sb] +
+                                               frame->sb_sample[blk][1][sb];
+                                       frame->sb_sample[blk][1][sb] =
+                                               frame->sb_sample[blk][0][sb] -
+                                               frame->sb_sample[blk][1][sb];
+                                       frame->sb_sample[blk][0][sb] = temp;
+                               }
+                       }
+               }
+       }
+
+       if ((consumed & 0x7) != 0)
+               consumed += 8 - (consumed & 0x7);
+
+       return consumed >> 3;
+}
+
+static void sbc_decoder_init(struct sbc_decoder_state *state,
+                                       const struct sbc_frame *frame)
+{
+       int i, ch;
+
+       memset(state->V, 0, sizeof(state->V));
+       state->subbands = frame->subbands;
+
+       for (ch = 0; ch < 2; ch++)
+               for (i = 0; i < frame->subbands * 2; i++)
+                       state->offset[ch][i] = (10 * i + 10);
+}
+
+static SBC_ALWAYS_INLINE int16_t sbc_clip16(int32_t s)
+{
+       if (s > 0x7FFF)
+               return 0x7FFF;
+       else if (s < -0x8000)
+               return -0x8000;
+       else
+               return s;
+}
+
+static inline void sbc_synthesize_four(struct sbc_decoder_state *state,
+                               struct sbc_frame *frame, int ch, int blk)
+{
+       int i, k, idx;
+       int32_t *v = state->V[ch];
+       int *offset = state->offset[ch];
+
+       for (i = 0; i < 8; i++) {
+               /* Shifting */
+               offset[i]--;
+               if (offset[i] < 0) {
+                       offset[i] = 79;
+                       memcpy(v + 80, v, 9 * sizeof(*v));
+               }
+
+               /* Distribute the new matrix value to the shifted position */
+               v[offset[i]] = SCALE4_STAGED1(
+                       MULA(synmatrix4[i][0], frame->sb_sample[blk][ch][0],
+                       MULA(synmatrix4[i][1], frame->sb_sample[blk][ch][1],
+                       MULA(synmatrix4[i][2], frame->sb_sample[blk][ch][2],
+                       MUL (synmatrix4[i][3], frame->sb_sample[blk][ch][3])))));
+       }
+
+       /* Compute the samples */
+       for (idx = 0, i = 0; i < 4; i++, idx += 5) {
+               k = (i + 4) & 0xf;
+
+               /* Store in output, Q0 */
+               frame->pcm_sample[ch][blk * 4 + i] = sbc_clip16(SCALE4_STAGED1(
+                       MULA(v[offset[i] + 0], sbc_proto_4_40m0[idx + 0],
+                       MULA(v[offset[k] + 1], sbc_proto_4_40m1[idx + 0],
+                       MULA(v[offset[i] + 2], sbc_proto_4_40m0[idx + 1],
+                       MULA(v[offset[k] + 3], sbc_proto_4_40m1[idx + 1],
+                       MULA(v[offset[i] + 4], sbc_proto_4_40m0[idx + 2],
+                       MULA(v[offset[k] + 5], sbc_proto_4_40m1[idx + 2],
+                       MULA(v[offset[i] + 6], sbc_proto_4_40m0[idx + 3],
+                       MULA(v[offset[k] + 7], sbc_proto_4_40m1[idx + 3],
+                       MULA(v[offset[i] + 8], sbc_proto_4_40m0[idx + 4],
+                       MUL( v[offset[k] + 9], sbc_proto_4_40m1[idx + 4]))))))))))));
+       }
+}
+
+static inline void sbc_synthesize_eight(struct sbc_decoder_state *state,
+                               struct sbc_frame *frame, int ch, int blk)
+{
+       int i, j, k, idx;
+       int *offset = state->offset[ch];
+
+       for (i = 0; i < 16; i++) {
+               /* Shifting */
+               offset[i]--;
+               if (offset[i] < 0) {
+                       offset[i] = 159;
+                       for (j = 0; j < 9; j++)
+                               state->V[ch][j + 160] = state->V[ch][j];
+               }
+
+               /* Distribute the new matrix value to the shifted position */
+               state->V[ch][offset[i]] = SCALE8_STAGED1(
+                       MULA(synmatrix8[i][0], frame->sb_sample[blk][ch][0],
+                       MULA(synmatrix8[i][1], frame->sb_sample[blk][ch][1],
+                       MULA(synmatrix8[i][2], frame->sb_sample[blk][ch][2],
+                       MULA(synmatrix8[i][3], frame->sb_sample[blk][ch][3],
+                       MULA(synmatrix8[i][4], frame->sb_sample[blk][ch][4],
+                       MULA(synmatrix8[i][5], frame->sb_sample[blk][ch][5],
+                       MULA(synmatrix8[i][6], frame->sb_sample[blk][ch][6],
+                       MUL( synmatrix8[i][7], frame->sb_sample[blk][ch][7])))))))));
+       }
+
+       /* Compute the samples */
+       for (idx = 0, i = 0; i < 8; i++, idx += 5) {
+               k = (i + 8) & 0xf;
+
+               /* Store in output, Q0 */
+               frame->pcm_sample[ch][blk * 8 + i] = sbc_clip16(SCALE8_STAGED1(
+                       MULA(state->V[ch][offset[i] + 0], sbc_proto_8_80m0[idx + 0],
+                       MULA(state->V[ch][offset[k] + 1], sbc_proto_8_80m1[idx + 0],
+                       MULA(state->V[ch][offset[i] + 2], sbc_proto_8_80m0[idx + 1],
+                       MULA(state->V[ch][offset[k] + 3], sbc_proto_8_80m1[idx + 1],
+                       MULA(state->V[ch][offset[i] + 4], sbc_proto_8_80m0[idx + 2],
+                       MULA(state->V[ch][offset[k] + 5], sbc_proto_8_80m1[idx + 2],
+                       MULA(state->V[ch][offset[i] + 6], sbc_proto_8_80m0[idx + 3],
+                       MULA(state->V[ch][offset[k] + 7], sbc_proto_8_80m1[idx + 3],
+                       MULA(state->V[ch][offset[i] + 8], sbc_proto_8_80m0[idx + 4],
+                       MUL( state->V[ch][offset[k] + 9], sbc_proto_8_80m1[idx + 4]))))))))))));
+       }
+}
+
+static int sbc_synthesize_audio(struct sbc_decoder_state *state,
+                                               struct sbc_frame *frame)
+{
+       int ch, blk;
+
+       switch (frame->subbands) {
+       case 4:
+               for (ch = 0; ch < frame->channels; ch++) {
+                       for (blk = 0; blk < frame->blocks; blk++)
+                               sbc_synthesize_four(state, frame, ch, blk);
+               }
+               return frame->blocks * 4;
+
+       case 8:
+               for (ch = 0; ch < frame->channels; ch++) {
+                       for (blk = 0; blk < frame->blocks; blk++)
+                               sbc_synthesize_eight(state, frame, ch, blk);
+               }
+               return frame->blocks * 8;
+
+       default:
+               return -EIO;
+       }
+}
+
+static int sbc_analyze_audio(struct sbc_encoder_state *state,
+                                               struct sbc_frame *frame)
+{
+       int ch, blk;
+       int16_t *x;
+
+       switch (frame->subbands) {
+       case 4:
+               for (ch = 0; ch < frame->channels; ch++) {
+                       x = &state->X[ch][state->position - 16 +
+                                                       frame->blocks * 4];
+                       for (blk = 0; blk < frame->blocks; blk += 4) {
+                               state->sbc_analyze_4b_4s(
+                                       x,
+                                       frame->sb_sample_f[blk][ch],
+                                       frame->sb_sample_f[blk + 1][ch] -
+                                       frame->sb_sample_f[blk][ch]);
+                               x -= 16;
+                       }
+               }
+               return frame->blocks * 4;
+
+       case 8:
+               for (ch = 0; ch < frame->channels; ch++) {
+                       x = &state->X[ch][state->position - 32 +
+                                                       frame->blocks * 8];
+                       for (blk = 0; blk < frame->blocks; blk += 4) {
+                               state->sbc_analyze_4b_8s(
+                                       x,
+                                       frame->sb_sample_f[blk][ch],
+                                       frame->sb_sample_f[blk + 1][ch] -
+                                       frame->sb_sample_f[blk][ch]);
+                               x -= 32;
+                       }
+               }
+               return frame->blocks * 8;
+
+       default:
+               return -EIO;
+       }
+}
+
+/* Supplementary bitstream writing macros for 'sbc_pack_frame' */
+
+#define PUT_BITS(data_ptr, bits_cache, bits_count, v, n)               \
+       do {                                                            \
+               bits_cache = (v) | (bits_cache << (n));                 \
+               bits_count += (n);                                      \
+               if (bits_count >= 16) {                                 \
+                       bits_count -= 8;                                \
+                       *data_ptr++ = (uint8_t)                         \
+                               (bits_cache >> bits_count);             \
+                       bits_count -= 8;                                \
+                       *data_ptr++ = (uint8_t)                         \
+                               (bits_cache >> bits_count);             \
+               }                                                       \
+       } while (0)
+
+#define FLUSH_BITS(data_ptr, bits_cache, bits_count)                   \
+       do {                                                            \
+               while (bits_count >= 8) {                               \
+                       bits_count -= 8;                                \
+                       *data_ptr++ = (uint8_t)                         \
+                               (bits_cache >> bits_count);             \
+               }                                                       \
+               if (bits_count > 0)                                     \
+                       *data_ptr++ = (uint8_t)                         \
+                               (bits_cache << (8 - bits_count));       \
+       } while (0)
+
+/*
+ * Packs the SBC frame from frame into the memory at data. At most len
+ * bytes will be used, should more memory be needed an appropriate
+ * error code will be returned. Returns the length of the packed frame
+ * on success or a negative value on error.
+ *
+ * The error codes are:
+ * -1 Not enough memory reserved
+ * -2 Unsupported sampling rate
+ * -3 Unsupported number of blocks
+ * -4 Unsupported number of subbands
+ * -5 Bitpool value out of bounds
+ * -99 not implemented
+ */
+
+static SBC_ALWAYS_INLINE ssize_t sbc_pack_frame_internal(uint8_t *data,
+                                       struct sbc_frame *frame, size_t len,
+                                       int frame_subbands, int frame_channels,
+                                       int joint)
+{
+       /* Bitstream writer starts from the fourth byte */
+       uint8_t *data_ptr = data + 4;
+       uint32_t bits_cache = 0;
+       uint32_t bits_count = 0;
+
+       /* Will copy the header parts for CRC-8 calculation here */
+       uint8_t crc_header[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+       int crc_pos = 0;
+
+       uint32_t audio_sample;
+
+       int ch, sb, blk;        /* channel, subband, block and bit counters */
+       int bits[2][8];         /* bits distribution */
+       uint32_t levels[2][8];  /* levels are derived from that */
+       uint32_t sb_sample_delta[2][8];
+
+       data[0] = SBC_SYNCWORD;
+
+       data[1] = (frame->frequency & 0x03) << 6;
+
+       data[1] |= (frame->block_mode & 0x03) << 4;
+
+       data[1] |= (frame->mode & 0x03) << 2;
+
+       data[1] |= (frame->allocation & 0x01) << 1;
+
+       switch (frame_subbands) {
+       case 4:
+               /* Nothing to do */
+               break;
+       case 8:
+               data[1] |= 0x01;
+               break;
+       default:
+               return -4;
+               break;
+       }
+
+       data[2] = frame->bitpool;
+
+       if ((frame->mode == MONO || frame->mode == DUAL_CHANNEL) &&
+                       frame->bitpool > frame_subbands << 4)
+               return -5;
+
+       if ((frame->mode == STEREO || frame->mode == JOINT_STEREO) &&
+                       frame->bitpool > frame_subbands << 5)
+               return -5;
+
+       /* Can't fill in crc yet */
+
+       crc_header[0] = data[1];
+       crc_header[1] = data[2];
+       crc_pos = 16;
+
+       if (frame->mode == JOINT_STEREO) {
+               PUT_BITS(data_ptr, bits_cache, bits_count,
+                       joint, frame_subbands);
+               crc_header[crc_pos >> 3] = joint;
+               crc_pos += frame_subbands;
+       }
+
+       for (ch = 0; ch < frame_channels; ch++) {
+               for (sb = 0; sb < frame_subbands; sb++) {
+                       PUT_BITS(data_ptr, bits_cache, bits_count,
+                               frame->scale_factor[ch][sb] & 0x0F, 4);
+                       crc_header[crc_pos >> 3] <<= 4;
+                       crc_header[crc_pos >> 3] |= frame->scale_factor[ch][sb] & 0x0F;
+                       crc_pos += 4;
+               }
+       }
+
+       /* align the last crc byte */
+       if (crc_pos % 8)
+               crc_header[crc_pos >> 3] <<= 8 - (crc_pos % 8);
+
+       data[3] = sbc_crc8(crc_header, crc_pos);
+
+       sbc_calculate_bits(frame, bits);
+
+       for (ch = 0; ch < frame_channels; ch++) {
+               for (sb = 0; sb < frame_subbands; sb++) {
+                       levels[ch][sb] = ((1 << bits[ch][sb]) - 1) <<
+                               (32 - (frame->scale_factor[ch][sb] +
+                                       SCALE_OUT_BITS + 2));
+                       sb_sample_delta[ch][sb] = (uint32_t) 1 <<
+                               (frame->scale_factor[ch][sb] +
+                                       SCALE_OUT_BITS + 1);
+               }
+       }
+
+       for (blk = 0; blk < frame->blocks; blk++) {
+               for (ch = 0; ch < frame_channels; ch++) {
+                       for (sb = 0; sb < frame_subbands; sb++) {
+
+                               if (bits[ch][sb] == 0)
+                                       continue;
+
+                               audio_sample = ((uint64_t) levels[ch][sb] *
+                                       (sb_sample_delta[ch][sb] +
+                                       frame->sb_sample_f[blk][ch][sb])) >> 32;
+
+                               PUT_BITS(data_ptr, bits_cache, bits_count,
+                                       audio_sample, bits[ch][sb]);
+                       }
+               }
+       }
+
+       FLUSH_BITS(data_ptr, bits_cache, bits_count);
+
+       return data_ptr - data;
+}
+
+static ssize_t sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len,
+                                                               int joint)
+{
+       if (frame->subbands == 4) {
+               if (frame->channels == 1)
+                       return sbc_pack_frame_internal(
+                               data, frame, len, 4, 1, joint);
+               else
+                       return sbc_pack_frame_internal(
+                               data, frame, len, 4, 2, joint);
+       } else {
+               if (frame->channels == 1)
+                       return sbc_pack_frame_internal(
+                               data, frame, len, 8, 1, joint);
+               else
+                       return sbc_pack_frame_internal(
+                               data, frame, len, 8, 2, joint);
+       }
+}
+
+static void sbc_encoder_init(struct sbc_encoder_state *state,
+                                       const struct sbc_frame *frame)
+{
+       memset(&state->X, 0, sizeof(state->X));
+       state->position = (SBC_X_BUFFER_SIZE - frame->subbands * 9) & ~7;
+
+       sbc_init_primitives(state);
+}
+
+struct sbc_priv {
+       int init;
+       struct SBC_ALIGNED sbc_frame frame;
+       struct SBC_ALIGNED sbc_decoder_state dec_state;
+       struct SBC_ALIGNED sbc_encoder_state enc_state;
+};
+
+static void sbc_set_defaults(sbc_t *sbc, unsigned long flags)
+{
+       sbc->frequency = SBC_FREQ_44100;
+       sbc->mode = SBC_MODE_STEREO;
+       sbc->subbands = SBC_SB_8;
+       sbc->blocks = SBC_BLK_16;
+       sbc->bitpool = 32;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+       sbc->endian = SBC_LE;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+       sbc->endian = SBC_BE;
+#else
+#error "Unknown byte order"
+#endif
+}
+
+int sbc_init(sbc_t *sbc, unsigned long flags)
+{
+       if (!sbc)
+               return -EIO;
+
+       memset(sbc, 0, sizeof(sbc_t));
+
+       sbc->priv_alloc_base = malloc(sizeof(struct sbc_priv) + SBC_ALIGN_MASK);
+       if (!sbc->priv_alloc_base)
+               return -ENOMEM;
+
+       sbc->priv = (void *) (((uintptr_t) sbc->priv_alloc_base +
+                       SBC_ALIGN_MASK) & ~((uintptr_t) SBC_ALIGN_MASK));
+
+       memset(sbc->priv, 0, sizeof(struct sbc_priv));
+
+       sbc_set_defaults(sbc, flags);
+
+       return 0;
+}
+
+ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len)
+{
+       return sbc_decode(sbc, input, input_len, NULL, 0, NULL);
+}
+
+ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len,
+                       void *output, size_t output_len, size_t *written)
+{
+       struct sbc_priv *priv;
+       char *ptr;
+       int i, ch, framelen, samples;
+
+       if (!sbc || !input)
+               return -EIO;
+
+       priv = sbc->priv;
+
+       framelen = sbc_unpack_frame(input, &priv->frame, input_len);
+
+       if (!priv->init) {
+               sbc_decoder_init(&priv->dec_state, &priv->frame);
+               priv->init = 1;
+
+               sbc->frequency = priv->frame.frequency;
+               sbc->mode = priv->frame.mode;
+               sbc->subbands = priv->frame.subband_mode;
+               sbc->blocks = priv->frame.block_mode;
+               sbc->allocation = priv->frame.allocation;
+               sbc->bitpool = priv->frame.bitpool;
+
+               priv->frame.codesize = sbc_get_codesize(sbc);
+               priv->frame.length = framelen;
+       } else if (priv->frame.bitpool != sbc->bitpool) {
+               priv->frame.length = framelen;
+               sbc->bitpool = priv->frame.bitpool;
+       }
+
+       if (!output)
+               return framelen;
+
+       if (written)
+               *written = 0;
+
+       if (framelen <= 0)
+               return framelen;
+
+       samples = sbc_synthesize_audio(&priv->dec_state, &priv->frame);
+
+       ptr = output;
+
+       if (output_len < (size_t) (samples * priv->frame.channels * 2))
+               samples = output_len / (priv->frame.channels * 2);
+
+       for (i = 0; i < samples; i++) {
+               for (ch = 0; ch < priv->frame.channels; ch++) {
+                       int16_t s;
+                       s = priv->frame.pcm_sample[ch][i];
+
+                       if (sbc->endian == SBC_BE) {
+                               *ptr++ = (s & 0xff00) >> 8;
+                               *ptr++ = (s & 0x00ff);
+                       } else {
+                               *ptr++ = (s & 0x00ff);
+                               *ptr++ = (s & 0xff00) >> 8;
+                       }
+               }
+       }
+
+       if (written)
+               *written = samples * priv->frame.channels * 2;
+
+       return framelen;
+}
+
+ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len,
+                       void *output, size_t output_len, ssize_t *written)
+{
+       struct sbc_priv *priv;
+       int samples;
+       ssize_t framelen;
+       int (*sbc_enc_process_input)(int position,
+                       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+                       int nsamples, int nchannels);
+
+       if (!sbc || !input)
+               return -EIO;
+
+       priv = sbc->priv;
+
+       if (written)
+               *written = 0;
+
+       if (!priv->init) {
+               priv->frame.frequency = sbc->frequency;
+               priv->frame.mode = sbc->mode;
+               priv->frame.channels = sbc->mode == SBC_MODE_MONO ? 1 : 2;
+               priv->frame.allocation = sbc->allocation;
+               priv->frame.subband_mode = sbc->subbands;
+               priv->frame.subbands = sbc->subbands ? 8 : 4;
+               priv->frame.block_mode = sbc->blocks;
+               priv->frame.blocks = 4 + (sbc->blocks * 4);
+               priv->frame.bitpool = sbc->bitpool;
+               priv->frame.codesize = sbc_get_codesize(sbc);
+               priv->frame.length = sbc_get_frame_length(sbc);
+
+               sbc_encoder_init(&priv->enc_state, &priv->frame);
+               priv->init = 1;
+       } else if (priv->frame.bitpool != sbc->bitpool) {
+               priv->frame.length = sbc_get_frame_length(sbc);
+               priv->frame.bitpool = sbc->bitpool;
+       }
+
+       /* input must be large enough to encode a complete frame */
+       if (input_len < priv->frame.codesize)
+               return 0;
+
+       /* output must be large enough to receive the encoded frame */
+       if (!output || output_len < priv->frame.length)
+               return -ENOSPC;
+
+       /* Select the needed input data processing function and call it */
+       if (priv->frame.subbands == 8) {
+               if (sbc->endian == SBC_BE)
+                       sbc_enc_process_input =
+                               priv->enc_state.sbc_enc_process_input_8s_be;
+               else
+                       sbc_enc_process_input =
+                               priv->enc_state.sbc_enc_process_input_8s_le;
+       } else {
+               if (sbc->endian == SBC_BE)
+                       sbc_enc_process_input =
+                               priv->enc_state.sbc_enc_process_input_4s_be;
+               else
+                       sbc_enc_process_input =
+                               priv->enc_state.sbc_enc_process_input_4s_le;
+       }
+
+       priv->enc_state.position = sbc_enc_process_input(
+               priv->enc_state.position, (const uint8_t *) input,
+               priv->enc_state.X, priv->frame.subbands * priv->frame.blocks,
+               priv->frame.channels);
+
+       samples = sbc_analyze_audio(&priv->enc_state, &priv->frame);
+
+       if (priv->frame.mode == JOINT_STEREO) {
+               int j = priv->enc_state.sbc_calc_scalefactors_j(
+                       priv->frame.sb_sample_f, priv->frame.scale_factor,
+                       priv->frame.blocks, priv->frame.subbands);
+               framelen = sbc_pack_frame(output, &priv->frame, output_len, j);
+       } else {
+               priv->enc_state.sbc_calc_scalefactors(
+                       priv->frame.sb_sample_f, priv->frame.scale_factor,
+                       priv->frame.blocks, priv->frame.channels,
+                       priv->frame.subbands);
+               framelen = sbc_pack_frame(output, &priv->frame, output_len, 0);
+       }
+
+       if (written)
+               *written = framelen;
+
+       return samples * priv->frame.channels * 2;
+}
+
+void sbc_finish(sbc_t *sbc)
+{
+       if (!sbc)
+               return;
+
+       free(sbc->priv_alloc_base);
+
+       memset(sbc, 0, sizeof(sbc_t));
+}
+
+size_t sbc_get_frame_length(sbc_t *sbc)
+{
+       int ret;
+       uint8_t subbands, channels, blocks, joint, bitpool;
+       struct sbc_priv *priv;
+
+       priv = sbc->priv;
+       if (priv->init && priv->frame.bitpool == sbc->bitpool)
+               return priv->frame.length;
+
+       subbands = sbc->subbands ? 8 : 4;
+       blocks = 4 + (sbc->blocks * 4);
+       channels = sbc->mode == SBC_MODE_MONO ? 1 : 2;
+       joint = sbc->mode == SBC_MODE_JOINT_STEREO ? 1 : 0;
+       bitpool = sbc->bitpool;
+
+       ret = 4 + (4 * subbands * channels) / 8;
+       /* This term is not always evenly divide so we round it up */
+       if (channels == 1)
+               ret += ((blocks * channels * bitpool) + 7) / 8;
+       else
+               ret += (((joint ? subbands : 0) + blocks * bitpool) + 7) / 8;
+
+       return ret;
+}
+
+unsigned sbc_get_frame_duration(sbc_t *sbc)
+{
+       uint8_t subbands, blocks;
+       uint16_t frequency;
+       struct sbc_priv *priv;
+
+       priv = sbc->priv;
+       if (!priv->init) {
+               subbands = sbc->subbands ? 8 : 4;
+               blocks = 4 + (sbc->blocks * 4);
+       } else {
+               subbands = priv->frame.subbands;
+               blocks = priv->frame.blocks;
+       }
+
+       switch (sbc->frequency) {
+       case SBC_FREQ_16000:
+               frequency = 16000;
+               break;
+
+       case SBC_FREQ_32000:
+               frequency = 32000;
+               break;
+
+       case SBC_FREQ_44100:
+               frequency = 44100;
+               break;
+
+       case SBC_FREQ_48000:
+               frequency = 48000;
+               break;
+       default:
+               return 0;
+       }
+
+       return (1000000 * blocks * subbands) / frequency;
+}
+
+size_t sbc_get_codesize(sbc_t *sbc)
+{
+       uint16_t subbands, channels, blocks;
+       struct sbc_priv *priv;
+
+       priv = sbc->priv;
+       if (!priv->init) {
+               subbands = sbc->subbands ? 8 : 4;
+               blocks = 4 + (sbc->blocks * 4);
+               channels = sbc->mode == SBC_MODE_MONO ? 1 : 2;
+       } else {
+               subbands = priv->frame.subbands;
+               blocks = priv->frame.blocks;
+               channels = priv->frame.channels;
+       }
+
+       return subbands * blocks * channels * 2;
+}
+
+const char *sbc_get_implementation_info(sbc_t *sbc)
+{
+       struct sbc_priv *priv;
+
+       if (!sbc)
+               return NULL;
+
+       priv = sbc->priv;
+       if (!priv)
+               return NULL;
+
+       return priv->enc_state.implementation_info;
+}
+
+int sbc_reinit(sbc_t *sbc, unsigned long flags)
+{
+       struct sbc_priv *priv;
+
+       if (!sbc || !sbc->priv)
+               return -EIO;
+
+       priv = sbc->priv;
+
+       if (priv->init == 1)
+               memset(sbc->priv, 0, sizeof(struct sbc_priv));
+
+       sbc_set_defaults(sbc, flags);
+
+       return 0;
+}
diff --git a/sbc/sbc.h b/sbc/sbc.h
new file mode 100644 (file)
index 0000000..2f830ad
--- /dev/null
+++ b/sbc/sbc.h
@@ -0,0 +1,113 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 __SBC_H
+#define __SBC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <sys/types.h>
+
+/* sampling frequency */
+#define SBC_FREQ_16000         0x00
+#define SBC_FREQ_32000         0x01
+#define SBC_FREQ_44100         0x02
+#define SBC_FREQ_48000         0x03
+
+/* blocks */
+#define SBC_BLK_4              0x00
+#define SBC_BLK_8              0x01
+#define SBC_BLK_12             0x02
+#define SBC_BLK_16             0x03
+
+/* channel mode */
+#define SBC_MODE_MONO          0x00
+#define SBC_MODE_DUAL_CHANNEL  0x01
+#define SBC_MODE_STEREO                0x02
+#define SBC_MODE_JOINT_STEREO  0x03
+
+/* allocation method */
+#define SBC_AM_LOUDNESS                0x00
+#define SBC_AM_SNR             0x01
+
+/* subbands */
+#define SBC_SB_4               0x00
+#define SBC_SB_8               0x01
+
+/* Data endianess */
+#define SBC_LE                 0x00
+#define SBC_BE                 0x01
+
+struct sbc_struct {
+       unsigned long flags;
+
+       uint8_t frequency;
+       uint8_t blocks;
+       uint8_t subbands;
+       uint8_t mode;
+       uint8_t allocation;
+       uint8_t bitpool;
+       uint8_t endian;
+
+       void *priv;
+       void *priv_alloc_base;
+};
+
+typedef struct sbc_struct sbc_t;
+
+int sbc_init(sbc_t *sbc, unsigned long flags);
+int sbc_reinit(sbc_t *sbc, unsigned long flags);
+
+ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len);
+
+/* Decodes ONE input block into ONE output block */
+ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len,
+                       void *output, size_t output_len, size_t *written);
+
+/* Encodes ONE input block into ONE output block */
+ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len,
+                       void *output, size_t output_len, ssize_t *written);
+
+/* Returns the output block size in bytes */
+size_t sbc_get_frame_length(sbc_t *sbc);
+
+/* Returns the time one input/output block takes to play in msec*/
+unsigned sbc_get_frame_duration(sbc_t *sbc);
+
+/* Returns the input block size in bytes */
+size_t sbc_get_codesize(sbc_t *sbc);
+
+const char *sbc_get_implementation_info(sbc_t *sbc);
+void sbc_finish(sbc_t *sbc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SBC_H */
diff --git a/sbc/sbc_math.h b/sbc/sbc_math.h
new file mode 100644 (file)
index 0000000..9f126c6
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2008  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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
+ *
+ */
+
+#define fabs(x) ((x) < 0 ? -(x) : (x))
+/* C does not provide an explicit arithmetic shift right but this will
+   always be correct and every compiler *should* generate optimal code */
+#define ASR(val, bits) ((-2 >> 1 == -1) ? \
+                ((int32_t)(val)) >> (bits) : ((int32_t) (val)) / (1 << (bits)))
+
+#define SCALE_SPROTO4_TBL      12
+#define SCALE_SPROTO8_TBL      14
+#define SCALE_NPROTO4_TBL      11
+#define SCALE_NPROTO8_TBL      11
+#define SCALE4_STAGED1_BITS    15
+#define SCALE4_STAGED2_BITS    16
+#define SCALE8_STAGED1_BITS    15
+#define SCALE8_STAGED2_BITS    16
+
+typedef int32_t sbc_fixed_t;
+
+#define SCALE4_STAGED1(src) ASR(src, SCALE4_STAGED1_BITS)
+#define SCALE4_STAGED2(src) ASR(src, SCALE4_STAGED2_BITS)
+#define SCALE8_STAGED1(src) ASR(src, SCALE8_STAGED1_BITS)
+#define SCALE8_STAGED2(src) ASR(src, SCALE8_STAGED2_BITS)
+
+#define SBC_FIXED_0(val) { val = 0; }
+#define MUL(a, b)        ((a) * (b))
+#ifdef __arm__
+#define MULA(a, b, res) ({                             \
+               int tmp = res;                  \
+               __asm__(                                \
+                       "mla %0, %2, %3, %0"            \
+                       : "=&r" (tmp)                   \
+                       : "0" (tmp), "r" (a), "r" (b)); \
+               tmp; })
+#else
+#define MULA(a, b, res)  ((a) * (b) + (res))
+#endif
diff --git a/sbc/sbc_primitives.c b/sbc/sbc_primitives.c
new file mode 100644 (file)
index 0000000..ad780d0
--- /dev/null
@@ -0,0 +1,554 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 <stdint.h>
+#include <limits.h>
+#include <string.h>
+#include "sbc.h"
+#include "sbc_math.h"
+#include "sbc_tables.h"
+
+#include "sbc_primitives.h"
+#include "sbc_primitives_mmx.h"
+#include "sbc_primitives_iwmmxt.h"
+#include "sbc_primitives_neon.h"
+#include "sbc_primitives_armv6.h"
+
+/*
+ * A reference C code of analysis filter with SIMD-friendly tables
+ * reordering and code layout. This code can be used to develop platform
+ * specific SIMD optimizations. Also it may be used as some kind of test
+ * for compiler autovectorization capabilities (who knows, if the compiler
+ * is very good at this stuff, hand optimized assembly may be not strictly
+ * needed for some platform).
+ *
+ * Note: It is also possible to make a simple variant of analysis filter,
+ * which needs only a single constants table without taking care about
+ * even/odd cases. This simple variant of filter can be implemented without
+ * input data permutation. The only thing that would be lost is the
+ * possibility to use pairwise SIMD multiplications. But for some simple
+ * CPU cores without SIMD extensions it can be useful. If anybody is
+ * interested in implementing such variant of a filter, sourcecode from
+ * bluez versions 4.26/4.27 can be used as a reference and the history of
+ * the changes in git repository done around that time may be worth checking.
+ */
+
+static inline void sbc_analyze_four_simd(const int16_t *in, int32_t *out,
+                                                       const FIXED_T *consts)
+{
+       FIXED_A t1[4];
+       FIXED_T t2[4];
+       int hop = 0;
+
+       /* rounding coefficient */
+       t1[0] = t1[1] = t1[2] = t1[3] =
+               (FIXED_A) 1 << (SBC_PROTO_FIXED4_SCALE - 1);
+
+       /* low pass polyphase filter */
+       for (hop = 0; hop < 40; hop += 8) {
+               t1[0] += (FIXED_A) in[hop] * consts[hop];
+               t1[0] += (FIXED_A) in[hop + 1] * consts[hop + 1];
+               t1[1] += (FIXED_A) in[hop + 2] * consts[hop + 2];
+               t1[1] += (FIXED_A) in[hop + 3] * consts[hop + 3];
+               t1[2] += (FIXED_A) in[hop + 4] * consts[hop + 4];
+               t1[2] += (FIXED_A) in[hop + 5] * consts[hop + 5];
+               t1[3] += (FIXED_A) in[hop + 6] * consts[hop + 6];
+               t1[3] += (FIXED_A) in[hop + 7] * consts[hop + 7];
+       }
+
+       /* scaling */
+       t2[0] = t1[0] >> SBC_PROTO_FIXED4_SCALE;
+       t2[1] = t1[1] >> SBC_PROTO_FIXED4_SCALE;
+       t2[2] = t1[2] >> SBC_PROTO_FIXED4_SCALE;
+       t2[3] = t1[3] >> SBC_PROTO_FIXED4_SCALE;
+
+       /* do the cos transform */
+       t1[0]  = (FIXED_A) t2[0] * consts[40 + 0];
+       t1[0] += (FIXED_A) t2[1] * consts[40 + 1];
+       t1[1]  = (FIXED_A) t2[0] * consts[40 + 2];
+       t1[1] += (FIXED_A) t2[1] * consts[40 + 3];
+       t1[2]  = (FIXED_A) t2[0] * consts[40 + 4];
+       t1[2] += (FIXED_A) t2[1] * consts[40 + 5];
+       t1[3]  = (FIXED_A) t2[0] * consts[40 + 6];
+       t1[3] += (FIXED_A) t2[1] * consts[40 + 7];
+
+       t1[0] += (FIXED_A) t2[2] * consts[40 + 8];
+       t1[0] += (FIXED_A) t2[3] * consts[40 + 9];
+       t1[1] += (FIXED_A) t2[2] * consts[40 + 10];
+       t1[1] += (FIXED_A) t2[3] * consts[40 + 11];
+       t1[2] += (FIXED_A) t2[2] * consts[40 + 12];
+       t1[2] += (FIXED_A) t2[3] * consts[40 + 13];
+       t1[3] += (FIXED_A) t2[2] * consts[40 + 14];
+       t1[3] += (FIXED_A) t2[3] * consts[40 + 15];
+
+       out[0] = t1[0] >>
+               (SBC_COS_TABLE_FIXED4_SCALE - SCALE_OUT_BITS);
+       out[1] = t1[1] >>
+               (SBC_COS_TABLE_FIXED4_SCALE - SCALE_OUT_BITS);
+       out[2] = t1[2] >>
+               (SBC_COS_TABLE_FIXED4_SCALE - SCALE_OUT_BITS);
+       out[3] = t1[3] >>
+               (SBC_COS_TABLE_FIXED4_SCALE - SCALE_OUT_BITS);
+}
+
+static inline void sbc_analyze_eight_simd(const int16_t *in, int32_t *out,
+                                                       const FIXED_T *consts)
+{
+       FIXED_A t1[8];
+       FIXED_T t2[8];
+       int i, hop;
+
+       /* rounding coefficient */
+       t1[0] = t1[1] = t1[2] = t1[3] = t1[4] = t1[5] = t1[6] = t1[7] =
+               (FIXED_A) 1 << (SBC_PROTO_FIXED8_SCALE-1);
+
+       /* low pass polyphase filter */
+       for (hop = 0; hop < 80; hop += 16) {
+               t1[0] += (FIXED_A) in[hop] * consts[hop];
+               t1[0] += (FIXED_A) in[hop + 1] * consts[hop + 1];
+               t1[1] += (FIXED_A) in[hop + 2] * consts[hop + 2];
+               t1[1] += (FIXED_A) in[hop + 3] * consts[hop + 3];
+               t1[2] += (FIXED_A) in[hop + 4] * consts[hop + 4];
+               t1[2] += (FIXED_A) in[hop + 5] * consts[hop + 5];
+               t1[3] += (FIXED_A) in[hop + 6] * consts[hop + 6];
+               t1[3] += (FIXED_A) in[hop + 7] * consts[hop + 7];
+               t1[4] += (FIXED_A) in[hop + 8] * consts[hop + 8];
+               t1[4] += (FIXED_A) in[hop + 9] * consts[hop + 9];
+               t1[5] += (FIXED_A) in[hop + 10] * consts[hop + 10];
+               t1[5] += (FIXED_A) in[hop + 11] * consts[hop + 11];
+               t1[6] += (FIXED_A) in[hop + 12] * consts[hop + 12];
+               t1[6] += (FIXED_A) in[hop + 13] * consts[hop + 13];
+               t1[7] += (FIXED_A) in[hop + 14] * consts[hop + 14];
+               t1[7] += (FIXED_A) in[hop + 15] * consts[hop + 15];
+       }
+
+       /* scaling */
+       t2[0] = t1[0] >> SBC_PROTO_FIXED8_SCALE;
+       t2[1] = t1[1] >> SBC_PROTO_FIXED8_SCALE;
+       t2[2] = t1[2] >> SBC_PROTO_FIXED8_SCALE;
+       t2[3] = t1[3] >> SBC_PROTO_FIXED8_SCALE;
+       t2[4] = t1[4] >> SBC_PROTO_FIXED8_SCALE;
+       t2[5] = t1[5] >> SBC_PROTO_FIXED8_SCALE;
+       t2[6] = t1[6] >> SBC_PROTO_FIXED8_SCALE;
+       t2[7] = t1[7] >> SBC_PROTO_FIXED8_SCALE;
+
+
+       /* do the cos transform */
+       t1[0] = t1[1] = t1[2] = t1[3] = t1[4] = t1[5] = t1[6] = t1[7] = 0;
+
+       for (i = 0; i < 4; i++) {
+               t1[0] += (FIXED_A) t2[i * 2 + 0] * consts[80 + i * 16 + 0];
+               t1[0] += (FIXED_A) t2[i * 2 + 1] * consts[80 + i * 16 + 1];
+               t1[1] += (FIXED_A) t2[i * 2 + 0] * consts[80 + i * 16 + 2];
+               t1[1] += (FIXED_A) t2[i * 2 + 1] * consts[80 + i * 16 + 3];
+               t1[2] += (FIXED_A) t2[i * 2 + 0] * consts[80 + i * 16 + 4];
+               t1[2] += (FIXED_A) t2[i * 2 + 1] * consts[80 + i * 16 + 5];
+               t1[3] += (FIXED_A) t2[i * 2 + 0] * consts[80 + i * 16 + 6];
+               t1[3] += (FIXED_A) t2[i * 2 + 1] * consts[80 + i * 16 + 7];
+               t1[4] += (FIXED_A) t2[i * 2 + 0] * consts[80 + i * 16 + 8];
+               t1[4] += (FIXED_A) t2[i * 2 + 1] * consts[80 + i * 16 + 9];
+               t1[5] += (FIXED_A) t2[i * 2 + 0] * consts[80 + i * 16 + 10];
+               t1[5] += (FIXED_A) t2[i * 2 + 1] * consts[80 + i * 16 + 11];
+               t1[6] += (FIXED_A) t2[i * 2 + 0] * consts[80 + i * 16 + 12];
+               t1[6] += (FIXED_A) t2[i * 2 + 1] * consts[80 + i * 16 + 13];
+               t1[7] += (FIXED_A) t2[i * 2 + 0] * consts[80 + i * 16 + 14];
+               t1[7] += (FIXED_A) t2[i * 2 + 1] * consts[80 + i * 16 + 15];
+       }
+
+       for (i = 0; i < 8; i++)
+               out[i] = t1[i] >>
+                       (SBC_COS_TABLE_FIXED8_SCALE - SCALE_OUT_BITS);
+}
+
+static inline void sbc_analyze_4b_4s_simd(int16_t *x,
+                                               int32_t *out, int out_stride)
+{
+       /* Analyze blocks */
+       sbc_analyze_four_simd(x + 12, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       sbc_analyze_four_simd(x + 8, out, analysis_consts_fixed4_simd_even);
+       out += out_stride;
+       sbc_analyze_four_simd(x + 4, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       sbc_analyze_four_simd(x + 0, out, analysis_consts_fixed4_simd_even);
+}
+
+static inline void sbc_analyze_4b_8s_simd(int16_t *x,
+                                         int32_t *out, int out_stride)
+{
+       /* Analyze blocks */
+       sbc_analyze_eight_simd(x + 24, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       sbc_analyze_eight_simd(x + 16, out, analysis_consts_fixed8_simd_even);
+       out += out_stride;
+       sbc_analyze_eight_simd(x + 8, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       sbc_analyze_eight_simd(x + 0, out, analysis_consts_fixed8_simd_even);
+}
+
+static inline int16_t unaligned16_be(const uint8_t *ptr)
+{
+       return (int16_t) ((ptr[0] << 8) | ptr[1]);
+}
+
+static inline int16_t unaligned16_le(const uint8_t *ptr)
+{
+       return (int16_t) (ptr[0] | (ptr[1] << 8));
+}
+
+/*
+ * Internal helper functions for input data processing. In order to get
+ * optimal performance, it is important to have "nsamples", "nchannels"
+ * and "big_endian" arguments used with this inline function as compile
+ * time constants.
+ */
+
+static SBC_ALWAYS_INLINE int sbc_encoder_process_input_s4_internal(
+       int position,
+       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+       int nsamples, int nchannels, int big_endian)
+{
+       /* handle X buffer wraparound */
+       if (position < nsamples) {
+               if (nchannels > 0)
+                       memcpy(&X[0][SBC_X_BUFFER_SIZE - 40], &X[0][position],
+                                                       36 * sizeof(int16_t));
+               if (nchannels > 1)
+                       memcpy(&X[1][SBC_X_BUFFER_SIZE - 40], &X[1][position],
+                                                       36 * sizeof(int16_t));
+               position = SBC_X_BUFFER_SIZE - 40;
+       }
+
+       #define PCM(i) (big_endian ? \
+               unaligned16_be(pcm + (i) * 2) : unaligned16_le(pcm + (i) * 2))
+
+       /* copy/permutate audio samples */
+       while ((nsamples -= 8) >= 0) {
+               position -= 8;
+               if (nchannels > 0) {
+                       int16_t *x = &X[0][position];
+                       x[0]  = PCM(0 + 7 * nchannels);
+                       x[1]  = PCM(0 + 3 * nchannels);
+                       x[2]  = PCM(0 + 6 * nchannels);
+                       x[3]  = PCM(0 + 4 * nchannels);
+                       x[4]  = PCM(0 + 0 * nchannels);
+                       x[5]  = PCM(0 + 2 * nchannels);
+                       x[6]  = PCM(0 + 1 * nchannels);
+                       x[7]  = PCM(0 + 5 * nchannels);
+               }
+               if (nchannels > 1) {
+                       int16_t *x = &X[1][position];
+                       x[0]  = PCM(1 + 7 * nchannels);
+                       x[1]  = PCM(1 + 3 * nchannels);
+                       x[2]  = PCM(1 + 6 * nchannels);
+                       x[3]  = PCM(1 + 4 * nchannels);
+                       x[4]  = PCM(1 + 0 * nchannels);
+                       x[5]  = PCM(1 + 2 * nchannels);
+                       x[6]  = PCM(1 + 1 * nchannels);
+                       x[7]  = PCM(1 + 5 * nchannels);
+               }
+               pcm += 16 * nchannels;
+       }
+       #undef PCM
+
+       return position;
+}
+
+static SBC_ALWAYS_INLINE int sbc_encoder_process_input_s8_internal(
+       int position,
+       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+       int nsamples, int nchannels, int big_endian)
+{
+       /* handle X buffer wraparound */
+       if (position < nsamples) {
+               if (nchannels > 0)
+                       memcpy(&X[0][SBC_X_BUFFER_SIZE - 72], &X[0][position],
+                                                       72 * sizeof(int16_t));
+               if (nchannels > 1)
+                       memcpy(&X[1][SBC_X_BUFFER_SIZE - 72], &X[1][position],
+                                                       72 * sizeof(int16_t));
+               position = SBC_X_BUFFER_SIZE - 72;
+       }
+
+       #define PCM(i) (big_endian ? \
+               unaligned16_be(pcm + (i) * 2) : unaligned16_le(pcm + (i) * 2))
+
+       /* copy/permutate audio samples */
+       while ((nsamples -= 16) >= 0) {
+               position -= 16;
+               if (nchannels > 0) {
+                       int16_t *x = &X[0][position];
+                       x[0]  = PCM(0 + 15 * nchannels);
+                       x[1]  = PCM(0 + 7 * nchannels);
+                       x[2]  = PCM(0 + 14 * nchannels);
+                       x[3]  = PCM(0 + 8 * nchannels);
+                       x[4]  = PCM(0 + 13 * nchannels);
+                       x[5]  = PCM(0 + 9 * nchannels);
+                       x[6]  = PCM(0 + 12 * nchannels);
+                       x[7]  = PCM(0 + 10 * nchannels);
+                       x[8]  = PCM(0 + 11 * nchannels);
+                       x[9]  = PCM(0 + 3 * nchannels);
+                       x[10] = PCM(0 + 6 * nchannels);
+                       x[11] = PCM(0 + 0 * nchannels);
+                       x[12] = PCM(0 + 5 * nchannels);
+                       x[13] = PCM(0 + 1 * nchannels);
+                       x[14] = PCM(0 + 4 * nchannels);
+                       x[15] = PCM(0 + 2 * nchannels);
+               }
+               if (nchannels > 1) {
+                       int16_t *x = &X[1][position];
+                       x[0]  = PCM(1 + 15 * nchannels);
+                       x[1]  = PCM(1 + 7 * nchannels);
+                       x[2]  = PCM(1 + 14 * nchannels);
+                       x[3]  = PCM(1 + 8 * nchannels);
+                       x[4]  = PCM(1 + 13 * nchannels);
+                       x[5]  = PCM(1 + 9 * nchannels);
+                       x[6]  = PCM(1 + 12 * nchannels);
+                       x[7]  = PCM(1 + 10 * nchannels);
+                       x[8]  = PCM(1 + 11 * nchannels);
+                       x[9]  = PCM(1 + 3 * nchannels);
+                       x[10] = PCM(1 + 6 * nchannels);
+                       x[11] = PCM(1 + 0 * nchannels);
+                       x[12] = PCM(1 + 5 * nchannels);
+                       x[13] = PCM(1 + 1 * nchannels);
+                       x[14] = PCM(1 + 4 * nchannels);
+                       x[15] = PCM(1 + 2 * nchannels);
+               }
+               pcm += 32 * nchannels;
+       }
+       #undef PCM
+
+       return position;
+}
+
+/*
+ * Input data processing functions. The data is endian converted if needed,
+ * channels are deintrleaved and audio samples are reordered for use in
+ * SIMD-friendly analysis filter function. The results are put into "X"
+ * array, getting appended to the previous data (or it is better to say
+ * prepended, as the buffer is filled from top to bottom). Old data is
+ * discarded when neededed, but availability of (10 * nrof_subbands)
+ * contiguous samples is always guaranteed for the input to the analysis
+ * filter. This is achieved by copying a sufficient part of old data
+ * to the top of the buffer on buffer wraparound.
+ */
+
+static int sbc_enc_process_input_4s_le(int position,
+               const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+               int nsamples, int nchannels)
+{
+       if (nchannels > 1)
+               return sbc_encoder_process_input_s4_internal(
+                       position, pcm, X, nsamples, 2, 0);
+       else
+               return sbc_encoder_process_input_s4_internal(
+                       position, pcm, X, nsamples, 1, 0);
+}
+
+static int sbc_enc_process_input_4s_be(int position,
+               const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+               int nsamples, int nchannels)
+{
+       if (nchannels > 1)
+               return sbc_encoder_process_input_s4_internal(
+                       position, pcm, X, nsamples, 2, 1);
+       else
+               return sbc_encoder_process_input_s4_internal(
+                       position, pcm, X, nsamples, 1, 1);
+}
+
+static int sbc_enc_process_input_8s_le(int position,
+               const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+               int nsamples, int nchannels)
+{
+       if (nchannels > 1)
+               return sbc_encoder_process_input_s8_internal(
+                       position, pcm, X, nsamples, 2, 0);
+       else
+               return sbc_encoder_process_input_s8_internal(
+                       position, pcm, X, nsamples, 1, 0);
+}
+
+static int sbc_enc_process_input_8s_be(int position,
+               const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+               int nsamples, int nchannels)
+{
+       if (nchannels > 1)
+               return sbc_encoder_process_input_s8_internal(
+                       position, pcm, X, nsamples, 2, 1);
+       else
+               return sbc_encoder_process_input_s8_internal(
+                       position, pcm, X, nsamples, 1, 1);
+}
+
+/* Supplementary function to count the number of leading zeros */
+
+static inline int sbc_clz(uint32_t x)
+{
+#ifdef __GNUC__
+       return __builtin_clz(x);
+#else
+       /* TODO: this should be replaced with something better if good
+        * performance is wanted when using compilers other than gcc */
+       int cnt = 0;
+       while (x) {
+               cnt++;
+               x >>= 1;
+       }
+       return 32 - cnt;
+#endif
+}
+
+static void sbc_calc_scalefactors(
+       int32_t sb_sample_f[16][2][8],
+       uint32_t scale_factor[2][8],
+       int blocks, int channels, int subbands)
+{
+       int ch, sb, blk;
+       for (ch = 0; ch < channels; ch++) {
+               for (sb = 0; sb < subbands; sb++) {
+                       uint32_t x = 1 << SCALE_OUT_BITS;
+                       for (blk = 0; blk < blocks; blk++) {
+                               int32_t tmp = fabs(sb_sample_f[blk][ch][sb]);
+                               if (tmp != 0)
+                                       x |= tmp - 1;
+                       }
+                       scale_factor[ch][sb] = (31 - SCALE_OUT_BITS) -
+                               sbc_clz(x);
+               }
+       }
+}
+
+static int sbc_calc_scalefactors_j(
+       int32_t sb_sample_f[16][2][8],
+       uint32_t scale_factor[2][8],
+       int blocks, int subbands)
+{
+       int blk, joint = 0;
+       int32_t tmp0, tmp1;
+       uint32_t x, y;
+
+       /* last subband does not use joint stereo */
+       int sb = subbands - 1;
+       x = 1 << SCALE_OUT_BITS;
+       y = 1 << SCALE_OUT_BITS;
+       for (blk = 0; blk < blocks; blk++) {
+               tmp0 = fabs(sb_sample_f[blk][0][sb]);
+               tmp1 = fabs(sb_sample_f[blk][1][sb]);
+               if (tmp0 != 0)
+                       x |= tmp0 - 1;
+               if (tmp1 != 0)
+                       y |= tmp1 - 1;
+       }
+       scale_factor[0][sb] = (31 - SCALE_OUT_BITS) - sbc_clz(x);
+       scale_factor[1][sb] = (31 - SCALE_OUT_BITS) - sbc_clz(y);
+
+       /* the rest of subbands can use joint stereo */
+       while (--sb >= 0) {
+               int32_t sb_sample_j[16][2];
+               x = 1 << SCALE_OUT_BITS;
+               y = 1 << SCALE_OUT_BITS;
+               for (blk = 0; blk < blocks; blk++) {
+                       tmp0 = sb_sample_f[blk][0][sb];
+                       tmp1 = sb_sample_f[blk][1][sb];
+                       sb_sample_j[blk][0] = ASR(tmp0, 1) + ASR(tmp1, 1);
+                       sb_sample_j[blk][1] = ASR(tmp0, 1) - ASR(tmp1, 1);
+                       tmp0 = fabs(tmp0);
+                       tmp1 = fabs(tmp1);
+                       if (tmp0 != 0)
+                               x |= tmp0 - 1;
+                       if (tmp1 != 0)
+                               y |= tmp1 - 1;
+               }
+               scale_factor[0][sb] = (31 - SCALE_OUT_BITS) -
+                       sbc_clz(x);
+               scale_factor[1][sb] = (31 - SCALE_OUT_BITS) -
+                       sbc_clz(y);
+               x = 1 << SCALE_OUT_BITS;
+               y = 1 << SCALE_OUT_BITS;
+               for (blk = 0; blk < blocks; blk++) {
+                       tmp0 = fabs(sb_sample_j[blk][0]);
+                       tmp1 = fabs(sb_sample_j[blk][1]);
+                       if (tmp0 != 0)
+                               x |= tmp0 - 1;
+                       if (tmp1 != 0)
+                               y |= tmp1 - 1;
+               }
+               x = (31 - SCALE_OUT_BITS) - sbc_clz(x);
+               y = (31 - SCALE_OUT_BITS) - sbc_clz(y);
+
+               /* decide whether to use joint stereo for this subband */
+               if ((scale_factor[0][sb] + scale_factor[1][sb]) > x + y) {
+                       joint |= 1 << (subbands - 1 - sb);
+                       scale_factor[0][sb] = x;
+                       scale_factor[1][sb] = y;
+                       for (blk = 0; blk < blocks; blk++) {
+                               sb_sample_f[blk][0][sb] = sb_sample_j[blk][0];
+                               sb_sample_f[blk][1][sb] = sb_sample_j[blk][1];
+                       }
+               }
+       }
+
+       /* bitmask with the information about subbands using joint stereo */
+       return joint;
+}
+
+/*
+ * Detect CPU features and setup function pointers
+ */
+void sbc_init_primitives(struct sbc_encoder_state *state)
+{
+       /* Default implementation for analyze functions */
+       state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_simd;
+       state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_simd;
+
+       /* Default implementation for input reordering / deinterleaving */
+       state->sbc_enc_process_input_4s_le = sbc_enc_process_input_4s_le;
+       state->sbc_enc_process_input_4s_be = sbc_enc_process_input_4s_be;
+       state->sbc_enc_process_input_8s_le = sbc_enc_process_input_8s_le;
+       state->sbc_enc_process_input_8s_be = sbc_enc_process_input_8s_be;
+
+       /* Default implementation for scale factors calculation */
+       state->sbc_calc_scalefactors = sbc_calc_scalefactors;
+       state->sbc_calc_scalefactors_j = sbc_calc_scalefactors_j;
+       state->implementation_info = "Generic C";
+
+       /* X86/AMD64 optimizations */
+#ifdef SBC_BUILD_WITH_MMX_SUPPORT
+       sbc_init_primitives_mmx(state);
+#endif
+
+       /* ARM optimizations */
+#ifdef SBC_BUILD_WITH_ARMV6_SUPPORT
+       sbc_init_primitives_armv6(state);
+#endif
+#ifdef SBC_BUILD_WITH_IWMMXT_SUPPORT
+       sbc_init_primitives_iwmmxt(state);
+#endif
+#ifdef SBC_BUILD_WITH_NEON_SUPPORT
+       sbc_init_primitives_neon(state);
+#endif
+}
diff --git a/sbc/sbc_primitives.h b/sbc/sbc_primitives.h
new file mode 100644 (file)
index 0000000..3fec8d5
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 __SBC_PRIMITIVES_H
+#define __SBC_PRIMITIVES_H
+
+#define SCALE_OUT_BITS 15
+#define SBC_X_BUFFER_SIZE 328
+
+#ifdef __GNUC__
+#define SBC_ALWAYS_INLINE __attribute__((always_inline))
+#else
+#define SBC_ALWAYS_INLINE inline
+#endif
+
+struct sbc_encoder_state {
+       int position;
+       int16_t SBC_ALIGNED X[2][SBC_X_BUFFER_SIZE];
+       /* Polyphase analysis filter for 4 subbands configuration,
+        * it handles 4 blocks at once */
+       void (*sbc_analyze_4b_4s)(int16_t *x, int32_t *out, int out_stride);
+       /* Polyphase analysis filter for 8 subbands configuration,
+        * it handles 4 blocks at once */
+       void (*sbc_analyze_4b_8s)(int16_t *x, int32_t *out, int out_stride);
+       /* Process input data (deinterleave, endian conversion, reordering),
+        * depending on the number of subbands and input data byte order */
+       int (*sbc_enc_process_input_4s_le)(int position,
+                       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+                       int nsamples, int nchannels);
+       int (*sbc_enc_process_input_4s_be)(int position,
+                       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+                       int nsamples, int nchannels);
+       int (*sbc_enc_process_input_8s_le)(int position,
+                       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+                       int nsamples, int nchannels);
+       int (*sbc_enc_process_input_8s_be)(int position,
+                       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+                       int nsamples, int nchannels);
+       /* Scale factors calculation */
+       void (*sbc_calc_scalefactors)(int32_t sb_sample_f[16][2][8],
+                       uint32_t scale_factor[2][8],
+                       int blocks, int channels, int subbands);
+       /* Scale factors calculation with joint stereo support */
+       int (*sbc_calc_scalefactors_j)(int32_t sb_sample_f[16][2][8],
+                       uint32_t scale_factor[2][8],
+                       int blocks, int subbands);
+       const char *implementation_info;
+};
+
+/*
+ * Initialize pointers to the functions which are the basic "building bricks"
+ * of SBC codec. Best implementation is selected based on target CPU
+ * capabilities.
+ */
+void sbc_init_primitives(struct sbc_encoder_state *encoder_state);
+
+#endif
diff --git a/sbc/sbc_primitives_armv6.c b/sbc/sbc_primitives_armv6.c
new file mode 100644 (file)
index 0000000..9586098
--- /dev/null
@@ -0,0 +1,299 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 <stdint.h>
+#include <limits.h>
+#include "sbc.h"
+#include "sbc_math.h"
+#include "sbc_tables.h"
+
+#include "sbc_primitives_armv6.h"
+
+/*
+ * ARMv6 optimizations. The instructions are scheduled for ARM11 pipeline.
+ */
+
+#ifdef SBC_BUILD_WITH_ARMV6_SUPPORT
+
+static void __attribute__((naked)) sbc_analyze_four_armv6()
+{
+       /* r0 = in, r1 = out, r2 = consts */
+       asm volatile (
+               "push   {r1, r4-r7, lr}\n"
+               "push   {r8-r11}\n"
+               "ldrd   r4,  r5,  [r0, #0]\n"
+               "ldrd   r6,  r7,  [r2, #0]\n"
+               "ldrd   r8,  r9,  [r0, #16]\n"
+               "ldrd   r10, r11, [r2, #16]\n"
+               "mov    r14, #0x8000\n"
+               "smlad  r3,  r4,  r6,  r14\n"
+               "smlad  r12, r5,  r7,  r14\n"
+               "ldrd   r4,  r5,  [r0, #32]\n"
+               "ldrd   r6,  r7,  [r2, #32]\n"
+               "smlad  r3,  r8,  r10, r3\n"
+               "smlad  r12, r9,  r11, r12\n"
+               "ldrd   r8,  r9,  [r0, #48]\n"
+               "ldrd   r10, r11, [r2, #48]\n"
+               "smlad  r3,  r4,  r6,  r3\n"
+               "smlad  r12, r5,  r7,  r12\n"
+               "ldrd   r4,  r5,  [r0, #64]\n"
+               "ldrd   r6,  r7,  [r2, #64]\n"
+               "smlad  r3,  r8,  r10, r3\n"
+               "smlad  r12, r9,  r11, r12\n"
+               "ldrd   r8,  r9,  [r0, #8]\n"
+               "ldrd   r10, r11, [r2, #8]\n"
+               "smlad  r3,  r4,  r6,  r3\n"      /* t1[0] is done */
+               "smlad  r12, r5,  r7,  r12\n"     /* t1[1] is done */
+               "ldrd   r4,  r5,  [r0, #24]\n"
+               "ldrd   r6,  r7,  [r2, #24]\n"
+               "pkhtb  r3,  r12, r3, asr #16\n"  /* combine t1[0] and t1[1] */
+               "smlad  r12, r8,  r10, r14\n"
+               "smlad  r14, r9,  r11, r14\n"
+               "ldrd   r8,  r9,  [r0, #40]\n"
+               "ldrd   r10, r11, [r2, #40]\n"
+               "smlad  r12, r4,  r6,  r12\n"
+               "smlad  r14, r5,  r7,  r14\n"
+               "ldrd   r4,  r5,  [r0, #56]\n"
+               "ldrd   r6,  r7,  [r2, #56]\n"
+               "smlad  r12, r8,  r10, r12\n"
+               "smlad  r14, r9,  r11, r14\n"
+               "ldrd   r8,  r9,  [r0, #72]\n"
+               "ldrd   r10, r11, [r2, #72]\n"
+               "smlad  r12, r4,  r6,  r12\n"
+               "smlad  r14, r5,  r7,  r14\n"
+               "ldrd   r4,  r5,  [r2, #80]\n"    /* start loading cos table */
+               "smlad  r12, r8,  r10, r12\n"     /* t1[2] is done */
+               "smlad  r14, r9,  r11, r14\n"     /* t1[3] is done */
+               "ldrd   r6,  r7,  [r2, #88]\n"
+               "ldrd   r8,  r9,  [r2, #96]\n"
+               "ldrd   r10, r11, [r2, #104]\n"   /* cos table fully loaded */
+               "pkhtb  r12, r14, r12, asr #16\n" /* combine t1[2] and t1[3] */
+               "smuad  r4,  r3,  r4\n"
+               "smuad  r5,  r3,  r5\n"
+               "smlad  r4,  r12, r8,  r4\n"
+               "smlad  r5,  r12, r9,  r5\n"
+               "smuad  r6,  r3,  r6\n"
+               "smuad  r7,  r3,  r7\n"
+               "smlad  r6,  r12, r10, r6\n"
+               "smlad  r7,  r12, r11, r7\n"
+               "pop    {r8-r11}\n"
+               "stmia  r1, {r4, r5, r6, r7}\n"
+               "pop    {r1, r4-r7, pc}\n"
+       );
+}
+
+#define sbc_analyze_four(in, out, consts) \
+       ((void (*)(int16_t *, int32_t *, const FIXED_T*)) \
+               sbc_analyze_four_armv6)((in), (out), (consts))
+
+static void __attribute__((naked)) sbc_analyze_eight_armv6()
+{
+       /* r0 = in, r1 = out, r2 = consts */
+       asm volatile (
+               "push   {r1, r4-r7, lr}\n"
+               "push   {r8-r11}\n"
+               "ldrd   r4,  r5,  [r0, #24]\n"
+               "ldrd   r6,  r7,  [r2, #24]\n"
+               "ldrd   r8,  r9,  [r0, #56]\n"
+               "ldrd   r10, r11, [r2, #56]\n"
+               "mov    r14, #0x8000\n"
+               "smlad  r3,  r4,  r6,  r14\n"
+               "smlad  r12, r5,  r7,  r14\n"
+               "ldrd   r4,  r5,  [r0, #88]\n"
+               "ldrd   r6,  r7,  [r2, #88]\n"
+               "smlad  r3,  r8,  r10, r3\n"
+               "smlad  r12, r9,  r11, r12\n"
+               "ldrd   r8,  r9,  [r0, #120]\n"
+               "ldrd   r10, r11, [r2, #120]\n"
+               "smlad  r3,  r4,  r6,  r3\n"
+               "smlad  r12, r5,  r7,  r12\n"
+               "ldrd   r4,  r5,  [r0, #152]\n"
+               "ldrd   r6,  r7,  [r2, #152]\n"
+               "smlad  r3,  r8,  r10, r3\n"
+               "smlad  r12, r9,  r11, r12\n"
+               "ldrd   r8,  r9,  [r0, #16]\n"
+               "ldrd   r10, r11, [r2, #16]\n"
+               "smlad  r3,  r4,  r6,  r3\n"      /* t1[6] is done */
+               "smlad  r12, r5,  r7,  r12\n"     /* t1[7] is done */
+               "ldrd   r4,  r5,  [r0, #48]\n"
+               "ldrd   r6,  r7,  [r2, #48]\n"
+               "pkhtb  r3,  r12, r3, asr #16\n"  /* combine t1[6] and t1[7] */
+               "str    r3,  [sp, #-4]!\n"        /* save to stack */
+               "smlad  r3,  r8,  r10, r14\n"
+               "smlad  r12, r9,  r11, r14\n"
+               "ldrd   r8,  r9,  [r0, #80]\n"
+               "ldrd   r10, r11, [r2, #80]\n"
+               "smlad  r3,  r4,  r6,  r3\n"
+               "smlad  r12, r5,  r7,  r12\n"
+               "ldrd   r4,  r5,  [r0, #112]\n"
+               "ldrd   r6,  r7,  [r2, #112]\n"
+               "smlad  r3,  r8,  r10, r3\n"
+               "smlad  r12, r9,  r11, r12\n"
+               "ldrd   r8,  r9,  [r0, #144]\n"
+               "ldrd   r10, r11, [r2, #144]\n"
+               "smlad  r3,  r4,  r6,  r3\n"
+               "smlad  r12, r5,  r7,  r12\n"
+               "ldrd   r4,  r5,  [r0, #0]\n"
+               "ldrd   r6,  r7,  [r2, #0]\n"
+               "smlad  r3,  r8,  r10, r3\n"      /* t1[4] is done */
+               "smlad  r12, r9,  r11, r12\n"     /* t1[5] is done */
+               "ldrd   r8,  r9,  [r0, #32]\n"
+               "ldrd   r10, r11, [r2, #32]\n"
+               "pkhtb  r3,  r12, r3, asr #16\n"  /* combine t1[4] and t1[5] */
+               "str    r3,  [sp, #-4]!\n"        /* save to stack */
+               "smlad  r3,  r4,  r6,  r14\n"
+               "smlad  r12, r5,  r7,  r14\n"
+               "ldrd   r4,  r5,  [r0, #64]\n"
+               "ldrd   r6,  r7,  [r2, #64]\n"
+               "smlad  r3,  r8,  r10, r3\n"
+               "smlad  r12, r9,  r11, r12\n"
+               "ldrd   r8,  r9,  [r0, #96]\n"
+               "ldrd   r10, r11, [r2, #96]\n"
+               "smlad  r3,  r4,  r6,  r3\n"
+               "smlad  r12, r5,  r7,  r12\n"
+               "ldrd   r4,  r5,  [r0, #128]\n"
+               "ldrd   r6,  r7,  [r2, #128]\n"
+               "smlad  r3,  r8,  r10, r3\n"
+               "smlad  r12, r9,  r11, r12\n"
+               "ldrd   r8,  r9,  [r0, #8]\n"
+               "ldrd   r10, r11, [r2, #8]\n"
+               "smlad  r3,  r4,  r6,  r3\n"      /* t1[0] is done */
+               "smlad  r12, r5,  r7,  r12\n"     /* t1[1] is done */
+               "ldrd   r4,  r5,  [r0, #40]\n"
+               "ldrd   r6,  r7,  [r2, #40]\n"
+               "pkhtb  r3,  r12, r3, asr #16\n"  /* combine t1[0] and t1[1] */
+               "smlad  r12, r8,  r10, r14\n"
+               "smlad  r14, r9,  r11, r14\n"
+               "ldrd   r8,  r9,  [r0, #72]\n"
+               "ldrd   r10, r11, [r2, #72]\n"
+               "smlad  r12, r4,  r6,  r12\n"
+               "smlad  r14, r5,  r7,  r14\n"
+               "ldrd   r4,  r5,  [r0, #104]\n"
+               "ldrd   r6,  r7,  [r2, #104]\n"
+               "smlad  r12, r8,  r10, r12\n"
+               "smlad  r14, r9,  r11, r14\n"
+               "ldrd   r8,  r9,  [r0, #136]\n"
+               "ldrd   r10, r11, [r2, #136]!\n"
+               "smlad  r12, r4,  r6,  r12\n"
+               "smlad  r14, r5,  r7,  r14\n"
+               "ldrd   r4,  r5,  [r2, #(160 - 136 + 0)]\n"
+               "smlad  r12, r8,  r10, r12\n"     /* t1[2] is done */
+               "smlad  r14, r9,  r11, r14\n"     /* t1[3] is done */
+               "ldrd   r6,  r7,  [r2, #(160 - 136 + 8)]\n"
+               "smuad  r4,  r3,  r4\n"
+               "smuad  r5,  r3,  r5\n"
+               "pkhtb  r12, r14, r12, asr #16\n" /* combine t1[2] and t1[3] */
+                                                 /* r3  = t2[0:1] */
+                                                 /* r12 = t2[2:3] */
+               "pop    {r0, r14}\n"              /* t2[4:5], t2[6:7] */
+               "ldrd   r8,  r9,  [r2, #(160 - 136 + 32)]\n"
+               "smuad  r6,  r3,  r6\n"
+               "smuad  r7,  r3,  r7\n"
+               "ldrd   r10, r11, [r2, #(160 - 136 + 40)]\n"
+               "smlad  r4,  r12, r8,  r4\n"
+               "smlad  r5,  r12, r9,  r5\n"
+               "ldrd   r8,  r9,  [r2, #(160 - 136 + 64)]\n"
+               "smlad  r6,  r12, r10, r6\n"
+               "smlad  r7,  r12, r11, r7\n"
+               "ldrd   r10, r11, [r2, #(160 - 136 + 72)]\n"
+               "smlad  r4,  r0,  r8,  r4\n"
+               "smlad  r5,  r0,  r9,  r5\n"
+               "ldrd   r8,  r9,  [r2, #(160 - 136 + 96)]\n"
+               "smlad  r6,  r0,  r10, r6\n"
+               "smlad  r7,  r0,  r11, r7\n"
+               "ldrd   r10, r11, [r2, #(160 - 136 + 104)]\n"
+               "smlad  r4,  r14, r8,  r4\n"
+               "smlad  r5,  r14, r9,  r5\n"
+               "ldrd   r8,  r9,  [r2, #(160 - 136 + 16 + 0)]\n"
+               "smlad  r6,  r14, r10, r6\n"
+               "smlad  r7,  r14, r11, r7\n"
+               "ldrd   r10, r11, [r2, #(160 - 136 + 16 + 8)]\n"
+               "stmia  r1!, {r4, r5}\n"
+               "smuad  r4,  r3,  r8\n"
+               "smuad  r5,  r3,  r9\n"
+               "ldrd   r8,  r9,  [r2, #(160 - 136 + 16 + 32)]\n"
+               "stmia  r1!, {r6, r7}\n"
+               "smuad  r6,  r3,  r10\n"
+               "smuad  r7,  r3,  r11\n"
+               "ldrd   r10, r11, [r2, #(160 - 136 + 16 + 40)]\n"
+               "smlad  r4,  r12, r8,  r4\n"
+               "smlad  r5,  r12, r9,  r5\n"
+               "ldrd   r8,  r9,  [r2, #(160 - 136 + 16 + 64)]\n"
+               "smlad  r6,  r12, r10, r6\n"
+               "smlad  r7,  r12, r11, r7\n"
+               "ldrd   r10, r11, [r2, #(160 - 136 + 16 + 72)]\n"
+               "smlad  r4,  r0,  r8,  r4\n"
+               "smlad  r5,  r0,  r9,  r5\n"
+               "ldrd   r8,  r9,  [r2, #(160 - 136 + 16 + 96)]\n"
+               "smlad  r6,  r0,  r10, r6\n"
+               "smlad  r7,  r0,  r11, r7\n"
+               "ldrd   r10, r11, [r2, #(160 - 136 + 16 + 104)]\n"
+               "smlad  r4,  r14, r8,  r4\n"
+               "smlad  r5,  r14, r9,  r5\n"
+               "smlad  r6,  r14, r10, r6\n"
+               "smlad  r7,  r14, r11, r7\n"
+               "pop    {r8-r11}\n"
+               "stmia  r1!, {r4, r5, r6, r7}\n"
+               "pop    {r1, r4-r7, pc}\n"
+       );
+}
+
+#define sbc_analyze_eight(in, out, consts) \
+       ((void (*)(int16_t *, int32_t *, const FIXED_T*)) \
+               sbc_analyze_eight_armv6)((in), (out), (consts))
+
+static void sbc_analyze_4b_4s_armv6(int16_t *x, int32_t *out, int out_stride)
+{
+       /* Analyze blocks */
+       sbc_analyze_four(x + 12, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       sbc_analyze_four(x + 8, out, analysis_consts_fixed4_simd_even);
+       out += out_stride;
+       sbc_analyze_four(x + 4, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       sbc_analyze_four(x + 0, out, analysis_consts_fixed4_simd_even);
+}
+
+static void sbc_analyze_4b_8s_armv6(int16_t *x, int32_t *out, int out_stride)
+{
+       /* Analyze blocks */
+       sbc_analyze_eight(x + 24, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       sbc_analyze_eight(x + 16, out, analysis_consts_fixed8_simd_even);
+       out += out_stride;
+       sbc_analyze_eight(x + 8, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       sbc_analyze_eight(x + 0, out, analysis_consts_fixed8_simd_even);
+}
+
+void sbc_init_primitives_armv6(struct sbc_encoder_state *state)
+{
+       state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_armv6;
+       state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_armv6;
+       state->implementation_info = "ARMv6 SIMD";
+}
+
+#endif
diff --git a/sbc/sbc_primitives_armv6.h b/sbc/sbc_primitives_armv6.h
new file mode 100644 (file)
index 0000000..1862aed
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 __SBC_PRIMITIVES_ARMV6_H
+#define __SBC_PRIMITIVES_ARMV6_H
+
+#include "sbc_primitives.h"
+
+#if defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || \
+       defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || \
+       defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) || \
+       defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_7__) || \
+       defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || \
+       defined(__ARM_ARCH_7M__)
+#define SBC_HAVE_ARMV6 1
+#endif
+
+#if !defined(SBC_HIGH_PRECISION) && (SCALE_OUT_BITS == 15) && \
+       defined(__GNUC__) && defined(SBC_HAVE_ARMV6) && \
+       defined(__ARM_EABI__) && !defined(__thumb__) && \
+       !defined(__ARM_NEON__)
+
+#define SBC_BUILD_WITH_ARMV6_SUPPORT
+
+void sbc_init_primitives_armv6(struct sbc_encoder_state *encoder_state);
+
+#endif
+
+#endif
diff --git a/sbc/sbc_primitives_iwmmxt.c b/sbc/sbc_primitives_iwmmxt.c
new file mode 100644 (file)
index 0000000..213967e
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2010 Keith Mok <ek9852@gmail.com>
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 <stdint.h>
+#include <limits.h>
+#include "sbc.h"
+#include "sbc_math.h"
+#include "sbc_tables.h"
+
+#include "sbc_primitives_iwmmxt.h"
+
+/*
+ * IWMMXT optimizations
+ */
+
+#ifdef SBC_BUILD_WITH_IWMMXT_SUPPORT
+
+static inline void sbc_analyze_four_iwmmxt(const int16_t *in, int32_t *out,
+                                       const FIXED_T *consts)
+{
+       asm volatile (
+               "wldrd        wr0, [%0]\n"
+               "tbcstw       wr4, %2\n"
+               "wldrd        wr2, [%1]\n"
+               "wldrd        wr1, [%0, #8]\n"
+               "wldrd        wr3, [%1, #8]\n"
+               "wmadds       wr0, wr2, wr0\n"
+               " wldrd       wr6, [%0, #16]\n"
+               "wmadds       wr1, wr3, wr1\n"
+               " wldrd       wr7, [%0, #24]\n"
+               "waddwss      wr0, wr0, wr4\n"
+               " wldrd       wr8, [%1, #16]\n"
+               "waddwss      wr1, wr1, wr4\n"
+               " wldrd       wr9, [%1, #24]\n"
+               " wmadds      wr6, wr8, wr6\n"
+               "  wldrd      wr2, [%0, #32]\n"
+               " wmadds      wr7, wr9, wr7\n"
+               "  wldrd      wr3, [%0, #40]\n"
+               " waddwss     wr0, wr6, wr0\n"
+               "  wldrd      wr4, [%1, #32]\n"
+               " waddwss     wr1, wr7, wr1\n"
+               "  wldrd      wr5, [%1, #40]\n"
+               "  wmadds     wr2, wr4, wr2\n"
+               "wldrd        wr6, [%0, #48]\n"
+               "  wmadds     wr3, wr5, wr3\n"
+               "wldrd        wr7, [%0, #56]\n"
+               "  waddwss    wr0, wr2, wr0\n"
+               "wldrd        wr8, [%1, #48]\n"
+               "  waddwss    wr1, wr3, wr1\n"
+               "wldrd        wr9, [%1, #56]\n"
+               "wmadds       wr6, wr8, wr6\n"
+               " wldrd       wr2, [%0, #64]\n"
+               "wmadds       wr7, wr9, wr7\n"
+               " wldrd       wr3, [%0, #72]\n"
+               "waddwss      wr0, wr6, wr0\n"
+               " wldrd       wr4, [%1, #64]\n"
+               "waddwss      wr1, wr7, wr1\n"
+               " wldrd       wr5, [%1, #72]\n"
+               " wmadds      wr2, wr4, wr2\n"
+               "tmcr       wcgr0, %4\n"
+               " wmadds      wr3, wr5, wr3\n"
+               " waddwss     wr0, wr2, wr0\n"
+               " waddwss     wr1, wr3, wr1\n"
+               "\n"
+               "wsrawg       wr0, wr0, wcgr0\n"
+               " wldrd       wr4, [%1, #80]\n"
+               "wsrawg       wr1, wr1, wcgr0\n"
+               " wldrd       wr5, [%1, #88]\n"
+               "wpackwss     wr0, wr0, wr0\n"
+               " wldrd       wr6, [%1, #96]\n"
+               "wpackwss     wr1, wr1, wr1\n"
+               "wmadds       wr2, wr5, wr0\n"
+               " wldrd       wr7, [%1, #104]\n"
+               "wmadds       wr0, wr4, wr0\n"
+               "\n"
+               " wmadds      wr3, wr7, wr1\n"
+               " wmadds      wr1, wr6, wr1\n"
+               " waddwss     wr2, wr3, wr2\n"
+               " waddwss     wr0, wr1, wr0\n"
+               "\n"
+               "wstrd        wr0, [%3]\n"
+               "wstrd        wr2, [%3, #8]\n"
+               :
+               : "r" (in), "r" (consts),
+                       "r" (1 << (SBC_PROTO_FIXED4_SCALE - 1)), "r" (out),
+                       "r" (SBC_PROTO_FIXED4_SCALE)
+               : "wr0", "wr1", "wr2", "wr3", "wr4", "wr5", "wr6", "wr7",
+                 "wr8", "wr9", "wcgr0", "memory");
+}
+
+static inline void sbc_analyze_eight_iwmmxt(const int16_t *in, int32_t *out,
+                                                       const FIXED_T *consts)
+{
+       asm volatile (
+               "wldrd        wr0, [%0]\n"
+               "tbcstw       wr15, %2\n"
+               "wldrd        wr1, [%0, #8]\n"
+               "wldrd        wr2, [%0, #16]\n"
+               "wldrd        wr3, [%0, #24]\n"
+               "wldrd        wr4, [%1]\n"
+               "wldrd        wr5, [%1, #8]\n"
+               "wldrd        wr6, [%1, #16]\n"
+               "wldrd        wr7, [%1, #24]\n"
+               "wmadds       wr0, wr0, wr4\n"
+               " wldrd       wr8, [%1, #32]\n"
+               "wmadds       wr1, wr1, wr5\n"
+               " wldrd       wr9, [%1, #40]\n"
+               "wmadds       wr2, wr2, wr6\n"
+               " wldrd      wr10, [%1, #48]\n"
+               "wmadds       wr3, wr3, wr7\n"
+               " wldrd      wr11, [%1, #56]\n"
+               "waddwss      wr0, wr0, wr15\n"
+               " wldrd       wr4, [%0, #32]\n"
+               "waddwss      wr1, wr1, wr15\n"
+               " wldrd       wr5, [%0, #40]\n"
+               "waddwss      wr2, wr2, wr15\n"
+               " wldrd       wr6, [%0, #48]\n"
+               "waddwss      wr3, wr3, wr15\n"
+               " wldrd       wr7, [%0, #56]\n"
+               " wmadds      wr4, wr4, wr8\n"
+               "  wldrd     wr12, [%0, #64]\n"
+               " wmadds      wr5, wr5, wr9\n"
+               "  wldrd     wr13, [%0, #72]\n"
+               " wmadds      wr6, wr6, wr10\n"
+               "  wldrd     wr14, [%0, #80]\n"
+               " wmadds      wr7, wr7, wr11\n"
+               "  wldrd     wr15, [%0, #88]\n"
+               " waddwss     wr0, wr4, wr0\n"
+               "  wldrd      wr8, [%1, #64]\n"
+               " waddwss     wr1, wr5, wr1\n"
+               "  wldrd      wr9, [%1, #72]\n"
+               " waddwss     wr2, wr6, wr2\n"
+               "  wldrd     wr10, [%1, #80]\n"
+               " waddwss     wr3, wr7, wr3\n"
+               "  wldrd     wr11, [%1, #88]\n"
+               "  wmadds    wr12, wr12, wr8\n"
+               "wldrd        wr4, [%0, #96]\n"
+               "  wmadds    wr13, wr13, wr9\n"
+               "wldrd        wr5, [%0, #104]\n"
+               "  wmadds    wr14, wr14, wr10\n"
+               "wldrd        wr6, [%0, #112]\n"
+               "  wmadds    wr15, wr15, wr11\n"
+               "wldrd        wr7, [%0, #120]\n"
+               "  waddwss    wr0, wr12, wr0\n"
+               "wldrd        wr8, [%1, #96]\n"
+               "  waddwss    wr1, wr13, wr1\n"
+               "wldrd        wr9, [%1, #104]\n"
+               "  waddwss    wr2, wr14, wr2\n"
+               "wldrd       wr10, [%1, #112]\n"
+               "  waddwss    wr3, wr15, wr3\n"
+               "wldrd       wr11, [%1, #120]\n"
+               "wmadds       wr4, wr4, wr8\n"
+               " wldrd      wr12, [%0, #128]\n"
+               "wmadds       wr5, wr5, wr9\n"
+               " wldrd      wr13, [%0, #136]\n"
+               "wmadds       wr6, wr6, wr10\n"
+               " wldrd      wr14, [%0, #144]\n"
+               "wmadds       wr7, wr7, wr11\n"
+               " wldrd      wr15, [%0, #152]\n"
+               "waddwss      wr0, wr4, wr0\n"
+               " wldrd       wr8, [%1, #128]\n"
+               "waddwss      wr1, wr5, wr1\n"
+               " wldrd       wr9, [%1, #136]\n"
+               "waddwss      wr2, wr6, wr2\n"
+               " wldrd      wr10, [%1, #144]\n"
+               " waddwss     wr3, wr7, wr3\n"
+               " wldrd     wr11, [%1, #152]\n"
+               " wmadds     wr12, wr12, wr8\n"
+               "tmcr       wcgr0, %4\n"
+               " wmadds     wr13, wr13, wr9\n"
+               " wmadds     wr14, wr14, wr10\n"
+               " wmadds     wr15, wr15, wr11\n"
+               " waddwss     wr0, wr12, wr0\n"
+               " waddwss     wr1, wr13, wr1\n"
+               " waddwss     wr2, wr14, wr2\n"
+               " waddwss     wr3, wr15, wr3\n"
+               "\n"
+               "wsrawg       wr0, wr0, wcgr0\n"
+               "wsrawg       wr1, wr1, wcgr0\n"
+               "wsrawg       wr2, wr2, wcgr0\n"
+               "wsrawg       wr3, wr3, wcgr0\n"
+               "\n"
+               "wpackwss     wr0, wr0, wr0\n"
+               "wpackwss     wr1, wr1, wr1\n"
+               " wldrd       wr4, [%1, #160]\n"
+               "wpackwss     wr2, wr2, wr2\n"
+               " wldrd       wr5, [%1, #168]\n"
+               "wpackwss     wr3, wr3, wr3\n"
+               "  wldrd      wr6, [%1, #192]\n"
+               " wmadds      wr4, wr4, wr0\n"
+               "  wldrd      wr7, [%1, #200]\n"
+               " wmadds      wr5, wr5, wr0\n"
+               "   wldrd     wr8, [%1, #224]\n"
+               "  wmadds     wr6, wr6, wr1\n"
+               "   wldrd     wr9, [%1, #232]\n"
+               "  wmadds     wr7, wr7, wr1\n"
+               "  waddwss    wr4, wr6, wr4\n"
+               "  waddwss    wr5, wr7, wr5\n"
+               "   wmadds    wr8, wr8, wr2\n"
+               "wldrd        wr6, [%1, #256]\n"
+               "   wmadds    wr9, wr9, wr2\n"
+               "wldrd        wr7, [%1, #264]\n"
+               "waddwss      wr4, wr8, wr4\n"
+               "   waddwss   wr5, wr9, wr5\n"
+               "wmadds       wr6, wr6, wr3\n"
+               "wmadds       wr7, wr7, wr3\n"
+               "waddwss      wr4, wr6, wr4\n"
+               "waddwss      wr5, wr7, wr5\n"
+               "\n"
+               "wstrd        wr4, [%3]\n"
+               "wstrd        wr5, [%3, #8]\n"
+               "\n"
+               "wldrd        wr6, [%1, #176]\n"
+               "wldrd        wr5, [%1, #184]\n"
+               "wmadds       wr5, wr5, wr0\n"
+               "wldrd        wr8, [%1, #208]\n"
+               "wmadds       wr0, wr6, wr0\n"
+               "wldrd        wr9, [%1, #216]\n"
+               "wmadds       wr9, wr9, wr1\n"
+               "wldrd        wr6, [%1, #240]\n"
+               "wmadds       wr1, wr8, wr1\n"
+               "wldrd        wr7, [%1, #248]\n"
+               "waddwss      wr0, wr1, wr0\n"
+               "waddwss      wr5, wr9, wr5\n"
+               "wmadds       wr7, wr7, wr2\n"
+               "wldrd        wr8, [%1, #272]\n"
+               "wmadds       wr2, wr6, wr2\n"
+               "wldrd        wr9, [%1, #280]\n"
+               "waddwss      wr0, wr2, wr0\n"
+               "waddwss      wr5, wr7, wr5\n"
+               "wmadds       wr9, wr9, wr3\n"
+               "wmadds       wr3, wr8, wr3\n"
+               "waddwss      wr0, wr3, wr0\n"
+               "waddwss      wr5, wr9, wr5\n"
+               "\n"
+               "wstrd        wr0, [%3, #16]\n"
+               "wstrd        wr5, [%3, #24]\n"
+               :
+               : "r" (in), "r" (consts),
+                       "r" (1 << (SBC_PROTO_FIXED8_SCALE - 1)), "r" (out),
+                       "r" (SBC_PROTO_FIXED8_SCALE)
+               : "wr0", "wr1", "wr2", "wr3", "wr4", "wr5", "wr6", "wr7",
+                 "wr8", "wr9", "wr10", "wr11", "wr12", "wr13", "wr14", "wr15",
+                 "wcgr0", "memory");
+}
+
+static inline void sbc_analyze_4b_4s_iwmmxt(int16_t *x, int32_t *out,
+                                               int out_stride)
+{
+       /* Analyze blocks */
+       sbc_analyze_four_iwmmxt(x + 12, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       sbc_analyze_four_iwmmxt(x + 8, out, analysis_consts_fixed4_simd_even);
+       out += out_stride;
+       sbc_analyze_four_iwmmxt(x + 4, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       sbc_analyze_four_iwmmxt(x + 0, out, analysis_consts_fixed4_simd_even);
+}
+
+static inline void sbc_analyze_4b_8s_iwmmxt(int16_t *x, int32_t *out,
+                                               int out_stride)
+{
+       /* Analyze blocks */
+       sbc_analyze_eight_iwmmxt(x + 24, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       sbc_analyze_eight_iwmmxt(x + 16, out, analysis_consts_fixed8_simd_even);
+       out += out_stride;
+       sbc_analyze_eight_iwmmxt(x + 8, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       sbc_analyze_eight_iwmmxt(x + 0, out, analysis_consts_fixed8_simd_even);
+}
+
+void sbc_init_primitives_iwmmxt(struct sbc_encoder_state *state)
+{
+       state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_iwmmxt;
+       state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_iwmmxt;
+       state->implementation_info = "IWMMXT";
+}
+
+#endif
diff --git a/sbc/sbc_primitives_iwmmxt.h b/sbc/sbc_primitives_iwmmxt.h
new file mode 100644 (file)
index 0000000..b535e68
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2010 Keith Mok <ek9852@gmail.com>
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 __SBC_PRIMITIVES_IWMMXT_H
+#define __SBC_PRIMITIVES_IWMMXT_H
+
+#include "sbc_primitives.h"
+
+#if defined(__GNUC__) && defined(__IWMMXT__) && \
+               !defined(SBC_HIGH_PRECISION) && (SCALE_OUT_BITS == 15)
+
+#define SBC_BUILD_WITH_IWMMXT_SUPPORT
+
+void sbc_init_primitives_iwmmxt(struct sbc_encoder_state *encoder_state);
+
+#endif
+
+#endif
diff --git a/sbc/sbc_primitives_mmx.c b/sbc/sbc_primitives_mmx.c
new file mode 100644 (file)
index 0000000..7f2fbc3
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 <stdint.h>
+#include <limits.h>
+#include "sbc.h"
+#include "sbc_math.h"
+#include "sbc_tables.h"
+
+#include "sbc_primitives_mmx.h"
+
+/*
+ * MMX optimizations
+ */
+
+#ifdef SBC_BUILD_WITH_MMX_SUPPORT
+
+static inline void sbc_analyze_four_mmx(const int16_t *in, int32_t *out,
+                                       const FIXED_T *consts)
+{
+       static const SBC_ALIGNED int32_t round_c[2] = {
+               1 << (SBC_PROTO_FIXED4_SCALE - 1),
+               1 << (SBC_PROTO_FIXED4_SCALE - 1),
+       };
+       asm volatile (
+               "movq        (%0), %%mm0\n"
+               "movq       8(%0), %%mm1\n"
+               "pmaddwd     (%1), %%mm0\n"
+               "pmaddwd    8(%1), %%mm1\n"
+               "paddd       (%2), %%mm0\n"
+               "paddd       (%2), %%mm1\n"
+               "\n"
+               "movq      16(%0), %%mm2\n"
+               "movq      24(%0), %%mm3\n"
+               "pmaddwd   16(%1), %%mm2\n"
+               "pmaddwd   24(%1), %%mm3\n"
+               "paddd      %%mm2, %%mm0\n"
+               "paddd      %%mm3, %%mm1\n"
+               "\n"
+               "movq      32(%0), %%mm2\n"
+               "movq      40(%0), %%mm3\n"
+               "pmaddwd   32(%1), %%mm2\n"
+               "pmaddwd   40(%1), %%mm3\n"
+               "paddd      %%mm2, %%mm0\n"
+               "paddd      %%mm3, %%mm1\n"
+               "\n"
+               "movq      48(%0), %%mm2\n"
+               "movq      56(%0), %%mm3\n"
+               "pmaddwd   48(%1), %%mm2\n"
+               "pmaddwd   56(%1), %%mm3\n"
+               "paddd      %%mm2, %%mm0\n"
+               "paddd      %%mm3, %%mm1\n"
+               "\n"
+               "movq      64(%0), %%mm2\n"
+               "movq      72(%0), %%mm3\n"
+               "pmaddwd   64(%1), %%mm2\n"
+               "pmaddwd   72(%1), %%mm3\n"
+               "paddd      %%mm2, %%mm0\n"
+               "paddd      %%mm3, %%mm1\n"
+               "\n"
+               "psrad         %4, %%mm0\n"
+               "psrad         %4, %%mm1\n"
+               "packssdw   %%mm0, %%mm0\n"
+               "packssdw   %%mm1, %%mm1\n"
+               "\n"
+               "movq       %%mm0, %%mm2\n"
+               "pmaddwd   80(%1), %%mm0\n"
+               "pmaddwd   88(%1), %%mm2\n"
+               "\n"
+               "movq       %%mm1, %%mm3\n"
+               "pmaddwd   96(%1), %%mm1\n"
+               "pmaddwd  104(%1), %%mm3\n"
+               "paddd      %%mm1, %%mm0\n"
+               "paddd      %%mm3, %%mm2\n"
+               "\n"
+               "movq       %%mm0, (%3)\n"
+               "movq       %%mm2, 8(%3)\n"
+               :
+               : "r" (in), "r" (consts), "r" (&round_c), "r" (out),
+                       "i" (SBC_PROTO_FIXED4_SCALE)
+               : "cc", "memory");
+}
+
+static inline void sbc_analyze_eight_mmx(const int16_t *in, int32_t *out,
+                                                       const FIXED_T *consts)
+{
+       static const SBC_ALIGNED int32_t round_c[2] = {
+               1 << (SBC_PROTO_FIXED8_SCALE - 1),
+               1 << (SBC_PROTO_FIXED8_SCALE - 1),
+       };
+       asm volatile (
+               "movq        (%0), %%mm0\n"
+               "movq       8(%0), %%mm1\n"
+               "movq      16(%0), %%mm2\n"
+               "movq      24(%0), %%mm3\n"
+               "pmaddwd     (%1), %%mm0\n"
+               "pmaddwd    8(%1), %%mm1\n"
+               "pmaddwd   16(%1), %%mm2\n"
+               "pmaddwd   24(%1), %%mm3\n"
+               "paddd       (%2), %%mm0\n"
+               "paddd       (%2), %%mm1\n"
+               "paddd       (%2), %%mm2\n"
+               "paddd       (%2), %%mm3\n"
+               "\n"
+               "movq      32(%0), %%mm4\n"
+               "movq      40(%0), %%mm5\n"
+               "movq      48(%0), %%mm6\n"
+               "movq      56(%0), %%mm7\n"
+               "pmaddwd   32(%1), %%mm4\n"
+               "pmaddwd   40(%1), %%mm5\n"
+               "pmaddwd   48(%1), %%mm6\n"
+               "pmaddwd   56(%1), %%mm7\n"
+               "paddd      %%mm4, %%mm0\n"
+               "paddd      %%mm5, %%mm1\n"
+               "paddd      %%mm6, %%mm2\n"
+               "paddd      %%mm7, %%mm3\n"
+               "\n"
+               "movq      64(%0), %%mm4\n"
+               "movq      72(%0), %%mm5\n"
+               "movq      80(%0), %%mm6\n"
+               "movq      88(%0), %%mm7\n"
+               "pmaddwd   64(%1), %%mm4\n"
+               "pmaddwd   72(%1), %%mm5\n"
+               "pmaddwd   80(%1), %%mm6\n"
+               "pmaddwd   88(%1), %%mm7\n"
+               "paddd      %%mm4, %%mm0\n"
+               "paddd      %%mm5, %%mm1\n"
+               "paddd      %%mm6, %%mm2\n"
+               "paddd      %%mm7, %%mm3\n"
+               "\n"
+               "movq      96(%0), %%mm4\n"
+               "movq     104(%0), %%mm5\n"
+               "movq     112(%0), %%mm6\n"
+               "movq     120(%0), %%mm7\n"
+               "pmaddwd   96(%1), %%mm4\n"
+               "pmaddwd  104(%1), %%mm5\n"
+               "pmaddwd  112(%1), %%mm6\n"
+               "pmaddwd  120(%1), %%mm7\n"
+               "paddd      %%mm4, %%mm0\n"
+               "paddd      %%mm5, %%mm1\n"
+               "paddd      %%mm6, %%mm2\n"
+               "paddd      %%mm7, %%mm3\n"
+               "\n"
+               "movq     128(%0), %%mm4\n"
+               "movq     136(%0), %%mm5\n"
+               "movq     144(%0), %%mm6\n"
+               "movq     152(%0), %%mm7\n"
+               "pmaddwd  128(%1), %%mm4\n"
+               "pmaddwd  136(%1), %%mm5\n"
+               "pmaddwd  144(%1), %%mm6\n"
+               "pmaddwd  152(%1), %%mm7\n"
+               "paddd      %%mm4, %%mm0\n"
+               "paddd      %%mm5, %%mm1\n"
+               "paddd      %%mm6, %%mm2\n"
+               "paddd      %%mm7, %%mm3\n"
+               "\n"
+               "psrad         %4, %%mm0\n"
+               "psrad         %4, %%mm1\n"
+               "psrad         %4, %%mm2\n"
+               "psrad         %4, %%mm3\n"
+               "\n"
+               "packssdw   %%mm0, %%mm0\n"
+               "packssdw   %%mm1, %%mm1\n"
+               "packssdw   %%mm2, %%mm2\n"
+               "packssdw   %%mm3, %%mm3\n"
+               "\n"
+               "movq       %%mm0, %%mm4\n"
+               "movq       %%mm0, %%mm5\n"
+               "pmaddwd  160(%1), %%mm4\n"
+               "pmaddwd  168(%1), %%mm5\n"
+               "\n"
+               "movq       %%mm1, %%mm6\n"
+               "movq       %%mm1, %%mm7\n"
+               "pmaddwd  192(%1), %%mm6\n"
+               "pmaddwd  200(%1), %%mm7\n"
+               "paddd      %%mm6, %%mm4\n"
+               "paddd      %%mm7, %%mm5\n"
+               "\n"
+               "movq       %%mm2, %%mm6\n"
+               "movq       %%mm2, %%mm7\n"
+               "pmaddwd  224(%1), %%mm6\n"
+               "pmaddwd  232(%1), %%mm7\n"
+               "paddd      %%mm6, %%mm4\n"
+               "paddd      %%mm7, %%mm5\n"
+               "\n"
+               "movq       %%mm3, %%mm6\n"
+               "movq       %%mm3, %%mm7\n"
+               "pmaddwd  256(%1), %%mm6\n"
+               "pmaddwd  264(%1), %%mm7\n"
+               "paddd      %%mm6, %%mm4\n"
+               "paddd      %%mm7, %%mm5\n"
+               "\n"
+               "movq       %%mm4, (%3)\n"
+               "movq       %%mm5, 8(%3)\n"
+               "\n"
+               "movq       %%mm0, %%mm5\n"
+               "pmaddwd  176(%1), %%mm0\n"
+               "pmaddwd  184(%1), %%mm5\n"
+               "\n"
+               "movq       %%mm1, %%mm7\n"
+               "pmaddwd  208(%1), %%mm1\n"
+               "pmaddwd  216(%1), %%mm7\n"
+               "paddd      %%mm1, %%mm0\n"
+               "paddd      %%mm7, %%mm5\n"
+               "\n"
+               "movq       %%mm2, %%mm7\n"
+               "pmaddwd  240(%1), %%mm2\n"
+               "pmaddwd  248(%1), %%mm7\n"
+               "paddd      %%mm2, %%mm0\n"
+               "paddd      %%mm7, %%mm5\n"
+               "\n"
+               "movq       %%mm3, %%mm7\n"
+               "pmaddwd  272(%1), %%mm3\n"
+               "pmaddwd  280(%1), %%mm7\n"
+               "paddd      %%mm3, %%mm0\n"
+               "paddd      %%mm7, %%mm5\n"
+               "\n"
+               "movq       %%mm0, 16(%3)\n"
+               "movq       %%mm5, 24(%3)\n"
+               :
+               : "r" (in), "r" (consts), "r" (&round_c), "r" (out),
+                       "i" (SBC_PROTO_FIXED8_SCALE)
+               : "cc", "memory");
+}
+
+static inline void sbc_analyze_4b_4s_mmx(int16_t *x, int32_t *out,
+                                               int out_stride)
+{
+       /* Analyze blocks */
+       sbc_analyze_four_mmx(x + 12, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       sbc_analyze_four_mmx(x + 8, out, analysis_consts_fixed4_simd_even);
+       out += out_stride;
+       sbc_analyze_four_mmx(x + 4, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       sbc_analyze_four_mmx(x + 0, out, analysis_consts_fixed4_simd_even);
+
+       asm volatile ("emms\n");
+}
+
+static inline void sbc_analyze_4b_8s_mmx(int16_t *x, int32_t *out,
+                                               int out_stride)
+{
+       /* Analyze blocks */
+       sbc_analyze_eight_mmx(x + 24, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       sbc_analyze_eight_mmx(x + 16, out, analysis_consts_fixed8_simd_even);
+       out += out_stride;
+       sbc_analyze_eight_mmx(x + 8, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       sbc_analyze_eight_mmx(x + 0, out, analysis_consts_fixed8_simd_even);
+
+       asm volatile ("emms\n");
+}
+
+static void sbc_calc_scalefactors_mmx(
+       int32_t sb_sample_f[16][2][8],
+       uint32_t scale_factor[2][8],
+       int blocks, int channels, int subbands)
+{
+       static const SBC_ALIGNED int32_t consts[2] = {
+               1 << SCALE_OUT_BITS,
+               1 << SCALE_OUT_BITS,
+       };
+       int ch, sb;
+       intptr_t blk;
+       for (ch = 0; ch < channels; ch++) {
+               for (sb = 0; sb < subbands; sb += 2) {
+                       blk = (blocks - 1) * (((char *) &sb_sample_f[1][0][0] -
+                               (char *) &sb_sample_f[0][0][0]));
+                       asm volatile (
+                               "movq         (%4), %%mm0\n"
+                       "1:\n"
+                               "movq     (%1, %0), %%mm1\n"
+                               "pxor        %%mm2, %%mm2\n"
+                               "pcmpgtd     %%mm2, %%mm1\n"
+                               "paddd    (%1, %0), %%mm1\n"
+                               "pcmpgtd     %%mm1, %%mm2\n"
+                               "pxor        %%mm2, %%mm1\n"
+
+                               "por         %%mm1, %%mm0\n"
+
+                               "sub            %2, %0\n"
+                               "jns            1b\n"
+
+                               "movd        %%mm0, %k0\n"
+                               "psrlq         $32, %%mm0\n"
+                               "bsrl          %k0, %k0\n"
+                               "subl           %5, %k0\n"
+                               "movl          %k0, (%3)\n"
+
+                               "movd        %%mm0, %k0\n"
+                               "bsrl          %k0, %k0\n"
+                               "subl           %5, %k0\n"
+                               "movl          %k0, 4(%3)\n"
+                       : "+r" (blk)
+                       : "r" (&sb_sample_f[0][ch][sb]),
+                               "i" ((char *) &sb_sample_f[1][0][0] -
+                                       (char *) &sb_sample_f[0][0][0]),
+                               "r" (&scale_factor[ch][sb]),
+                               "r" (&consts),
+                               "i" (SCALE_OUT_BITS)
+                       : "cc", "memory");
+               }
+       }
+       asm volatile ("emms\n");
+}
+
+static int check_mmx_support(void)
+{
+#ifdef __amd64__
+       return 1; /* We assume that all 64-bit processors have MMX support */
+#else
+       int cpuid_feature_information;
+       asm volatile (
+               /* According to Intel manual, CPUID instruction is supported
+                * if the value of ID bit (bit 21) in EFLAGS can be modified */
+               "pushf\n"
+               "movl     (%%esp),   %0\n"
+               "xorl     $0x200000, (%%esp)\n" /* try to modify ID bit */
+               "popf\n"
+               "pushf\n"
+               "xorl     (%%esp),   %0\n"      /* check if ID bit changed */
+               "jz       1f\n"
+               "push     %%eax\n"
+               "push     %%ebx\n"
+               "push     %%ecx\n"
+               "mov      $1,        %%eax\n"
+               "cpuid\n"
+               "pop      %%ecx\n"
+               "pop      %%ebx\n"
+               "pop      %%eax\n"
+               "1:\n"
+               "popf\n"
+               : "=d" (cpuid_feature_information)
+               :
+               : "cc");
+    return cpuid_feature_information & (1 << 23);
+#endif
+}
+
+void sbc_init_primitives_mmx(struct sbc_encoder_state *state)
+{
+       if (check_mmx_support()) {
+               state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_mmx;
+               state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_mmx;
+               state->sbc_calc_scalefactors = sbc_calc_scalefactors_mmx;
+               state->implementation_info = "MMX";
+       }
+}
+
+#endif
diff --git a/sbc/sbc_primitives_mmx.h b/sbc/sbc_primitives_mmx.h
new file mode 100644 (file)
index 0000000..e0e728b
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 __SBC_PRIMITIVES_MMX_H
+#define __SBC_PRIMITIVES_MMX_H
+
+#include "sbc_primitives.h"
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__amd64__)) && \
+               !defined(SBC_HIGH_PRECISION) && (SCALE_OUT_BITS == 15)
+
+#define SBC_BUILD_WITH_MMX_SUPPORT
+
+void sbc_init_primitives_mmx(struct sbc_encoder_state *encoder_state);
+
+#endif
+
+#endif
diff --git a/sbc/sbc_primitives_neon.c b/sbc/sbc_primitives_neon.c
new file mode 100644 (file)
index 0000000..0572158
--- /dev/null
@@ -0,0 +1,893 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 <stdint.h>
+#include <limits.h>
+#include "sbc.h"
+#include "sbc_math.h"
+#include "sbc_tables.h"
+
+#include "sbc_primitives_neon.h"
+
+/*
+ * ARM NEON optimizations
+ */
+
+#ifdef SBC_BUILD_WITH_NEON_SUPPORT
+
+static inline void _sbc_analyze_four_neon(const int16_t *in, int32_t *out,
+                                                       const FIXED_T *consts)
+{
+       /* TODO: merge even and odd cases (or even merge all four calls to this
+        * function) in order to have only aligned reads from 'in' array
+        * and reduce number of load instructions */
+       asm volatile (
+               "vld1.16    {d4, d5}, [%0, :64]!\n"
+               "vld1.16    {d8, d9}, [%1, :128]!\n"
+
+               "vmull.s16  q0, d4, d8\n"
+               "vld1.16    {d6,  d7}, [%0, :64]!\n"
+               "vmull.s16  q1, d5, d9\n"
+               "vld1.16    {d10, d11}, [%1, :128]!\n"
+
+               "vmlal.s16  q0, d6, d10\n"
+               "vld1.16    {d4, d5}, [%0, :64]!\n"
+               "vmlal.s16  q1, d7, d11\n"
+               "vld1.16    {d8, d9}, [%1, :128]!\n"
+
+               "vmlal.s16  q0, d4, d8\n"
+               "vld1.16    {d6,  d7}, [%0, :64]!\n"
+               "vmlal.s16  q1, d5, d9\n"
+               "vld1.16    {d10, d11}, [%1, :128]!\n"
+
+               "vmlal.s16  q0, d6, d10\n"
+               "vld1.16    {d4, d5}, [%0, :64]!\n"
+               "vmlal.s16  q1, d7, d11\n"
+               "vld1.16    {d8, d9}, [%1, :128]!\n"
+
+               "vmlal.s16  q0, d4, d8\n"
+               "vmlal.s16  q1, d5, d9\n"
+
+               "vpadd.s32  d0, d0, d1\n"
+               "vpadd.s32  d1, d2, d3\n"
+
+               "vrshrn.s32 d0, q0, %3\n"
+
+               "vld1.16    {d2, d3, d4, d5}, [%1, :128]!\n"
+
+               "vdup.i32   d1, d0[1]\n"  /* TODO: can be eliminated */
+               "vdup.i32   d0, d0[0]\n"  /* TODO: can be eliminated */
+
+               "vmull.s16  q3, d2, d0\n"
+               "vmull.s16  q4, d3, d0\n"
+               "vmlal.s16  q3, d4, d1\n"
+               "vmlal.s16  q4, d5, d1\n"
+
+               "vpadd.s32  d0, d6, d7\n" /* TODO: can be eliminated */
+               "vpadd.s32  d1, d8, d9\n" /* TODO: can be eliminated */
+
+               "vst1.32    {d0, d1}, [%2, :128]\n"
+               : "+r" (in), "+r" (consts)
+               : "r" (out),
+                       "i" (SBC_PROTO_FIXED4_SCALE)
+               : "memory",
+                       "d0", "d1", "d2", "d3", "d4", "d5",
+                       "d6", "d7", "d8", "d9", "d10", "d11");
+}
+
+static inline void _sbc_analyze_eight_neon(const int16_t *in, int32_t *out,
+                                                       const FIXED_T *consts)
+{
+       /* TODO: merge even and odd cases (or even merge all four calls to this
+        * function) in order to have only aligned reads from 'in' array
+        * and reduce number of load instructions */
+       asm volatile (
+               "vld1.16    {d4, d5}, [%0, :64]!\n"
+               "vld1.16    {d8, d9}, [%1, :128]!\n"
+
+               "vmull.s16  q6, d4, d8\n"
+               "vld1.16    {d6,  d7}, [%0, :64]!\n"
+               "vmull.s16  q7, d5, d9\n"
+               "vld1.16    {d10, d11}, [%1, :128]!\n"
+               "vmull.s16  q8, d6, d10\n"
+               "vld1.16    {d4, d5}, [%0, :64]!\n"
+               "vmull.s16  q9, d7, d11\n"
+               "vld1.16    {d8, d9}, [%1, :128]!\n"
+
+               "vmlal.s16  q6, d4, d8\n"
+               "vld1.16    {d6,  d7}, [%0, :64]!\n"
+               "vmlal.s16  q7, d5, d9\n"
+               "vld1.16    {d10, d11}, [%1, :128]!\n"
+               "vmlal.s16  q8, d6, d10\n"
+               "vld1.16    {d4, d5}, [%0, :64]!\n"
+               "vmlal.s16  q9, d7, d11\n"
+               "vld1.16    {d8, d9}, [%1, :128]!\n"
+
+               "vmlal.s16  q6, d4, d8\n"
+               "vld1.16    {d6,  d7}, [%0, :64]!\n"
+               "vmlal.s16  q7, d5, d9\n"
+               "vld1.16    {d10, d11}, [%1, :128]!\n"
+               "vmlal.s16  q8, d6, d10\n"
+               "vld1.16    {d4, d5}, [%0, :64]!\n"
+               "vmlal.s16  q9, d7, d11\n"
+               "vld1.16    {d8, d9}, [%1, :128]!\n"
+
+               "vmlal.s16  q6, d4, d8\n"
+               "vld1.16    {d6,  d7}, [%0, :64]!\n"
+               "vmlal.s16  q7, d5, d9\n"
+               "vld1.16    {d10, d11}, [%1, :128]!\n"
+               "vmlal.s16  q8, d6, d10\n"
+               "vld1.16    {d4, d5}, [%0, :64]!\n"
+               "vmlal.s16  q9, d7, d11\n"
+               "vld1.16    {d8, d9}, [%1, :128]!\n"
+
+               "vmlal.s16  q6, d4, d8\n"
+               "vld1.16    {d6,  d7}, [%0, :64]!\n"
+               "vmlal.s16  q7, d5, d9\n"
+               "vld1.16    {d10, d11}, [%1, :128]!\n"
+
+               "vmlal.s16  q8, d6, d10\n"
+               "vmlal.s16  q9, d7, d11\n"
+
+               "vpadd.s32  d0, d12, d13\n"
+               "vpadd.s32  d1, d14, d15\n"
+               "vpadd.s32  d2, d16, d17\n"
+               "vpadd.s32  d3, d18, d19\n"
+
+               "vrshr.s32 q0, q0, %3\n"
+               "vrshr.s32 q1, q1, %3\n"
+               "vmovn.s32 d0, q0\n"
+               "vmovn.s32 d1, q1\n"
+
+               "vdup.i32   d3, d1[1]\n"  /* TODO: can be eliminated */
+               "vdup.i32   d2, d1[0]\n"  /* TODO: can be eliminated */
+               "vdup.i32   d1, d0[1]\n"  /* TODO: can be eliminated */
+               "vdup.i32   d0, d0[0]\n"  /* TODO: can be eliminated */
+
+               "vld1.16    {d4, d5}, [%1, :128]!\n"
+               "vmull.s16  q6, d4, d0\n"
+               "vld1.16    {d6, d7}, [%1, :128]!\n"
+               "vmull.s16  q7, d5, d0\n"
+               "vmull.s16  q8, d6, d0\n"
+               "vmull.s16  q9, d7, d0\n"
+
+               "vld1.16    {d4, d5}, [%1, :128]!\n"
+               "vmlal.s16  q6, d4, d1\n"
+               "vld1.16    {d6, d7}, [%1, :128]!\n"
+               "vmlal.s16  q7, d5, d1\n"
+               "vmlal.s16  q8, d6, d1\n"
+               "vmlal.s16  q9, d7, d1\n"
+
+               "vld1.16    {d4, d5}, [%1, :128]!\n"
+               "vmlal.s16  q6, d4, d2\n"
+               "vld1.16    {d6, d7}, [%1, :128]!\n"
+               "vmlal.s16  q7, d5, d2\n"
+               "vmlal.s16  q8, d6, d2\n"
+               "vmlal.s16  q9, d7, d2\n"
+
+               "vld1.16    {d4, d5}, [%1, :128]!\n"
+               "vmlal.s16  q6, d4, d3\n"
+               "vld1.16    {d6, d7}, [%1, :128]!\n"
+               "vmlal.s16  q7, d5, d3\n"
+               "vmlal.s16  q8, d6, d3\n"
+               "vmlal.s16  q9, d7, d3\n"
+
+               "vpadd.s32  d0, d12, d13\n" /* TODO: can be eliminated */
+               "vpadd.s32  d1, d14, d15\n" /* TODO: can be eliminated */
+               "vpadd.s32  d2, d16, d17\n" /* TODO: can be eliminated */
+               "vpadd.s32  d3, d18, d19\n" /* TODO: can be eliminated */
+
+               "vst1.32    {d0, d1, d2, d3}, [%2, :128]\n"
+               : "+r" (in), "+r" (consts)
+               : "r" (out),
+                       "i" (SBC_PROTO_FIXED8_SCALE)
+               : "memory",
+                       "d0", "d1", "d2", "d3", "d4", "d5",
+                       "d6", "d7", "d8", "d9", "d10", "d11",
+                       "d12", "d13", "d14", "d15", "d16", "d17",
+                       "d18", "d19");
+}
+
+static inline void sbc_analyze_4b_4s_neon(int16_t *x,
+                                               int32_t *out, int out_stride)
+{
+       /* Analyze blocks */
+       _sbc_analyze_four_neon(x + 12, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       _sbc_analyze_four_neon(x + 8, out, analysis_consts_fixed4_simd_even);
+       out += out_stride;
+       _sbc_analyze_four_neon(x + 4, out, analysis_consts_fixed4_simd_odd);
+       out += out_stride;
+       _sbc_analyze_four_neon(x + 0, out, analysis_consts_fixed4_simd_even);
+}
+
+static inline void sbc_analyze_4b_8s_neon(int16_t *x,
+                                               int32_t *out, int out_stride)
+{
+       /* Analyze blocks */
+       _sbc_analyze_eight_neon(x + 24, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       _sbc_analyze_eight_neon(x + 16, out, analysis_consts_fixed8_simd_even);
+       out += out_stride;
+       _sbc_analyze_eight_neon(x + 8, out, analysis_consts_fixed8_simd_odd);
+       out += out_stride;
+       _sbc_analyze_eight_neon(x + 0, out, analysis_consts_fixed8_simd_even);
+}
+
+static void sbc_calc_scalefactors_neon(
+       int32_t sb_sample_f[16][2][8],
+       uint32_t scale_factor[2][8],
+       int blocks, int channels, int subbands)
+{
+       int ch, sb;
+       for (ch = 0; ch < channels; ch++) {
+               for (sb = 0; sb < subbands; sb += 4) {
+                       int blk = blocks;
+                       int32_t *in = &sb_sample_f[0][ch][sb];
+                       asm volatile (
+                               "vmov.s32  q0, #0\n"
+                               "vmov.s32  q1, %[c1]\n"
+                               "vmov.s32  q14, #1\n"
+                               "vmov.s32  q15, %[c2]\n"
+                               "vadd.s32  q1, q1, q14\n"
+                       "1:\n"
+                               "vld1.32   {d16, d17}, [%[in], :128], %[inc]\n"
+                               "vabs.s32  q8,  q8\n"
+                               "vld1.32   {d18, d19}, [%[in], :128], %[inc]\n"
+                               "vabs.s32  q9,  q9\n"
+                               "vld1.32   {d20, d21}, [%[in], :128], %[inc]\n"
+                               "vabs.s32  q10, q10\n"
+                               "vld1.32   {d22, d23}, [%[in], :128], %[inc]\n"
+                               "vabs.s32  q11, q11\n"
+                               "vmax.s32  q0,  q0,  q8\n"
+                               "vmax.s32  q1,  q1,  q9\n"
+                               "vmax.s32  q0,  q0,  q10\n"
+                               "vmax.s32  q1,  q1,  q11\n"
+                               "subs      %[blk], %[blk], #4\n"
+                               "bgt       1b\n"
+                               "vmax.s32  q0,  q0,  q1\n"
+                               "vsub.s32  q0,  q0,  q14\n"
+                               "vclz.s32  q0,  q0\n"
+                               "vsub.s32  q0,  q15, q0\n"
+                               "vst1.32   {d0, d1}, [%[out], :128]\n"
+                       :
+                         [blk]    "+r" (blk),
+                         [in]     "+r" (in)
+                       :
+                         [inc]     "r" ((char *) &sb_sample_f[1][0][0] -
+                                        (char *) &sb_sample_f[0][0][0]),
+                         [out]     "r" (&scale_factor[ch][sb]),
+                         [c1]      "i" (1 << SCALE_OUT_BITS),
+                         [c2]      "i" (31 - SCALE_OUT_BITS)
+                       : "d0", "d1", "d2", "d3", "d16", "d17", "d18", "d19",
+                         "d20", "d21", "d22", "d23", "d24", "d25", "d26",
+                         "d27", "d28", "d29", "d30", "d31", "cc", "memory");
+               }
+       }
+}
+
+int sbc_calc_scalefactors_j_neon(
+       int32_t sb_sample_f[16][2][8],
+       uint32_t scale_factor[2][8],
+       int blocks, int subbands)
+{
+       static SBC_ALIGNED int32_t joint_bits_mask[8] = {
+               8,   4,  2,  1, 128, 64, 32, 16
+       };
+       int joint, i;
+       int32_t  *in0, *in1;
+       int32_t  *in = &sb_sample_f[0][0][0];
+       uint32_t *out0, *out1;
+       uint32_t *out = &scale_factor[0][0];
+       int32_t  *consts = joint_bits_mask;
+
+       i = subbands;
+
+       asm volatile (
+               /*
+                * constants: q13 = (31 - SCALE_OUT_BITS), q14 = 1
+                * input:     q0  = ((1 << SCALE_OUT_BITS) + 1)
+                *            %[in0] - samples for channel 0
+                *            %[in1] - samples for shannel 1
+                * output:    q0, q1 - scale factors without joint stereo
+                *            q2, q3 - scale factors with joint stereo
+                *            q15    - joint stereo selection mask
+                */
+               ".macro calc_scalefactors\n"
+                       "vmov.s32  q1, q0\n"
+                       "vmov.s32  q2, q0\n"
+                       "vmov.s32  q3, q0\n"
+                       "mov       %[i], %[blocks]\n"
+               "1:\n"
+                       "vld1.32   {d18, d19}, [%[in1], :128], %[inc]\n"
+                       "vbic.s32  q11, q9,  q14\n"
+                       "vld1.32   {d16, d17}, [%[in0], :128], %[inc]\n"
+                       "vhadd.s32 q10, q8,  q11\n"
+                       "vhsub.s32 q11, q8,  q11\n"
+                       "vabs.s32  q8,  q8\n"
+                       "vabs.s32  q9,  q9\n"
+                       "vabs.s32  q10, q10\n"
+                       "vabs.s32  q11, q11\n"
+                       "vmax.s32  q0,  q0,  q8\n"
+                       "vmax.s32  q1,  q1,  q9\n"
+                       "vmax.s32  q2,  q2,  q10\n"
+                       "vmax.s32  q3,  q3,  q11\n"
+                       "subs      %[i], %[i], #1\n"
+                       "bgt       1b\n"
+                       "vsub.s32  q0,  q0,  q14\n"
+                       "vsub.s32  q1,  q1,  q14\n"
+                       "vsub.s32  q2,  q2,  q14\n"
+                       "vsub.s32  q3,  q3,  q14\n"
+                       "vclz.s32  q0,  q0\n"
+                       "vclz.s32  q1,  q1\n"
+                       "vclz.s32  q2,  q2\n"
+                       "vclz.s32  q3,  q3\n"
+                       "vsub.s32  q0,  q13, q0\n"
+                       "vsub.s32  q1,  q13, q1\n"
+                       "vsub.s32  q2,  q13, q2\n"
+                       "vsub.s32  q3,  q13, q3\n"
+               ".endm\n"
+               /*
+                * constants: q14 = 1
+                * input: q15    - joint stereo selection mask
+                *        %[in0] - value set by calc_scalefactors macro
+                *        %[in1] - value set by calc_scalefactors macro
+                */
+               ".macro update_joint_stereo_samples\n"
+                       "sub       %[out1], %[in1], %[inc]\n"
+                       "sub       %[out0], %[in0], %[inc]\n"
+                       "sub       %[in1], %[in1], %[inc], asl #1\n"
+                       "sub       %[in0], %[in0], %[inc], asl #1\n"
+                       "vld1.32   {d18, d19}, [%[in1], :128]\n"
+                       "vbic.s32  q11, q9,  q14\n"
+                       "vld1.32   {d16, d17}, [%[in0], :128]\n"
+                       "vld1.32   {d2, d3}, [%[out1], :128]\n"
+                       "vbic.s32  q3,  q1,  q14\n"
+                       "vld1.32   {d0, d1}, [%[out0], :128]\n"
+                       "vhsub.s32 q10, q8,  q11\n"
+                       "vhadd.s32 q11, q8,  q11\n"
+                       "vhsub.s32 q2,  q0,  q3\n"
+                       "vhadd.s32 q3,  q0,  q3\n"
+                       "vbif.s32  q10, q9,  q15\n"
+                       "vbif.s32  d22, d16, d30\n"
+                       "sub       %[inc], %[zero], %[inc], asl #1\n"
+                       "sub       %[i], %[blocks], #2\n"
+               "2:\n"
+                       "vbif.s32  d23, d17, d31\n"
+                       "vst1.32   {d20, d21}, [%[in1], :128], %[inc]\n"
+                       "vbif.s32  d4,  d2,  d30\n"
+                       "vld1.32   {d18, d19}, [%[in1], :128]\n"
+                       "vbif.s32  d5,  d3,  d31\n"
+                       "vst1.32   {d22, d23}, [%[in0], :128], %[inc]\n"
+                       "vbif.s32  d6,  d0,  d30\n"
+                       "vld1.32   {d16, d17}, [%[in0], :128]\n"
+                       "vbif.s32  d7,  d1,  d31\n"
+                       "vst1.32   {d4, d5}, [%[out1], :128], %[inc]\n"
+                       "vbic.s32  q11, q9,  q14\n"
+                       "vld1.32   {d2, d3}, [%[out1], :128]\n"
+                       "vst1.32   {d6, d7}, [%[out0], :128], %[inc]\n"
+                       "vbic.s32  q3,  q1,  q14\n"
+                       "vld1.32   {d0, d1}, [%[out0], :128]\n"
+                       "vhsub.s32 q10, q8,  q11\n"
+                       "vhadd.s32 q11, q8,  q11\n"
+                       "vhsub.s32 q2,  q0,  q3\n"
+                       "vhadd.s32 q3,  q0,  q3\n"
+                       "vbif.s32  q10, q9,  q15\n"
+                       "vbif.s32  d22, d16, d30\n"
+                       "subs      %[i], %[i], #2\n"
+                       "bgt       2b\n"
+                       "sub       %[inc], %[zero], %[inc], asr #1\n"
+                       "vbif.s32  d23, d17, d31\n"
+                       "vst1.32   {d20, d21}, [%[in1], :128]\n"
+                       "vbif.s32  q2,  q1,  q15\n"
+                       "vst1.32   {d22, d23}, [%[in0], :128]\n"
+                       "vbif.s32  q3,  q0,  q15\n"
+                       "vst1.32   {d4, d5}, [%[out1], :128]\n"
+                       "vst1.32   {d6, d7}, [%[out0], :128]\n"
+               ".endm\n"
+
+               "vmov.s32  q14, #1\n"
+               "vmov.s32  q13, %[c2]\n"
+
+               "cmp   %[i], #4\n"
+               "bne   8f\n"
+
+       "4:\n" /* 4 subbands */
+               "add   %[in0], %[in], #0\n"
+               "add   %[in1], %[in], #32\n"
+               "add   %[out0], %[out], #0\n"
+               "add   %[out1], %[out], #32\n"
+               "vmov.s32  q0, %[c1]\n"
+               "vadd.s32  q0, q0, q14\n"
+
+               "calc_scalefactors\n"
+
+               /* check whether to use joint stereo for subbands 0, 1, 2 */
+               "vadd.s32  q15, q0,  q1\n"
+               "vadd.s32  q9,  q2,  q3\n"
+               "vmov.s32  d31[1], %[zero]\n" /* last subband -> no joint */
+               "vld1.32   {d16, d17}, [%[consts], :128]!\n"
+               "vcgt.s32  q15, q15, q9\n"
+
+               /* calculate and save to memory 'joint' variable */
+               /* update and save scale factors to memory */
+               "  vand.s32  q8, q8, q15\n"
+               "vbit.s32  q0,  q2,  q15\n"
+               "  vpadd.s32 d16, d16, d17\n"
+               "vbit.s32  q1,  q3,  q15\n"
+               "  vpadd.s32 d16, d16, d16\n"
+               "vst1.32   {d0, d1}, [%[out0], :128]\n"
+               "vst1.32   {d2, d3}, [%[out1], :128]\n"
+               "  vst1.32   {d16[0]}, [%[joint]]\n"
+
+               "update_joint_stereo_samples\n"
+               "b     9f\n"
+
+       "8:\n" /* 8 subbands */
+               "add   %[in0], %[in], #16\n\n"
+               "add   %[in1], %[in], #48\n"
+               "add   %[out0], %[out], #16\n\n"
+               "add   %[out1], %[out], #48\n"
+               "vmov.s32  q0, %[c1]\n"
+               "vadd.s32  q0, q0, q14\n"
+
+               "calc_scalefactors\n"
+
+               /* check whether to use joint stereo for subbands 4, 5, 6 */
+               "vadd.s32  q15, q0,  q1\n"
+               "vadd.s32  q9,  q2,  q3\n"
+               "vmov.s32  d31[1], %[zero]\n"  /* last subband -> no joint */
+               "vld1.32   {d16, d17}, [%[consts], :128]!\n"
+               "vcgt.s32  q15, q15, q9\n"
+
+               /* calculate part of 'joint' variable and save it to d24 */
+               /* update and save scale factors to memory */
+               "  vand.s32  q8, q8, q15\n"
+               "vbit.s32  q0,  q2,  q15\n"
+               "  vpadd.s32 d16, d16, d17\n"
+               "vbit.s32  q1,  q3,  q15\n"
+               "vst1.32   {d0, d1}, [%[out0], :128]\n"
+               "vst1.32   {d2, d3}, [%[out1], :128]\n"
+               "  vpadd.s32 d24, d16, d16\n"
+
+               "update_joint_stereo_samples\n"
+
+               "add   %[in0], %[in], #0\n"
+               "add   %[in1], %[in], #32\n"
+               "add   %[out0], %[out], #0\n\n"
+               "add   %[out1], %[out], #32\n"
+               "vmov.s32  q0, %[c1]\n"
+               "vadd.s32  q0, q0, q14\n"
+
+               "calc_scalefactors\n"
+
+               /* check whether to use joint stereo for subbands 0, 1, 2, 3 */
+               "vadd.s32  q15, q0,  q1\n"
+               "vadd.s32  q9,  q2,  q3\n"
+               "vld1.32   {d16, d17}, [%[consts], :128]!\n"
+               "vcgt.s32  q15, q15, q9\n"
+
+               /* combine last part of 'joint' with d24 and save to memory */
+               /* update and save scale factors to memory */
+               "  vand.s32  q8, q8, q15\n"
+               "vbit.s32  q0,  q2,  q15\n"
+               "  vpadd.s32 d16, d16, d17\n"
+               "vbit.s32  q1,  q3,  q15\n"
+               "  vpadd.s32 d16, d16, d16\n"
+               "vst1.32   {d0, d1}, [%[out0], :128]\n"
+               "  vadd.s32  d16, d16, d24\n"
+               "vst1.32   {d2, d3}, [%[out1], :128]\n"
+               "  vst1.32   {d16[0]}, [%[joint]]\n"
+
+               "update_joint_stereo_samples\n"
+       "9:\n"
+               ".purgem calc_scalefactors\n"
+               ".purgem update_joint_stereo_samples\n"
+               :
+                 [i]      "+&r" (i),
+                 [in]     "+&r" (in),
+                 [in0]    "=&r" (in0),
+                 [in1]    "=&r" (in1),
+                 [out]    "+&r" (out),
+                 [out0]   "=&r" (out0),
+                 [out1]   "=&r" (out1),
+                 [consts] "+&r" (consts)
+               :
+                 [inc]      "r" ((char *) &sb_sample_f[1][0][0] -
+                                (char *) &sb_sample_f[0][0][0]),
+                 [blocks]   "r" (blocks),
+                 [joint]    "r" (&joint),
+                 [c1]       "i" (1 << SCALE_OUT_BITS),
+                 [c2]       "i" (31 - SCALE_OUT_BITS),
+                 [zero]     "r" (0)
+               : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
+                 "d16", "d17", "d18", "d19", "d20", "d21", "d22",
+                 "d23", "d24", "d25", "d26", "d27", "d28", "d29",
+                 "d30", "d31", "cc", "memory");
+
+       return joint;
+}
+
+#define PERM_BE(a, b, c, d) {             \
+               (a * 2) + 1, (a * 2) + 0, \
+               (b * 2) + 1, (b * 2) + 0, \
+               (c * 2) + 1, (c * 2) + 0, \
+               (d * 2) + 1, (d * 2) + 0  \
+       }
+#define PERM_LE(a, b, c, d) {             \
+               (a * 2) + 0, (a * 2) + 1, \
+               (b * 2) + 0, (b * 2) + 1, \
+               (c * 2) + 0, (c * 2) + 1, \
+               (d * 2) + 0, (d * 2) + 1  \
+       }
+
+static SBC_ALWAYS_INLINE int sbc_enc_process_input_4s_neon_internal(
+       int position,
+       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+       int nsamples, int nchannels, int big_endian)
+{
+       static SBC_ALIGNED uint8_t perm_be[2][8] = {
+               PERM_BE(7, 3, 6, 4),
+               PERM_BE(0, 2, 1, 5)
+       };
+       static SBC_ALIGNED uint8_t perm_le[2][8] = {
+               PERM_LE(7, 3, 6, 4),
+               PERM_LE(0, 2, 1, 5)
+       };
+       /* handle X buffer wraparound */
+       if (position < nsamples) {
+               int16_t *dst = &X[0][SBC_X_BUFFER_SIZE - 40];
+               int16_t *src = &X[0][position];
+               asm volatile (
+                       "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                       "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                       "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                       "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                       "vld1.16 {d0}, [%[src], :64]!\n"
+                       "vst1.16 {d0}, [%[dst], :64]!\n"
+                       :
+                         [dst] "+r" (dst),
+                         [src] "+r" (src)
+                       : : "memory", "d0", "d1", "d2", "d3");
+               if (nchannels > 1) {
+                       dst = &X[1][SBC_X_BUFFER_SIZE - 40];
+                       src = &X[1][position];
+                       asm volatile (
+                               "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                               "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                               "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                               "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                               "vld1.16 {d0}, [%[src], :64]!\n"
+                               "vst1.16 {d0}, [%[dst], :64]!\n"
+                               :
+                                 [dst] "+r" (dst),
+                                 [src] "+r" (src)
+                               : : "memory", "d0", "d1", "d2", "d3");
+               }
+               position = SBC_X_BUFFER_SIZE - 40;
+       }
+
+       if ((nchannels > 1) && ((uintptr_t)pcm & 1)) {
+               /* poor 'pcm' alignment */
+               int16_t *x = &X[0][position];
+               int16_t *y = &X[1][position];
+               asm volatile (
+                       "vld1.8  {d0, d1}, [%[perm], :128]\n"
+               "1:\n"
+                       "sub     %[x], %[x], #16\n"
+                       "sub     %[y], %[y], #16\n"
+                       "sub     %[position], %[position], #8\n"
+                       "vld1.8  {d4, d5}, [%[pcm]]!\n"
+                       "vuzp.16 d4,  d5\n"
+                       "vld1.8  {d20, d21}, [%[pcm]]!\n"
+                       "vuzp.16 d20, d21\n"
+                       "vswp    d5,  d20\n"
+                       "vtbl.8  d16, {d4, d5}, d0\n"
+                       "vtbl.8  d17, {d4, d5}, d1\n"
+                       "vtbl.8  d18, {d20, d21}, d0\n"
+                       "vtbl.8  d19, {d20, d21}, d1\n"
+                       "vst1.16 {d16, d17}, [%[x], :128]\n"
+                       "vst1.16 {d18, d19}, [%[y], :128]\n"
+                       "subs    %[nsamples], %[nsamples], #8\n"
+                       "bgt     1b\n"
+                       :
+                         [x]        "+r" (x),
+                         [y]        "+r" (y),
+                         [pcm]      "+r" (pcm),
+                         [nsamples] "+r" (nsamples),
+                         [position] "+r" (position)
+                       :
+                         [perm]      "r" (big_endian ? perm_be : perm_le)
+                       : "cc", "memory", "d0", "d1", "d2", "d3", "d4",
+                         "d5", "d6", "d7", "d16", "d17", "d18", "d19",
+                         "d20", "d21", "d22", "d23");
+       } else if (nchannels > 1) {
+               /* proper 'pcm' alignment */
+               int16_t *x = &X[0][position];
+               int16_t *y = &X[1][position];
+               asm volatile (
+                       "vld1.8  {d0, d1}, [%[perm], :128]\n"
+               "1:\n"
+                       "sub     %[x], %[x], #16\n"
+                       "sub     %[y], %[y], #16\n"
+                       "sub     %[position], %[position], #8\n"
+                       "vld2.16 {d4, d5}, [%[pcm]]!\n"
+                       "vld2.16 {d20, d21}, [%[pcm]]!\n"
+                       "vswp    d5, d20\n"
+                       "vtbl.8  d16, {d4, d5}, d0\n"
+                       "vtbl.8  d17, {d4, d5}, d1\n"
+                       "vtbl.8  d18, {d20, d21}, d0\n"
+                       "vtbl.8  d19, {d20, d21}, d1\n"
+                       "vst1.16 {d16, d17}, [%[x], :128]\n"
+                       "vst1.16 {d18, d19}, [%[y], :128]\n"
+                       "subs    %[nsamples], %[nsamples], #8\n"
+                       "bgt     1b\n"
+                       :
+                         [x]        "+r" (x),
+                         [y]        "+r" (y),
+                         [pcm]      "+r" (pcm),
+                         [nsamples] "+r" (nsamples),
+                         [position] "+r" (position)
+                       :
+                         [perm]      "r" (big_endian ? perm_be : perm_le)
+                       : "cc", "memory", "d0", "d1", "d2", "d3", "d4",
+                         "d5", "d6", "d7", "d16", "d17", "d18", "d19",
+                         "d20", "d21", "d22", "d23");
+       } else {
+               int16_t *x = &X[0][position];
+               asm volatile (
+                       "vld1.8  {d0, d1}, [%[perm], :128]\n"
+               "1:\n"
+                       "sub     %[x], %[x], #16\n"
+                       "sub     %[position], %[position], #8\n"
+                       "vld1.8  {d4, d5}, [%[pcm]]!\n"
+                       "vtbl.8  d16, {d4, d5}, d0\n"
+                       "vtbl.8  d17, {d4, d5}, d1\n"
+                       "vst1.16 {d16, d17}, [%[x], :128]\n"
+                       "subs    %[nsamples], %[nsamples], #8\n"
+                       "bgt     1b\n"
+                       :
+                         [x]        "+r" (x),
+                         [pcm]      "+r" (pcm),
+                         [nsamples] "+r" (nsamples),
+                         [position] "+r" (position)
+                       :
+                         [perm]      "r" (big_endian ? perm_be : perm_le)
+                       : "cc", "memory", "d0", "d1", "d2", "d3", "d4",
+                         "d5", "d6", "d7", "d16", "d17", "d18", "d19");
+       }
+       return position;
+}
+
+static SBC_ALWAYS_INLINE int sbc_enc_process_input_8s_neon_internal(
+       int position,
+       const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE],
+       int nsamples, int nchannels, int big_endian)
+{
+       static SBC_ALIGNED uint8_t perm_be[4][8] = {
+               PERM_BE(15, 7, 14, 8),
+               PERM_BE(13, 9, 12, 10),
+               PERM_BE(11, 3, 6,  0),
+               PERM_BE(5,  1, 4,  2)
+       };
+       static SBC_ALIGNED uint8_t perm_le[4][8] = {
+               PERM_LE(15, 7, 14, 8),
+               PERM_LE(13, 9, 12, 10),
+               PERM_LE(11, 3, 6,  0),
+               PERM_LE(5,  1, 4,  2)
+       };
+       /* handle X buffer wraparound */
+       if (position < nsamples) {
+               int16_t *dst = &X[0][SBC_X_BUFFER_SIZE - 72];
+               int16_t *src = &X[0][position];
+               asm volatile (
+                       "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                       "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                       "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                       "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                       "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                       "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                       "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                       "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                       "vld1.16 {d0, d1}, [%[src], :128]!\n"
+                       "vst1.16 {d0, d1}, [%[dst], :128]!\n"
+                       :
+                         [dst] "+r" (dst),
+                         [src] "+r" (src)
+                       : : "memory", "d0", "d1", "d2", "d3");
+               if (nchannels > 1) {
+                       dst = &X[1][SBC_X_BUFFER_SIZE - 72];
+                       src = &X[1][position];
+                       asm volatile (
+                               "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                               "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                               "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                               "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                               "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                               "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                               "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n"
+                               "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n"
+                               "vld1.16 {d0, d1}, [%[src], :128]!\n"
+                               "vst1.16 {d0, d1}, [%[dst], :128]!\n"
+                               :
+                                 [dst] "+r" (dst),
+                                 [src] "+r" (src)
+                               : : "memory", "d0", "d1", "d2", "d3");
+               }
+               position = SBC_X_BUFFER_SIZE - 72;
+       }
+
+       if ((nchannels > 1) && ((uintptr_t)pcm & 1)) {
+               /* poor 'pcm' alignment */
+               int16_t *x = &X[0][position];
+               int16_t *y = &X[1][position];
+               asm volatile (
+                       "vld1.8  {d0, d1, d2, d3}, [%[perm], :128]\n"
+               "1:\n"
+                       "sub     %[x], %[x], #32\n"
+                       "sub     %[y], %[y], #32\n"
+                       "sub     %[position], %[position], #16\n"
+                       "vld1.8  {d4, d5, d6, d7}, [%[pcm]]!\n"
+                       "vuzp.16 q2,  q3\n"
+                       "vld1.8  {d20, d21, d22, d23}, [%[pcm]]!\n"
+                       "vuzp.16 q10, q11\n"
+                       "vswp    q3,  q10\n"
+                       "vtbl.8  d16, {d4, d5, d6, d7}, d0\n"
+                       "vtbl.8  d17, {d4, d5, d6, d7}, d1\n"
+                       "vtbl.8  d18, {d4, d5, d6, d7}, d2\n"
+                       "vtbl.8  d19, {d4, d5, d6, d7}, d3\n"
+                       "vst1.16 {d16, d17, d18, d19}, [%[x], :128]\n"
+                       "vtbl.8  d16, {d20, d21, d22, d23}, d0\n"
+                       "vtbl.8  d17, {d20, d21, d22, d23}, d1\n"
+                       "vtbl.8  d18, {d20, d21, d22, d23}, d2\n"
+                       "vtbl.8  d19, {d20, d21, d22, d23}, d3\n"
+                       "vst1.16 {d16, d17, d18, d19}, [%[y], :128]\n"
+                       "subs    %[nsamples], %[nsamples], #16\n"
+                       "bgt     1b\n"
+                       :
+                         [x]        "+r" (x),
+                         [y]        "+r" (y),
+                         [pcm]      "+r" (pcm),
+                         [nsamples] "+r" (nsamples),
+                         [position] "+r" (position)
+                       :
+                         [perm]      "r" (big_endian ? perm_be : perm_le)
+                       : "cc", "memory", "d0", "d1", "d2", "d3", "d4",
+                         "d5", "d6", "d7", "d16", "d17", "d18", "d19",
+                         "d20", "d21", "d22", "d23");
+       } else if (nchannels > 1) {
+               /* proper 'pcm' alignment */
+               int16_t *x = &X[0][position];
+               int16_t *y = &X[1][position];
+               asm volatile (
+                       "vld1.8  {d0, d1, d2, d3}, [%[perm], :128]\n"
+               "1:\n"
+                       "sub     %[x], %[x], #32\n"
+                       "sub     %[y], %[y], #32\n"
+                       "sub     %[position], %[position], #16\n"
+                       "vld2.16  {d4, d5, d6, d7}, [%[pcm]]!\n"
+                       "vld2.16  {d20, d21, d22, d23}, [%[pcm]]!\n"
+                       "vswp    q3, q10\n"
+                       "vtbl.8  d16, {d4, d5, d6, d7}, d0\n"
+                       "vtbl.8  d17, {d4, d5, d6, d7}, d1\n"
+                       "vtbl.8  d18, {d4, d5, d6, d7}, d2\n"
+                       "vtbl.8  d19, {d4, d5, d6, d7}, d3\n"
+                       "vst1.16 {d16, d17, d18, d19}, [%[x], :128]\n"
+                       "vtbl.8  d16, {d20, d21, d22, d23}, d0\n"
+                       "vtbl.8  d17, {d20, d21, d22, d23}, d1\n"
+                       "vtbl.8  d18, {d20, d21, d22, d23}, d2\n"
+                       "vtbl.8  d19, {d20, d21, d22, d23}, d3\n"
+                       "vst1.16 {d16, d17, d18, d19}, [%[y], :128]\n"
+                       "subs    %[nsamples], %[nsamples], #16\n"
+                       "bgt     1b\n"
+                       :
+                         [x]        "+r" (x),
+                         [y]        "+r" (y),
+                         [pcm]      "+r" (pcm),
+                         [nsamples] "+r" (nsamples),
+                         [position] "+r" (position)
+                       :
+                         [perm]      "r" (big_endian ? perm_be : perm_le)
+                       : "cc", "memory", "d0", "d1", "d2", "d3", "d4",
+                         "d5", "d6", "d7", "d16", "d17", "d18", "d19",
+                         "d20", "d21", "d22", "d23");
+       } else {
+               int16_t *x = &X[0][position];
+               asm volatile (
+                       "vld1.8  {d0, d1, d2, d3}, [%[perm], :128]\n"
+               "1:\n"
+                       "sub     %[x], %[x], #32\n"
+                       "sub     %[position], %[position], #16\n"
+                       "vld1.8  {d4, d5, d6, d7}, [%[pcm]]!\n"
+                       "vtbl.8  d16, {d4, d5, d6, d7}, d0\n"
+                       "vtbl.8  d17, {d4, d5, d6, d7}, d1\n"
+                       "vtbl.8  d18, {d4, d5, d6, d7}, d2\n"
+                       "vtbl.8  d19, {d4, d5, d6, d7}, d3\n"
+                       "vst1.16 {d16, d17, d18, d19}, [%[x], :128]\n"
+                       "subs    %[nsamples], %[nsamples], #16\n"
+                       "bgt     1b\n"
+                       :
+                         [x]        "+r" (x),
+                         [pcm]      "+r" (pcm),
+                         [nsamples] "+r" (nsamples),
+                         [position] "+r" (position)
+                       :
+                         [perm]      "r" (big_endian ? perm_be : perm_le)
+                       : "cc", "memory", "d0", "d1", "d2", "d3", "d4",
+                         "d5", "d6", "d7", "d16", "d17", "d18", "d19");
+       }
+       return position;
+}
+
+#undef PERM_BE
+#undef PERM_LE
+
+static int sbc_enc_process_input_4s_be_neon(int position, const uint8_t *pcm,
+                                       int16_t X[2][SBC_X_BUFFER_SIZE],
+                                       int nsamples, int nchannels)
+{
+       return sbc_enc_process_input_4s_neon_internal(
+               position, pcm, X, nsamples, nchannels, 1);
+}
+
+static int sbc_enc_process_input_4s_le_neon(int position, const uint8_t *pcm,
+                                       int16_t X[2][SBC_X_BUFFER_SIZE],
+                                       int nsamples, int nchannels)
+{
+       return sbc_enc_process_input_4s_neon_internal(
+               position, pcm, X, nsamples, nchannels, 0);
+}
+
+static int sbc_enc_process_input_8s_be_neon(int position, const uint8_t *pcm,
+                                       int16_t X[2][SBC_X_BUFFER_SIZE],
+                                       int nsamples, int nchannels)
+{
+       return sbc_enc_process_input_8s_neon_internal(
+               position, pcm, X, nsamples, nchannels, 1);
+}
+
+static int sbc_enc_process_input_8s_le_neon(int position, const uint8_t *pcm,
+                                       int16_t X[2][SBC_X_BUFFER_SIZE],
+                                       int nsamples, int nchannels)
+{
+       return sbc_enc_process_input_8s_neon_internal(
+               position, pcm, X, nsamples, nchannels, 0);
+}
+
+void sbc_init_primitives_neon(struct sbc_encoder_state *state)
+{
+       state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_neon;
+       state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_neon;
+       state->sbc_calc_scalefactors = sbc_calc_scalefactors_neon;
+       state->sbc_calc_scalefactors_j = sbc_calc_scalefactors_j_neon;
+       state->sbc_enc_process_input_4s_le = sbc_enc_process_input_4s_le_neon;
+       state->sbc_enc_process_input_4s_be = sbc_enc_process_input_4s_be_neon;
+       state->sbc_enc_process_input_8s_le = sbc_enc_process_input_8s_le_neon;
+       state->sbc_enc_process_input_8s_be = sbc_enc_process_input_8s_be_neon;
+       state->implementation_info = "NEON";
+}
+
+#endif
diff --git a/sbc/sbc_primitives_neon.h b/sbc/sbc_primitives_neon.h
new file mode 100644 (file)
index 0000000..ea3da06
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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 __SBC_PRIMITIVES_NEON_H
+#define __SBC_PRIMITIVES_NEON_H
+
+#include "sbc_primitives.h"
+
+#if defined(__GNUC__) && defined(__ARM_NEON__) && \
+               !defined(SBC_HIGH_PRECISION) && (SCALE_OUT_BITS == 15)
+
+#define SBC_BUILD_WITH_NEON_SUPPORT
+
+void sbc_init_primitives_neon(struct sbc_encoder_state *encoder_state);
+
+#endif
+
+#endif
diff --git a/sbc/sbc_tables.h b/sbc/sbc_tables.h
new file mode 100644 (file)
index 0000000..28c0d54
--- /dev/null
@@ -0,0 +1,660 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *
+ *
+ *  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
+ *
+ */
+
+/* A2DP specification: Appendix B, page 69 */
+static const int sbc_offset4[4][4] = {
+       { -1, 0, 0, 0 },
+       { -2, 0, 0, 1 },
+       { -2, 0, 0, 1 },
+       { -2, 0, 0, 1 }
+};
+
+/* A2DP specification: Appendix B, page 69 */
+static const int sbc_offset8[4][8] = {
+       { -2, 0, 0, 0, 0, 0, 0, 1 },
+       { -3, 0, 0, 0, 0, 0, 1, 2 },
+       { -4, 0, 0, 0, 0, 0, 1, 2 },
+       { -4, 0, 0, 0, 0, 0, 1, 2 }
+};
+
+
+#define SS4(val) ASR(val, SCALE_SPROTO4_TBL)
+#define SS8(val) ASR(val, SCALE_SPROTO8_TBL)
+#define SN4(val) ASR(val, SCALE_NPROTO4_TBL)
+#define SN8(val) ASR(val, SCALE_NPROTO8_TBL)
+
+static const int32_t sbc_proto_4_40m0[] = {
+       SS4(0x00000000), SS4(0xffa6982f), SS4(0xfba93848), SS4(0x0456c7b8),
+       SS4(0x005967d1), SS4(0xfffb9ac7), SS4(0xff589157), SS4(0xf9c2a8d8),
+       SS4(0x027c1434), SS4(0x0019118b), SS4(0xfff3c74c), SS4(0xff137330),
+       SS4(0xf81b8d70), SS4(0x00ec1b8b), SS4(0xfff0b71a), SS4(0xffe99b00),
+       SS4(0xfef84470), SS4(0xf6fb4370), SS4(0xffcdc351), SS4(0xffe01dc7)
+};
+
+static const int32_t sbc_proto_4_40m1[] = {
+       SS4(0xffe090ce), SS4(0xff2c0475), SS4(0xf694f800), SS4(0xff2c0475),
+       SS4(0xffe090ce), SS4(0xffe01dc7), SS4(0xffcdc351), SS4(0xf6fb4370),
+       SS4(0xfef84470), SS4(0xffe99b00), SS4(0xfff0b71a), SS4(0x00ec1b8b),
+       SS4(0xf81b8d70), SS4(0xff137330), SS4(0xfff3c74c), SS4(0x0019118b),
+       SS4(0x027c1434), SS4(0xf9c2a8d8), SS4(0xff589157), SS4(0xfffb9ac7)
+};
+
+static const int32_t sbc_proto_8_80m0[] = {
+       SS8(0x00000000), SS8(0xfe8d1970), SS8(0xee979f00), SS8(0x11686100),
+       SS8(0x0172e690), SS8(0xfff5bd1a), SS8(0xfdf1c8d4), SS8(0xeac182c0),
+       SS8(0x0d9daee0), SS8(0x00e530da), SS8(0xffe9811d), SS8(0xfd52986c),
+       SS8(0xe7054ca0), SS8(0x0a00d410), SS8(0x006c1de4), SS8(0xffdba705),
+       SS8(0xfcbc98e8), SS8(0xe3889d20), SS8(0x06af2308), SS8(0x000bb7db),
+       SS8(0xffca00ed), SS8(0xfc3fbb68), SS8(0xe071bc00), SS8(0x03bf7948),
+       SS8(0xffc4e05c), SS8(0xffb54b3b), SS8(0xfbedadc0), SS8(0xdde26200),
+       SS8(0x0142291c), SS8(0xff960e94), SS8(0xff9f3e17), SS8(0xfbd8f358),
+       SS8(0xdbf79400), SS8(0xff405e01), SS8(0xff7d4914), SS8(0xff8b1a31),
+       SS8(0xfc1417b8), SS8(0xdac7bb40), SS8(0xfdbb828c), SS8(0xff762170)
+};
+
+static const int32_t sbc_proto_8_80m1[] = {
+       SS8(0xff7c272c), SS8(0xfcb02620), SS8(0xda612700), SS8(0xfcb02620),
+       SS8(0xff7c272c), SS8(0xff762170), SS8(0xfdbb828c), SS8(0xdac7bb40),
+       SS8(0xfc1417b8), SS8(0xff8b1a31), SS8(0xff7d4914), SS8(0xff405e01),
+       SS8(0xdbf79400), SS8(0xfbd8f358), SS8(0xff9f3e17), SS8(0xff960e94),
+       SS8(0x0142291c), SS8(0xdde26200), SS8(0xfbedadc0), SS8(0xffb54b3b),
+       SS8(0xffc4e05c), SS8(0x03bf7948), SS8(0xe071bc00), SS8(0xfc3fbb68),
+       SS8(0xffca00ed), SS8(0x000bb7db), SS8(0x06af2308), SS8(0xe3889d20),
+       SS8(0xfcbc98e8), SS8(0xffdba705), SS8(0x006c1de4), SS8(0x0a00d410),
+       SS8(0xe7054ca0), SS8(0xfd52986c), SS8(0xffe9811d), SS8(0x00e530da),
+       SS8(0x0d9daee0), SS8(0xeac182c0), SS8(0xfdf1c8d4), SS8(0xfff5bd1a)
+};
+
+static const int32_t synmatrix4[8][4] = {
+       { SN4(0x05a82798), SN4(0xfa57d868), SN4(0xfa57d868), SN4(0x05a82798) },
+       { SN4(0x030fbc54), SN4(0xf89be510), SN4(0x07641af0), SN4(0xfcf043ac) },
+       { SN4(0x00000000), SN4(0x00000000), SN4(0x00000000), SN4(0x00000000) },
+       { SN4(0xfcf043ac), SN4(0x07641af0), SN4(0xf89be510), SN4(0x030fbc54) },
+       { SN4(0xfa57d868), SN4(0x05a82798), SN4(0x05a82798), SN4(0xfa57d868) },
+       { SN4(0xf89be510), SN4(0xfcf043ac), SN4(0x030fbc54), SN4(0x07641af0) },
+       { SN4(0xf8000000), SN4(0xf8000000), SN4(0xf8000000), SN4(0xf8000000) },
+       { SN4(0xf89be510), SN4(0xfcf043ac), SN4(0x030fbc54), SN4(0x07641af0) }
+};
+
+static const int32_t synmatrix8[16][8] = {
+       { SN8(0x05a82798), SN8(0xfa57d868), SN8(0xfa57d868), SN8(0x05a82798),
+         SN8(0x05a82798), SN8(0xfa57d868), SN8(0xfa57d868), SN8(0x05a82798) },
+       { SN8(0x0471ced0), SN8(0xf8275a10), SN8(0x018f8b84), SN8(0x06a6d988),
+         SN8(0xf9592678), SN8(0xfe70747c), SN8(0x07d8a5f0), SN8(0xfb8e3130) },
+       { SN8(0x030fbc54), SN8(0xf89be510), SN8(0x07641af0), SN8(0xfcf043ac),
+         SN8(0xfcf043ac), SN8(0x07641af0), SN8(0xf89be510), SN8(0x030fbc54) },
+       { SN8(0x018f8b84), SN8(0xfb8e3130), SN8(0x06a6d988), SN8(0xf8275a10),
+         SN8(0x07d8a5f0), SN8(0xf9592678), SN8(0x0471ced0), SN8(0xfe70747c) },
+       { SN8(0x00000000), SN8(0x00000000), SN8(0x00000000), SN8(0x00000000),
+         SN8(0x00000000), SN8(0x00000000), SN8(0x00000000), SN8(0x00000000) },
+       { SN8(0xfe70747c), SN8(0x0471ced0), SN8(0xf9592678), SN8(0x07d8a5f0),
+         SN8(0xf8275a10), SN8(0x06a6d988), SN8(0xfb8e3130), SN8(0x018f8b84) },
+       { SN8(0xfcf043ac), SN8(0x07641af0), SN8(0xf89be510), SN8(0x030fbc54),
+         SN8(0x030fbc54), SN8(0xf89be510), SN8(0x07641af0), SN8(0xfcf043ac) },
+       { SN8(0xfb8e3130), SN8(0x07d8a5f0), SN8(0xfe70747c), SN8(0xf9592678),
+         SN8(0x06a6d988), SN8(0x018f8b84), SN8(0xf8275a10), SN8(0x0471ced0) },
+       { SN8(0xfa57d868), SN8(0x05a82798), SN8(0x05a82798), SN8(0xfa57d868),
+         SN8(0xfa57d868), SN8(0x05a82798), SN8(0x05a82798), SN8(0xfa57d868) },
+       { SN8(0xf9592678), SN8(0x018f8b84), SN8(0x07d8a5f0), SN8(0x0471ced0),
+         SN8(0xfb8e3130), SN8(0xf8275a10), SN8(0xfe70747c), SN8(0x06a6d988) },
+       { SN8(0xf89be510), SN8(0xfcf043ac), SN8(0x030fbc54), SN8(0x07641af0),
+         SN8(0x07641af0), SN8(0x030fbc54), SN8(0xfcf043ac), SN8(0xf89be510) },
+       { SN8(0xf8275a10), SN8(0xf9592678), SN8(0xfb8e3130), SN8(0xfe70747c),
+         SN8(0x018f8b84), SN8(0x0471ced0), SN8(0x06a6d988), SN8(0x07d8a5f0) },
+       { SN8(0xf8000000), SN8(0xf8000000), SN8(0xf8000000), SN8(0xf8000000),
+         SN8(0xf8000000), SN8(0xf8000000), SN8(0xf8000000), SN8(0xf8000000) },
+       { SN8(0xf8275a10), SN8(0xf9592678), SN8(0xfb8e3130), SN8(0xfe70747c),
+         SN8(0x018f8b84), SN8(0x0471ced0), SN8(0x06a6d988), SN8(0x07d8a5f0) },
+       { SN8(0xf89be510), SN8(0xfcf043ac), SN8(0x030fbc54), SN8(0x07641af0),
+         SN8(0x07641af0), SN8(0x030fbc54), SN8(0xfcf043ac), SN8(0xf89be510) },
+       { SN8(0xf9592678), SN8(0x018f8b84), SN8(0x07d8a5f0), SN8(0x0471ced0),
+         SN8(0xfb8e3130), SN8(0xf8275a10), SN8(0xfe70747c), SN8(0x06a6d988) }
+};
+
+/* Uncomment the following line to enable high precision build of SBC encoder */
+
+/* #define SBC_HIGH_PRECISION */
+
+#ifdef SBC_HIGH_PRECISION
+#define FIXED_A int64_t /* data type for fixed point accumulator */
+#define FIXED_T int32_t /* data type for fixed point constants */
+#define SBC_FIXED_EXTRA_BITS 16
+#else
+#define FIXED_A int32_t /* data type for fixed point accumulator */
+#define FIXED_T int16_t /* data type for fixed point constants */
+#define SBC_FIXED_EXTRA_BITS 0
+#endif
+
+/* A2DP specification: Section 12.8 Tables
+ *
+ * Original values are premultiplied by 2 for better precision (that is the
+ * maximum which is possible without overflows)
+ *
+ * Note: in each block of 8 numbers sign was changed for elements 2 and 7
+ * in order to compensate the same change applied to cos_table_fixed_4
+ */
+#define SBC_PROTO_FIXED4_SCALE \
+       ((sizeof(FIXED_T) * CHAR_BIT - 1) - SBC_FIXED_EXTRA_BITS + 1)
+#define F_PROTO4(x) (FIXED_A) ((x * 2) * \
+       ((FIXED_A) 1 << (sizeof(FIXED_T) * CHAR_BIT - 1)) + 0.5)
+#define F(x) F_PROTO4(x)
+static const FIXED_T _sbc_proto_fixed4[40] = {
+       F(0.00000000E+00),  F(5.36548976E-04),
+       -F(1.49188357E-03),  F(2.73370904E-03),
+       F(3.83720193E-03),  F(3.89205149E-03),
+       F(1.86581691E-03),  F(3.06012286E-03),
+
+       F(1.09137620E-02),  F(2.04385087E-02),
+       -F(2.88757392E-02),  F(3.21939290E-02),
+       F(2.58767811E-02),  F(6.13245186E-03),
+       -F(2.88217274E-02),  F(7.76463494E-02),
+
+       F(1.35593274E-01),  F(1.94987841E-01),
+       -F(2.46636662E-01),  F(2.81828203E-01),
+       F(2.94315332E-01),  F(2.81828203E-01),
+       F(2.46636662E-01), -F(1.94987841E-01),
+
+       -F(1.35593274E-01), -F(7.76463494E-02),
+       F(2.88217274E-02),  F(6.13245186E-03),
+       F(2.58767811E-02),  F(3.21939290E-02),
+       F(2.88757392E-02), -F(2.04385087E-02),
+
+       -F(1.09137620E-02), -F(3.06012286E-03),
+       -F(1.86581691E-03),  F(3.89205149E-03),
+       F(3.83720193E-03),  F(2.73370904E-03),
+       F(1.49188357E-03), -F(5.36548976E-04),
+};
+#undef F
+
+/*
+ * To produce this cosine matrix in Octave:
+ *
+ * b = zeros(4, 8);
+ * for i = 0:3
+ * for j = 0:7 b(i+1, j+1) = cos((i + 0.5) * (j - 2) * (pi/4))
+ * endfor
+ * endfor;
+ * printf("%.10f, ", b');
+ *
+ * Note: in each block of 8 numbers sign was changed for elements 2 and 7
+ *
+ * Change of sign for element 2 allows to replace constant 1.0 (not
+ * representable in Q15 format) with -1.0 (fine with Q15).
+ * Changed sign for element 7 allows to have more similar constants
+ * and simplify subband filter function code.
+ */
+#define SBC_COS_TABLE_FIXED4_SCALE \
+       ((sizeof(FIXED_T) * CHAR_BIT - 1) + SBC_FIXED_EXTRA_BITS)
+#define F_COS4(x) (FIXED_A) ((x) * \
+       ((FIXED_A) 1 << (sizeof(FIXED_T) * CHAR_BIT - 1)) + 0.5)
+#define F(x) F_COS4(x)
+static const FIXED_T cos_table_fixed_4[32] = {
+       F(0.7071067812),  F(0.9238795325), -F(1.0000000000),  F(0.9238795325),
+       F(0.7071067812),  F(0.3826834324),  F(0.0000000000),  F(0.3826834324),
+
+       -F(0.7071067812),  F(0.3826834324), -F(1.0000000000),  F(0.3826834324),
+       -F(0.7071067812), -F(0.9238795325), -F(0.0000000000), -F(0.9238795325),
+
+       -F(0.7071067812), -F(0.3826834324), -F(1.0000000000), -F(0.3826834324),
+       -F(0.7071067812),  F(0.9238795325),  F(0.0000000000),  F(0.9238795325),
+
+       F(0.7071067812), -F(0.9238795325), -F(1.0000000000), -F(0.9238795325),
+       F(0.7071067812), -F(0.3826834324), -F(0.0000000000), -F(0.3826834324),
+};
+#undef F
+
+/* A2DP specification: Section 12.8 Tables
+ *
+ * Original values are premultiplied by 4 for better precision (that is the
+ * maximum which is possible without overflows)
+ *
+ * Note: in each block of 16 numbers sign was changed for elements 4, 13, 14, 15
+ * in order to compensate the same change applied to cos_table_fixed_8
+ */
+#define SBC_PROTO_FIXED8_SCALE \
+       ((sizeof(FIXED_T) * CHAR_BIT - 1) - SBC_FIXED_EXTRA_BITS + 1)
+#define F_PROTO8(x) (FIXED_A) ((x * 2) * \
+       ((FIXED_A) 1 << (sizeof(FIXED_T) * CHAR_BIT - 1)) + 0.5)
+#define F(x) F_PROTO8(x)
+static const FIXED_T _sbc_proto_fixed8[80] = {
+       F(0.00000000E+00),  F(1.56575398E-04),
+       F(3.43256425E-04),  F(5.54620202E-04),
+       -F(8.23919506E-04),  F(1.13992507E-03),
+       F(1.47640169E-03),  F(1.78371725E-03),
+       F(2.01182542E-03),  F(2.10371989E-03),
+       F(1.99454554E-03),  F(1.61656283E-03),
+       F(9.02154502E-04),  F(1.78805361E-04),
+       F(1.64973098E-03),  F(3.49717454E-03),
+
+       F(5.65949473E-03),  F(8.02941163E-03),
+       F(1.04584443E-02),  F(1.27472335E-02),
+       -F(1.46525263E-02),  F(1.59045603E-02),
+       F(1.62208471E-02),  F(1.53184106E-02),
+       F(1.29371806E-02),  F(8.85757540E-03),
+       F(2.92408442E-03), -F(4.91578024E-03),
+       -F(1.46404076E-02),  F(2.61098752E-02),
+       F(3.90751381E-02),  F(5.31873032E-02),
+
+       F(6.79989431E-02),  F(8.29847578E-02),
+       F(9.75753918E-02),  F(1.11196689E-01),
+       -F(1.23264548E-01),  F(1.33264415E-01),
+       F(1.40753505E-01),  F(1.45389847E-01),
+       F(1.46955068E-01),  F(1.45389847E-01),
+       F(1.40753505E-01),  F(1.33264415E-01),
+       F(1.23264548E-01), -F(1.11196689E-01),
+       -F(9.75753918E-02), -F(8.29847578E-02),
+
+       -F(6.79989431E-02), -F(5.31873032E-02),
+       -F(3.90751381E-02), -F(2.61098752E-02),
+       F(1.46404076E-02), -F(4.91578024E-03),
+       F(2.92408442E-03),  F(8.85757540E-03),
+       F(1.29371806E-02),  F(1.53184106E-02),
+       F(1.62208471E-02),  F(1.59045603E-02),
+       F(1.46525263E-02), -F(1.27472335E-02),
+       -F(1.04584443E-02), -F(8.02941163E-03),
+
+       -F(5.65949473E-03), -F(3.49717454E-03),
+       -F(1.64973098E-03), -F(1.78805361E-04),
+       -F(9.02154502E-04),  F(1.61656283E-03),
+       F(1.99454554E-03),  F(2.10371989E-03),
+       F(2.01182542E-03),  F(1.78371725E-03),
+       F(1.47640169E-03),  F(1.13992507E-03),
+       F(8.23919506E-04), -F(5.54620202E-04),
+       -F(3.43256425E-04), -F(1.56575398E-04),
+};
+#undef F
+
+/*
+ * To produce this cosine matrix in Octave:
+ *
+ * b = zeros(8, 16);
+ * for i = 0:7
+ * for j = 0:15 b(i+1, j+1) = cos((i + 0.5) * (j - 4) * (pi/8))
+ * endfor endfor;
+ * printf("%.10f, ", b');
+ *
+ * Note: in each block of 16 numbers sign was changed for elements 4, 13, 14, 15
+ *
+ * Change of sign for element 4 allows to replace constant 1.0 (not
+ * representable in Q15 format) with -1.0 (fine with Q15).
+ * Changed signs for elements 13, 14, 15 allow to have more similar constants
+ * and simplify subband filter function code.
+ */
+#define SBC_COS_TABLE_FIXED8_SCALE \
+       ((sizeof(FIXED_T) * CHAR_BIT - 1) + SBC_FIXED_EXTRA_BITS)
+#define F_COS8(x) (FIXED_A) ((x) * \
+       ((FIXED_A) 1 << (sizeof(FIXED_T) * CHAR_BIT - 1)) + 0.5)
+#define F(x) F_COS8(x)
+static const FIXED_T cos_table_fixed_8[128] = {
+       F(0.7071067812),  F(0.8314696123),  F(0.9238795325),  F(0.9807852804),
+       -F(1.0000000000),  F(0.9807852804),  F(0.9238795325),  F(0.8314696123),
+       F(0.7071067812),  F(0.5555702330),  F(0.3826834324),  F(0.1950903220),
+       F(0.0000000000),  F(0.1950903220),  F(0.3826834324),  F(0.5555702330),
+
+       -F(0.7071067812), -F(0.1950903220),  F(0.3826834324),  F(0.8314696123),
+       -F(1.0000000000),  F(0.8314696123),  F(0.3826834324), -F(0.1950903220),
+       -F(0.7071067812), -F(0.9807852804), -F(0.9238795325), -F(0.5555702330),
+       -F(0.0000000000), -F(0.5555702330), -F(0.9238795325), -F(0.9807852804),
+
+       -F(0.7071067812), -F(0.9807852804), -F(0.3826834324),  F(0.5555702330),
+       -F(1.0000000000),  F(0.5555702330), -F(0.3826834324), -F(0.9807852804),
+       -F(0.7071067812),  F(0.1950903220),  F(0.9238795325),  F(0.8314696123),
+       F(0.0000000000),  F(0.8314696123),  F(0.9238795325),  F(0.1950903220),
+
+       F(0.7071067812), -F(0.5555702330), -F(0.9238795325),  F(0.1950903220),
+       -F(1.0000000000),  F(0.1950903220), -F(0.9238795325), -F(0.5555702330),
+       F(0.7071067812),  F(0.8314696123), -F(0.3826834324), -F(0.9807852804),
+       -F(0.0000000000), -F(0.9807852804), -F(0.3826834324),  F(0.8314696123),
+
+       F(0.7071067812),  F(0.5555702330), -F(0.9238795325), -F(0.1950903220),
+       -F(1.0000000000), -F(0.1950903220), -F(0.9238795325),  F(0.5555702330),
+       F(0.7071067812), -F(0.8314696123), -F(0.3826834324),  F(0.9807852804),
+       F(0.0000000000),  F(0.9807852804), -F(0.3826834324), -F(0.8314696123),
+
+       -F(0.7071067812),  F(0.9807852804), -F(0.3826834324), -F(0.5555702330),
+       -F(1.0000000000), -F(0.5555702330), -F(0.3826834324),  F(0.9807852804),
+       -F(0.7071067812), -F(0.1950903220),  F(0.9238795325), -F(0.8314696123),
+       -F(0.0000000000), -F(0.8314696123),  F(0.9238795325), -F(0.1950903220),
+
+       -F(0.7071067812),  F(0.1950903220),  F(0.3826834324), -F(0.8314696123),
+       -F(1.0000000000), -F(0.8314696123),  F(0.3826834324),  F(0.1950903220),
+       -F(0.7071067812),  F(0.9807852804), -F(0.9238795325),  F(0.5555702330),
+       -F(0.0000000000),  F(0.5555702330), -F(0.9238795325),  F(0.9807852804),
+
+       F(0.7071067812), -F(0.8314696123),  F(0.9238795325), -F(0.9807852804),
+       -F(1.0000000000), -F(0.9807852804),  F(0.9238795325), -F(0.8314696123),
+       F(0.7071067812), -F(0.5555702330),  F(0.3826834324), -F(0.1950903220),
+       -F(0.0000000000), -F(0.1950903220),  F(0.3826834324), -F(0.5555702330),
+};
+#undef F
+
+/*
+ * Enforce 16 byte alignment for the data, which is supposed to be used
+ * with SIMD optimized code.
+ */
+
+#define SBC_ALIGN_BITS 4
+#define SBC_ALIGN_MASK ((1 << (SBC_ALIGN_BITS)) - 1)
+
+#ifdef __GNUC__
+#define SBC_ALIGNED __attribute__((aligned(1 << (SBC_ALIGN_BITS))))
+#else
+#define SBC_ALIGNED
+#endif
+
+/*
+ * Constant tables for the use in SIMD optimized analysis filters
+ * Each table consists of two parts:
+ * 1. reordered "proto" table
+ * 2. reordered "cos" table
+ *
+ * Due to non-symmetrical reordering, separate tables for "even"
+ * and "odd" cases are needed
+ */
+
+static const FIXED_T SBC_ALIGNED analysis_consts_fixed4_simd_even[40 + 16] = {
+#define C0 1.0932568993
+#define C1 1.3056875580
+#define C2 1.3056875580
+#define C3 1.6772280856
+
+#define F(x) F_PROTO4(x)
+        F(0.00000000E+00 * C0),  F(3.83720193E-03 * C0),
+        F(5.36548976E-04 * C1),  F(2.73370904E-03 * C1),
+        F(3.06012286E-03 * C2),  F(3.89205149E-03 * C2),
+        F(0.00000000E+00 * C3), -F(1.49188357E-03 * C3),
+        F(1.09137620E-02 * C0),  F(2.58767811E-02 * C0),
+        F(2.04385087E-02 * C1),  F(3.21939290E-02 * C1),
+        F(7.76463494E-02 * C2),  F(6.13245186E-03 * C2),
+        F(0.00000000E+00 * C3), -F(2.88757392E-02 * C3),
+        F(1.35593274E-01 * C0),  F(2.94315332E-01 * C0),
+        F(1.94987841E-01 * C1),  F(2.81828203E-01 * C1),
+       -F(1.94987841E-01 * C2),  F(2.81828203E-01 * C2),
+        F(0.00000000E+00 * C3), -F(2.46636662E-01 * C3),
+       -F(1.35593274E-01 * C0),  F(2.58767811E-02 * C0),
+       -F(7.76463494E-02 * C1),  F(6.13245186E-03 * C1),
+       -F(2.04385087E-02 * C2),  F(3.21939290E-02 * C2),
+        F(0.00000000E+00 * C3),  F(2.88217274E-02 * C3),
+       -F(1.09137620E-02 * C0),  F(3.83720193E-03 * C0),
+       -F(3.06012286E-03 * C1),  F(3.89205149E-03 * C1),
+       -F(5.36548976E-04 * C2),  F(2.73370904E-03 * C2),
+        F(0.00000000E+00 * C3), -F(1.86581691E-03 * C3),
+#undef F
+#define F(x) F_COS4(x)
+        F(0.7071067812 / C0),  F(0.9238795325 / C1),
+       -F(0.7071067812 / C0),  F(0.3826834324 / C1),
+       -F(0.7071067812 / C0), -F(0.3826834324 / C1),
+        F(0.7071067812 / C0), -F(0.9238795325 / C1),
+        F(0.3826834324 / C2), -F(1.0000000000 / C3),
+       -F(0.9238795325 / C2), -F(1.0000000000 / C3),
+        F(0.9238795325 / C2), -F(1.0000000000 / C3),
+       -F(0.3826834324 / C2), -F(1.0000000000 / C3),
+#undef F
+
+#undef C0
+#undef C1
+#undef C2
+#undef C3
+};
+
+static const FIXED_T SBC_ALIGNED analysis_consts_fixed4_simd_odd[40 + 16] = {
+#define C0 1.3056875580
+#define C1 1.6772280856
+#define C2 1.0932568993
+#define C3 1.3056875580
+
+#define F(x) F_PROTO4(x)
+        F(2.73370904E-03 * C0),  F(5.36548976E-04 * C0),
+       -F(1.49188357E-03 * C1),  F(0.00000000E+00 * C1),
+        F(3.83720193E-03 * C2),  F(1.09137620E-02 * C2),
+        F(3.89205149E-03 * C3),  F(3.06012286E-03 * C3),
+        F(3.21939290E-02 * C0),  F(2.04385087E-02 * C0),
+       -F(2.88757392E-02 * C1),  F(0.00000000E+00 * C1),
+        F(2.58767811E-02 * C2),  F(1.35593274E-01 * C2),
+        F(6.13245186E-03 * C3),  F(7.76463494E-02 * C3),
+        F(2.81828203E-01 * C0),  F(1.94987841E-01 * C0),
+       -F(2.46636662E-01 * C1),  F(0.00000000E+00 * C1),
+        F(2.94315332E-01 * C2), -F(1.35593274E-01 * C2),
+        F(2.81828203E-01 * C3), -F(1.94987841E-01 * C3),
+        F(6.13245186E-03 * C0), -F(7.76463494E-02 * C0),
+        F(2.88217274E-02 * C1),  F(0.00000000E+00 * C1),
+        F(2.58767811E-02 * C2), -F(1.09137620E-02 * C2),
+        F(3.21939290E-02 * C3), -F(2.04385087E-02 * C3),
+        F(3.89205149E-03 * C0), -F(3.06012286E-03 * C0),
+       -F(1.86581691E-03 * C1),  F(0.00000000E+00 * C1),
+        F(3.83720193E-03 * C2),  F(0.00000000E+00 * C2),
+        F(2.73370904E-03 * C3), -F(5.36548976E-04 * C3),
+#undef F
+#define F(x) F_COS4(x)
+        F(0.9238795325 / C0), -F(1.0000000000 / C1),
+        F(0.3826834324 / C0), -F(1.0000000000 / C1),
+       -F(0.3826834324 / C0), -F(1.0000000000 / C1),
+       -F(0.9238795325 / C0), -F(1.0000000000 / C1),
+        F(0.7071067812 / C2),  F(0.3826834324 / C3),
+       -F(0.7071067812 / C2), -F(0.9238795325 / C3),
+       -F(0.7071067812 / C2),  F(0.9238795325 / C3),
+        F(0.7071067812 / C2), -F(0.3826834324 / C3),
+#undef F
+
+#undef C0
+#undef C1
+#undef C2
+#undef C3
+};
+
+static const FIXED_T SBC_ALIGNED analysis_consts_fixed8_simd_even[80 + 64] = {
+#define C0 2.7906148894
+#define C1 2.4270044280
+#define C2 2.8015616024
+#define C3 3.1710363741
+#define C4 2.5377944043
+#define C5 2.4270044280
+#define C6 2.8015616024
+#define C7 3.1710363741
+
+#define F(x) F_PROTO8(x)
+        F(0.00000000E+00 * C0),  F(2.01182542E-03 * C0),
+        F(1.56575398E-04 * C1),  F(1.78371725E-03 * C1),
+        F(3.43256425E-04 * C2),  F(1.47640169E-03 * C2),
+        F(5.54620202E-04 * C3),  F(1.13992507E-03 * C3),
+       -F(8.23919506E-04 * C4),  F(0.00000000E+00 * C4),
+        F(2.10371989E-03 * C5),  F(3.49717454E-03 * C5),
+        F(1.99454554E-03 * C6),  F(1.64973098E-03 * C6),
+        F(1.61656283E-03 * C7),  F(1.78805361E-04 * C7),
+        F(5.65949473E-03 * C0),  F(1.29371806E-02 * C0),
+        F(8.02941163E-03 * C1),  F(1.53184106E-02 * C1),
+        F(1.04584443E-02 * C2),  F(1.62208471E-02 * C2),
+        F(1.27472335E-02 * C3),  F(1.59045603E-02 * C3),
+       -F(1.46525263E-02 * C4),  F(0.00000000E+00 * C4),
+        F(8.85757540E-03 * C5),  F(5.31873032E-02 * C5),
+        F(2.92408442E-03 * C6),  F(3.90751381E-02 * C6),
+       -F(4.91578024E-03 * C7),  F(2.61098752E-02 * C7),
+        F(6.79989431E-02 * C0),  F(1.46955068E-01 * C0),
+        F(8.29847578E-02 * C1),  F(1.45389847E-01 * C1),
+        F(9.75753918E-02 * C2),  F(1.40753505E-01 * C2),
+        F(1.11196689E-01 * C3),  F(1.33264415E-01 * C3),
+       -F(1.23264548E-01 * C4),  F(0.00000000E+00 * C4),
+        F(1.45389847E-01 * C5), -F(8.29847578E-02 * C5),
+        F(1.40753505E-01 * C6), -F(9.75753918E-02 * C6),
+        F(1.33264415E-01 * C7), -F(1.11196689E-01 * C7),
+       -F(6.79989431E-02 * C0),  F(1.29371806E-02 * C0),
+       -F(5.31873032E-02 * C1),  F(8.85757540E-03 * C1),
+       -F(3.90751381E-02 * C2),  F(2.92408442E-03 * C2),
+       -F(2.61098752E-02 * C3), -F(4.91578024E-03 * C3),
+        F(1.46404076E-02 * C4),  F(0.00000000E+00 * C4),
+        F(1.53184106E-02 * C5), -F(8.02941163E-03 * C5),
+        F(1.62208471E-02 * C6), -F(1.04584443E-02 * C6),
+        F(1.59045603E-02 * C7), -F(1.27472335E-02 * C7),
+       -F(5.65949473E-03 * C0),  F(2.01182542E-03 * C0),
+       -F(3.49717454E-03 * C1),  F(2.10371989E-03 * C1),
+       -F(1.64973098E-03 * C2),  F(1.99454554E-03 * C2),
+       -F(1.78805361E-04 * C3),  F(1.61656283E-03 * C3),
+       -F(9.02154502E-04 * C4),  F(0.00000000E+00 * C4),
+        F(1.78371725E-03 * C5), -F(1.56575398E-04 * C5),
+        F(1.47640169E-03 * C6), -F(3.43256425E-04 * C6),
+        F(1.13992507E-03 * C7), -F(5.54620202E-04 * C7),
+#undef F
+#define F(x) F_COS8(x)
+        F(0.7071067812 / C0),  F(0.8314696123 / C1),
+       -F(0.7071067812 / C0), -F(0.1950903220 / C1),
+       -F(0.7071067812 / C0), -F(0.9807852804 / C1),
+        F(0.7071067812 / C0), -F(0.5555702330 / C1),
+        F(0.7071067812 / C0),  F(0.5555702330 / C1),
+       -F(0.7071067812 / C0),  F(0.9807852804 / C1),
+       -F(0.7071067812 / C0),  F(0.1950903220 / C1),
+        F(0.7071067812 / C0), -F(0.8314696123 / C1),
+        F(0.9238795325 / C2),  F(0.9807852804 / C3),
+        F(0.3826834324 / C2),  F(0.8314696123 / C3),
+       -F(0.3826834324 / C2),  F(0.5555702330 / C3),
+       -F(0.9238795325 / C2),  F(0.1950903220 / C3),
+       -F(0.9238795325 / C2), -F(0.1950903220 / C3),
+       -F(0.3826834324 / C2), -F(0.5555702330 / C3),
+        F(0.3826834324 / C2), -F(0.8314696123 / C3),
+        F(0.9238795325 / C2), -F(0.9807852804 / C3),
+       -F(1.0000000000 / C4),  F(0.5555702330 / C5),
+       -F(1.0000000000 / C4), -F(0.9807852804 / C5),
+       -F(1.0000000000 / C4),  F(0.1950903220 / C5),
+       -F(1.0000000000 / C4),  F(0.8314696123 / C5),
+       -F(1.0000000000 / C4), -F(0.8314696123 / C5),
+       -F(1.0000000000 / C4), -F(0.1950903220 / C5),
+       -F(1.0000000000 / C4),  F(0.9807852804 / C5),
+       -F(1.0000000000 / C4), -F(0.5555702330 / C5),
+        F(0.3826834324 / C6),  F(0.1950903220 / C7),
+       -F(0.9238795325 / C6), -F(0.5555702330 / C7),
+        F(0.9238795325 / C6),  F(0.8314696123 / C7),
+       -F(0.3826834324 / C6), -F(0.9807852804 / C7),
+       -F(0.3826834324 / C6),  F(0.9807852804 / C7),
+        F(0.9238795325 / C6), -F(0.8314696123 / C7),
+       -F(0.9238795325 / C6),  F(0.5555702330 / C7),
+        F(0.3826834324 / C6), -F(0.1950903220 / C7),
+#undef F
+
+#undef C0
+#undef C1
+#undef C2
+#undef C3
+#undef C4
+#undef C5
+#undef C6
+#undef C7
+};
+
+static const FIXED_T SBC_ALIGNED analysis_consts_fixed8_simd_odd[80 + 64] = {
+#define C0 2.5377944043
+#define C1 2.4270044280
+#define C2 2.8015616024
+#define C3 3.1710363741
+#define C4 2.7906148894
+#define C5 2.4270044280
+#define C6 2.8015616024
+#define C7 3.1710363741
+
+#define F(x) F_PROTO8(x)
+        F(0.00000000E+00 * C0), -F(8.23919506E-04 * C0),
+        F(1.56575398E-04 * C1),  F(1.78371725E-03 * C1),
+        F(3.43256425E-04 * C2),  F(1.47640169E-03 * C2),
+        F(5.54620202E-04 * C3),  F(1.13992507E-03 * C3),
+        F(2.01182542E-03 * C4),  F(5.65949473E-03 * C4),
+        F(2.10371989E-03 * C5),  F(3.49717454E-03 * C5),
+        F(1.99454554E-03 * C6),  F(1.64973098E-03 * C6),
+        F(1.61656283E-03 * C7),  F(1.78805361E-04 * C7),
+        F(0.00000000E+00 * C0), -F(1.46525263E-02 * C0),
+        F(8.02941163E-03 * C1),  F(1.53184106E-02 * C1),
+        F(1.04584443E-02 * C2),  F(1.62208471E-02 * C2),
+        F(1.27472335E-02 * C3),  F(1.59045603E-02 * C3),
+        F(1.29371806E-02 * C4),  F(6.79989431E-02 * C4),
+        F(8.85757540E-03 * C5),  F(5.31873032E-02 * C5),
+        F(2.92408442E-03 * C6),  F(3.90751381E-02 * C6),
+       -F(4.91578024E-03 * C7),  F(2.61098752E-02 * C7),
+        F(0.00000000E+00 * C0), -F(1.23264548E-01 * C0),
+        F(8.29847578E-02 * C1),  F(1.45389847E-01 * C1),
+        F(9.75753918E-02 * C2),  F(1.40753505E-01 * C2),
+        F(1.11196689E-01 * C3),  F(1.33264415E-01 * C3),
+        F(1.46955068E-01 * C4), -F(6.79989431E-02 * C4),
+        F(1.45389847E-01 * C5), -F(8.29847578E-02 * C5),
+        F(1.40753505E-01 * C6), -F(9.75753918E-02 * C6),
+        F(1.33264415E-01 * C7), -F(1.11196689E-01 * C7),
+        F(0.00000000E+00 * C0),  F(1.46404076E-02 * C0),
+       -F(5.31873032E-02 * C1),  F(8.85757540E-03 * C1),
+       -F(3.90751381E-02 * C2),  F(2.92408442E-03 * C2),
+       -F(2.61098752E-02 * C3), -F(4.91578024E-03 * C3),
+        F(1.29371806E-02 * C4), -F(5.65949473E-03 * C4),
+        F(1.53184106E-02 * C5), -F(8.02941163E-03 * C5),
+        F(1.62208471E-02 * C6), -F(1.04584443E-02 * C6),
+        F(1.59045603E-02 * C7), -F(1.27472335E-02 * C7),
+        F(0.00000000E+00 * C0), -F(9.02154502E-04 * C0),
+       -F(3.49717454E-03 * C1),  F(2.10371989E-03 * C1),
+       -F(1.64973098E-03 * C2),  F(1.99454554E-03 * C2),
+       -F(1.78805361E-04 * C3),  F(1.61656283E-03 * C3),
+        F(2.01182542E-03 * C4),  F(0.00000000E+00 * C4),
+        F(1.78371725E-03 * C5), -F(1.56575398E-04 * C5),
+        F(1.47640169E-03 * C6), -F(3.43256425E-04 * C6),
+        F(1.13992507E-03 * C7), -F(5.54620202E-04 * C7),
+#undef F
+#define F(x) F_COS8(x)
+       -F(1.0000000000 / C0),  F(0.8314696123 / C1),
+       -F(1.0000000000 / C0), -F(0.1950903220 / C1),
+       -F(1.0000000000 / C0), -F(0.9807852804 / C1),
+       -F(1.0000000000 / C0), -F(0.5555702330 / C1),
+       -F(1.0000000000 / C0),  F(0.5555702330 / C1),
+       -F(1.0000000000 / C0),  F(0.9807852804 / C1),
+       -F(1.0000000000 / C0),  F(0.1950903220 / C1),
+       -F(1.0000000000 / C0), -F(0.8314696123 / C1),
+        F(0.9238795325 / C2),  F(0.9807852804 / C3),
+        F(0.3826834324 / C2),  F(0.8314696123 / C3),
+       -F(0.3826834324 / C2),  F(0.5555702330 / C3),
+       -F(0.9238795325 / C2),  F(0.1950903220 / C3),
+       -F(0.9238795325 / C2), -F(0.1950903220 / C3),
+       -F(0.3826834324 / C2), -F(0.5555702330 / C3),
+        F(0.3826834324 / C2), -F(0.8314696123 / C3),
+        F(0.9238795325 / C2), -F(0.9807852804 / C3),
+        F(0.7071067812 / C4),  F(0.5555702330 / C5),
+       -F(0.7071067812 / C4), -F(0.9807852804 / C5),
+       -F(0.7071067812 / C4),  F(0.1950903220 / C5),
+        F(0.7071067812 / C4),  F(0.8314696123 / C5),
+        F(0.7071067812 / C4), -F(0.8314696123 / C5),
+       -F(0.7071067812 / C4), -F(0.1950903220 / C5),
+       -F(0.7071067812 / C4),  F(0.9807852804 / C5),
+        F(0.7071067812 / C4), -F(0.5555702330 / C5),
+        F(0.3826834324 / C6),  F(0.1950903220 / C7),
+       -F(0.9238795325 / C6), -F(0.5555702330 / C7),
+        F(0.9238795325 / C6),  F(0.8314696123 / C7),
+       -F(0.3826834324 / C6), -F(0.9807852804 / C7),
+       -F(0.3826834324 / C6),  F(0.9807852804 / C7),
+        F(0.9238795325 / C6), -F(0.8314696123 / C7),
+       -F(0.9238795325 / C6),  F(0.5555702330 / C7),
+        F(0.3826834324 / C6), -F(0.1950903220 / C7),
+#undef F
+
+#undef C0
+#undef C1
+#undef C2
+#undef C3
+#undef C4
+#undef C5
+#undef C6
+#undef C7
+};
diff --git a/sbc/sbcdec.c b/sbc/sbcdec.c
new file mode 100644 (file)
index 0000000..7008e88
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) decoder
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+#include "sbc.h"
+#include "formats.h"
+
+#define BUF_SIZE 8192
+
+static int verbose = 0;
+
+static void decode(char *filename, char *output, int tofile)
+{
+       unsigned char buf[BUF_SIZE], *stream;
+       struct stat st;
+       sbc_t sbc;
+       int fd, ad, pos, streamlen, framelen, count;
+       size_t len;
+       int format = AFMT_S16_BE, frequency, channels;
+       ssize_t written;
+
+       if (stat(filename, &st) < 0) {
+               fprintf(stderr, "Can't get size of file %s: %s\n",
+                                               filename, strerror(errno));
+               return;
+       }
+
+       stream = malloc(st.st_size);
+
+       if (!stream) {
+               fprintf(stderr, "Can't allocate memory for %s: %s\n",
+                                               filename, strerror(errno));
+               return;
+       }
+
+       fd = open(filename, O_RDONLY);
+       if (fd < 0) {
+               fprintf(stderr, "Can't open file %s: %s\n",
+                                               filename, strerror(errno));
+               goto free;
+       }
+
+       if (read(fd, stream, st.st_size) != st.st_size) {
+               fprintf(stderr, "Can't read content of %s: %s\n",
+                                               filename, strerror(errno));
+               close(fd);
+               goto free;
+       }
+
+       close(fd);
+
+       pos = 0;
+       streamlen = st.st_size;
+
+       if (tofile)
+               ad = open(output, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+       else
+               ad = open(output, O_WRONLY, 0);
+
+       if (ad < 0) {
+               fprintf(stderr, "Can't open output %s: %s\n",
+                                               output, strerror(errno));
+               goto free;
+       }
+
+       sbc_init(&sbc, 0L);
+       sbc.endian = SBC_BE;
+
+       framelen = sbc_decode(&sbc, stream, streamlen, buf, sizeof(buf), &len);
+       channels = sbc.mode == SBC_MODE_MONO ? 1 : 2;
+       switch (sbc.frequency) {
+       case SBC_FREQ_16000:
+               frequency = 16000;
+               break;
+
+       case SBC_FREQ_32000:
+               frequency = 32000;
+               break;
+
+       case SBC_FREQ_44100:
+               frequency = 44100;
+               break;
+
+       case SBC_FREQ_48000:
+               frequency = 48000;
+               break;
+       default:
+               frequency = 0;
+       }
+
+       if (verbose) {
+               fprintf(stderr,"decoding %s with rate %d, %d subbands, "
+                       "%d bits, allocation method %s and mode %s\n",
+                       filename, frequency, sbc.subbands * 4 + 4, sbc.bitpool,
+                       sbc.allocation == SBC_AM_SNR ? "SNR" : "LOUDNESS",
+                       sbc.mode == SBC_MODE_MONO ? "MONO" :
+                                       sbc.mode == SBC_MODE_STEREO ?
+                                               "STEREO" : "JOINTSTEREO");
+       }
+
+       if (tofile) {
+               struct au_header au_hdr;
+
+               au_hdr.magic       = AU_MAGIC;
+               au_hdr.hdr_size    = BE_INT(24);
+               au_hdr.data_size   = BE_INT(0);
+               au_hdr.encoding    = BE_INT(AU_FMT_LIN16);
+               au_hdr.sample_rate = BE_INT(frequency);
+               au_hdr.channels    = BE_INT(channels);
+
+               written = write(ad, &au_hdr, sizeof(au_hdr));
+               if (written < (ssize_t) sizeof(au_hdr)) {
+                       fprintf(stderr, "Failed to write header\n");
+                       goto close;
+               }
+       } else {
+               if (ioctl(ad, SNDCTL_DSP_SETFMT, &format) < 0) {
+                       fprintf(stderr, "Can't set audio format on %s: %s\n",
+                                               output, strerror(errno));
+                       goto close;
+               }
+
+               if (ioctl(ad, SNDCTL_DSP_CHANNELS, &channels) < 0) {
+                       fprintf(stderr, "Can't set number of channels on %s: %s\n",
+                                               output, strerror(errno));
+                       goto close;
+               }
+
+               if (ioctl(ad, SNDCTL_DSP_SPEED, &frequency) < 0) {
+                       fprintf(stderr, "Can't set audio rate on %s: %s\n",
+                                               output, strerror(errno));
+                       goto close;
+               }
+       }
+
+       count = len;
+
+       while (framelen > 0) {
+               /* we have completed an sbc_decode at this point sbc.len is the
+                * length of the frame we just decoded count is the number of
+                * decoded bytes yet to be written */
+
+               if (count + len >= BUF_SIZE) {
+                       /* buffer is too full to stuff decoded audio in so it
+                        * must be written to the device */
+                       written = write(ad, buf, count);
+                       if (written > 0)
+                               count -= written;
+               }
+
+               /* sanity check */
+               if (count + len >= BUF_SIZE) {
+                       fprintf(stderr,
+                               "buffer size of %d is too small for decoded"
+                               " data (%lu)\n", BUF_SIZE, (unsigned long) (len + count));
+                       exit(1);
+               }
+
+               /* push the pointer in the file forward to the next bit to be
+                * decoded tell the decoder to decode up to the remaining
+                * length of the file (!) */
+               pos += framelen;
+               framelen = sbc_decode(&sbc, stream + pos, streamlen - pos,
+                                       buf + count, sizeof(buf) - count, &len);
+
+               /* increase the count */
+               count += len;
+       }
+
+       if (count > 0) {
+               written = write(ad, buf, count);
+               if (written > 0)
+                       count -= written;
+       }
+
+close:
+       sbc_finish(&sbc);
+
+       close(ad);
+
+free:
+       free(stream);
+}
+
+static void usage(void)
+{
+       printf("SBC decoder utility ver %s\n", VERSION);
+       printf("Copyright (c) 2004-2010  Marcel Holtmann\n\n");
+
+       printf("Usage:\n"
+               "\tsbcdec [options] file(s)\n"
+               "\n");
+
+       printf("Options:\n"
+               "\t-h, --help           Display help\n"
+               "\t-v, --verbose        Verbose mode\n"
+               "\t-d, --device <dsp>   Sound device\n"
+               "\t-f, --file <file>    Decode to a file\n"
+               "\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "device",     1, 0, 'd' },
+       { "verbose",    0, 0, 'v' },
+       { "file",       1, 0, 'f' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       char *output = NULL;
+       int i, opt, tofile = 0;
+
+       while ((opt = getopt_long(argc, argv, "+hvd:f:",
+                                               main_options, NULL)) != -1) {
+               switch(opt) {
+               case 'h':
+                       usage();
+                       exit(0);
+
+               case 'v':
+                       verbose = 1;
+                       break;
+
+               case 'd':
+                       free(output);
+                       output = strdup(optarg);
+                       tofile = 0;
+                       break;
+
+               case 'f' :
+                       free(output);
+                       output = strdup(optarg);
+                       tofile = 1;
+                       break;
+
+               default:
+                       exit(1);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       for (i = 0; i < argc; i++)
+               decode(argv[i], output ? output : "/dev/dsp", tofile);
+
+       free(output);
+
+       return 0;
+}
diff --git a/sbc/sbcenc.c b/sbc/sbcenc.c
new file mode 100644 (file)
index 0000000..3d3a7dc
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) encoder
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/stat.h>
+
+#include "sbc.h"
+#include "formats.h"
+
+static int verbose = 0;
+
+#define BUF_SIZE 32768
+static unsigned char input[BUF_SIZE], output[BUF_SIZE + BUF_SIZE / 4];
+
+static void encode(char *filename, int subbands, int bitpool, int joint,
+                                       int dualchannel, int snr, int blocks)
+{
+       struct au_header au_hdr;
+       sbc_t sbc;
+       int fd, size, srate, codesize, nframes;
+       ssize_t encoded;
+       ssize_t len;
+
+       if (sizeof(au_hdr) != 24) {
+               /* Sanity check just in case */
+               fprintf(stderr, "FIXME: sizeof(au_hdr) != 24\n");
+               return;
+       }
+
+       if (strcmp(filename, "-")) {
+               fd = open(filename, O_RDONLY);
+               if (fd < 0) {
+                       fprintf(stderr, "Can't open file %s: %s\n",
+                                               filename, strerror(errno));
+                       return;
+               }
+       } else
+               fd = fileno(stdin);
+
+       len = read(fd, &au_hdr, sizeof(au_hdr));
+       if (len < (ssize_t) sizeof(au_hdr)) {
+               if (fd > fileno(stderr))
+                       fprintf(stderr, "Can't read header from file %s: %s\n",
+                                               filename, strerror(errno));
+               else
+                       perror("Can't read audio header");
+               goto done;
+       }
+
+       if (au_hdr.magic != AU_MAGIC ||
+                       BE_INT(au_hdr.hdr_size) > 128 ||
+                       BE_INT(au_hdr.hdr_size) < sizeof(au_hdr) ||
+                       BE_INT(au_hdr.encoding) != AU_FMT_LIN16) {
+               fprintf(stderr, "Not in Sun/NeXT audio S16_BE format\n");
+               goto done;
+       }
+
+       sbc_init(&sbc, 0L);
+
+       switch (BE_INT(au_hdr.sample_rate)) {
+       case 16000:
+               sbc.frequency = SBC_FREQ_16000;
+               break;
+       case 32000:
+               sbc.frequency = SBC_FREQ_32000;
+               break;
+       case 44100:
+               sbc.frequency = SBC_FREQ_44100;
+               break;
+       case 48000:
+               sbc.frequency = SBC_FREQ_48000;
+               break;
+       }
+
+       srate = BE_INT(au_hdr.sample_rate);
+
+       sbc.subbands = subbands == 4 ? SBC_SB_4 : SBC_SB_8;
+
+       if (BE_INT(au_hdr.channels) == 1) {
+               sbc.mode = SBC_MODE_MONO;
+               if (joint || dualchannel) {
+                       fprintf(stderr, "Audio is mono but joint or "
+                               "dualchannel mode has been specified\n");
+                       goto done;
+               }
+       } else if (joint && !dualchannel)
+               sbc.mode = SBC_MODE_JOINT_STEREO;
+       else if (!joint && dualchannel)
+               sbc.mode = SBC_MODE_DUAL_CHANNEL;
+       else if (!joint && !dualchannel)
+               sbc.mode = SBC_MODE_STEREO;
+       else {
+               fprintf(stderr, "Both joint and dualchannel mode have been "
+                                                               "specified\n");
+               goto done;
+       }
+
+       sbc.endian = SBC_BE;
+       /* Skip extra bytes of the header if any */
+       if (read(fd, input, BE_INT(au_hdr.hdr_size) - len) < 0)
+               goto done;
+
+       sbc.bitpool = bitpool;
+       sbc.allocation = snr ? SBC_AM_SNR : SBC_AM_LOUDNESS;
+       sbc.blocks = blocks == 4 ? SBC_BLK_4 :
+                       blocks == 8 ? SBC_BLK_8 :
+                               blocks == 12 ? SBC_BLK_12 : SBC_BLK_16;
+
+       if (verbose) {
+               fprintf(stderr, "encoding %s with rate %d, %d blocks, "
+                       "%d subbands, %d bits, allocation method %s, "
+                                                       "and mode %s\n",
+                       filename, srate, blocks, subbands, bitpool,
+                       sbc.allocation == SBC_AM_SNR ? "SNR" : "LOUDNESS",
+                       sbc.mode == SBC_MODE_MONO ? "MONO" :
+                                       sbc.mode == SBC_MODE_STEREO ?
+                                               "STEREO" : "JOINTSTEREO");
+       }
+
+       codesize = sbc_get_codesize(&sbc);
+       nframes = sizeof(input) / codesize;
+       while (1) {
+               unsigned char *inp, *outp;
+               /* read data for up to 'nframes' frames of input data */
+               size = read(fd, input, codesize * nframes);
+               if (size < 0) {
+                       /* Something really bad happened */
+                       perror("Can't read audio data");
+                       break;
+               }
+               if (size < codesize) {
+                       /* Not enough data for encoding even a single frame */
+                       break;
+               }
+               /* encode all the data from the input buffer in a loop */
+               inp = input;
+               outp = output;
+               while (size >= codesize) {
+                       len = sbc_encode(&sbc, inp, codesize,
+                               outp, sizeof(output) - (outp - output),
+                               &encoded);
+                       if (len != codesize || encoded <= 0) {
+                               fprintf(stderr,
+                                       "sbc_encode fail, len=%zd, encoded=%lu\n",
+                                       len, (unsigned long) encoded);
+                               break;
+                       }
+                       size -= len;
+                       inp += len;
+                       outp += encoded;
+               }
+               len = write(fileno(stdout), output, outp - output);
+               if (len != outp - output) {
+                       perror("Can't write SBC output");
+                       break;
+               }
+               if (size != 0) {
+                       /*
+                        * sbc_encode failure has been detected earlier or end
+                        * of file reached (have trailing partial data which is
+                        * insufficient to encode SBC frame)
+                        */
+                       break;
+               }
+       }
+
+       sbc_finish(&sbc);
+
+done:
+       if (fd > fileno(stderr))
+               close(fd);
+}
+
+static void usage(void)
+{
+       printf("SBC encoder utility ver %s\n", VERSION);
+       printf("Copyright (c) 2004-2010  Marcel Holtmann\n\n");
+
+       printf("Usage:\n"
+               "\tsbcenc [options] file(s)\n"
+               "\n");
+
+       printf("Options:\n"
+               "\t-h, --help           Display help\n"
+               "\t-v, --verbose        Verbose mode\n"
+               "\t-s, --subbands       Number of subbands to use (4 or 8)\n"
+               "\t-b, --bitpool        Bitpool value (default is 32)\n"
+               "\t-j, --joint          Joint stereo\n"
+               "\t-d, --dualchannel    Dual channel\n"
+               "\t-S, --snr            Use SNR mode (default is loudness)\n"
+               "\t-B, --blocks         Number of blocks (4, 8, 12 or 16)\n"
+               "\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "verbose",    0, 0, 'v' },
+       { "subbands",   1, 0, 's' },
+       { "bitpool",    1, 0, 'b' },
+       { "joint",      0, 0, 'j' },
+       { "dualchannel",0, 0, 'd' },
+       { "snr",        0, 0, 'S' },
+       { "blocks",     1, 0, 'B' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       int i, opt, subbands = 8, bitpool = 32, joint = 0, dualchannel = 0;
+       int snr = 0, blocks = 16;
+
+       while ((opt = getopt_long(argc, argv, "+hvs:b:jdSB:",
+                                               main_options, NULL)) != -1) {
+               switch(opt) {
+               case 'h':
+                       usage();
+                       exit(0);
+
+               case 'v':
+                       verbose = 1;
+                       break;
+
+               case 's':
+                       subbands = atoi(optarg);
+                       if (subbands != 8 && subbands != 4) {
+                               fprintf(stderr, "Invalid subbands\n");
+                               exit(1);
+                       }
+                       break;
+
+               case 'b':
+                       bitpool = atoi(optarg);
+                       break;
+
+               case 'j':
+                       joint = 1;
+                       break;
+
+               case 'd':
+                       dualchannel = 1;
+                       break;
+
+               case 'S':
+                       snr = 1;
+                       break;
+
+               case 'B':
+                       blocks = atoi(optarg);
+                       if (blocks != 16 && blocks != 12 &&
+                                               blocks != 8 && blocks != 4) {
+                               fprintf(stderr, "Invalid blocks\n");
+                               exit(1);
+                       }
+                       break;
+
+               default:
+                       usage();
+                       exit(1);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       for (i = 0; i < argc; i++)
+               encode(argv[i], subbands, bitpool, joint, dualchannel,
+                                                               snr, blocks);
+
+       return 0;
+}
diff --git a/sbc/sbcinfo.c b/sbc/sbcinfo.c
new file mode 100644 (file)
index 0000000..6d92679
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <libgen.h>
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+struct sbc_frame_hdr {
+       uint8_t syncword:8;             /* Sync word */
+       uint8_t subbands:1;             /* Subbands */
+       uint8_t allocation_method:1;    /* Allocation method */
+       uint8_t channel_mode:2;         /* Channel mode */
+       uint8_t blocks:2;               /* Blocks */
+       uint8_t sampling_frequency:2;   /* Sampling frequency */
+       uint8_t bitpool:8;              /* Bitpool */
+       uint8_t crc_check:8;            /* CRC check */
+} __attribute__ ((packed));
+#elif __BYTE_ORDER == __BIG_ENDIAN
+struct sbc_frame_hdr {
+       uint8_t syncword:8;             /* Sync word */
+       uint8_t sampling_frequency:2;   /* Sampling frequency */
+       uint8_t blocks:2;               /* Blocks */
+       uint8_t channel_mode:2;         /* Channel mode */
+       uint8_t allocation_method:1;    /* Allocation method */
+       uint8_t subbands:1;             /* Subbands */
+       uint8_t bitpool:8;              /* Bitpool */
+       uint8_t crc_check:8;            /* CRC check */
+} __attribute__ ((packed));
+#else
+#error "Unknown byte order"
+#endif
+
+static int calc_frame_len(struct sbc_frame_hdr *hdr)
+{
+       int tmp, nrof_subbands, nrof_blocks;
+
+       nrof_subbands = (hdr->subbands + 1) * 4;
+       nrof_blocks = (hdr->blocks + 1) * 4;
+
+       switch (hdr->channel_mode) {
+       case 0x00:
+               nrof_subbands /= 2;
+               tmp = nrof_blocks * hdr->bitpool;
+               break;
+       case 0x01:
+               tmp = nrof_blocks * hdr->bitpool * 2;
+               break;
+       case 0x02:
+               tmp = nrof_blocks * hdr->bitpool;
+               break;
+       case 0x03:
+               tmp = nrof_blocks * hdr->bitpool + nrof_subbands;
+               break;
+       default:
+               return 0;
+       }
+
+       return (nrof_subbands + ((tmp + 7) / 8));
+}
+
+static double calc_bit_rate(struct sbc_frame_hdr *hdr)
+{
+       int nrof_subbands, nrof_blocks;
+       double f;
+
+       nrof_subbands = (hdr->subbands + 1) * 4;
+       nrof_blocks = (hdr->blocks + 1) * 4;
+
+       switch (hdr->sampling_frequency) {
+       case 0:
+               f = 16;
+               break;
+       case 1:
+               f = 32;
+               break;
+       case 2:
+               f = 44.1;
+               break;
+       case 3:
+               f = 48;
+               break;
+       default:
+               return 0;
+       }
+
+       return ((8 * (calc_frame_len(hdr) + 4) * f) /
+                       (nrof_subbands * nrof_blocks));
+}
+
+static char *freq2str(uint8_t freq)
+{
+       switch (freq) {
+       case 0:
+               return "16 kHz";
+       case 1:
+               return "32 kHz";
+       case 2:
+               return "44.1 kHz";
+       case 3:
+               return "48 kHz";
+       default:
+               return "Unknown";
+       }
+}
+
+static char *mode2str(uint8_t mode)
+{
+       switch (mode) {
+       case 0:
+               return "Mono";
+       case 1:
+               return "Dual Channel";
+       case 2:
+               return "Stereo";
+       case 3:
+               return "Joint Stereo";
+       default:
+               return "Unknown";
+       }
+}
+
+static ssize_t __read(int fd, void *buf, size_t count)
+{
+       ssize_t len, pos = 0;
+
+       while (count > 0) {
+               len = read(fd, buf + pos, count);
+               if (len <= 0)
+                       return len;
+
+               count -= len;
+               pos   += len;
+       }
+
+       return pos;
+}
+
+#define SIZE 32
+
+static int analyze_file(char *filename)
+{
+       struct sbc_frame_hdr hdr;
+       unsigned char buf[64];
+       double rate;
+       int bitpool[SIZE], frame_len[SIZE];
+       int subbands, blocks, freq, mode, method;
+       int n, p1, p2, fd, size, num;
+       ssize_t len;
+       unsigned int count;
+
+       if (strcmp(filename, "-")) {
+               printf("Filename\t\t%s\n", basename(filename));
+
+               fd = open(filename, O_RDONLY);
+               if (fd < 0) {
+                       perror("Can't open file");
+                       return -1;
+               }
+       } else
+               fd = fileno(stdin);
+
+       len = __read(fd, &hdr, sizeof(hdr));
+       if (len != sizeof(hdr) || hdr.syncword != 0x9c) {
+               fprintf(stderr, "Not a SBC audio file\n");
+               return -1;
+       }
+
+       subbands = (hdr.subbands + 1) * 4;
+       blocks = (hdr.blocks + 1) * 4;
+       freq = hdr.sampling_frequency;
+       mode = hdr.channel_mode;
+       method = hdr.allocation_method;
+
+       count = calc_frame_len(&hdr);
+
+       bitpool[0] = hdr.bitpool;
+       frame_len[0] = count + 4;
+
+       for (n = 1; n < SIZE; n++) {
+               bitpool[n] = 0;
+               frame_len[n] = 0;
+       }
+
+       if (lseek(fd, 0, SEEK_SET) < 0) {
+               num = 1;
+               rate = calc_bit_rate(&hdr);
+               while (count) {
+                       size = count > sizeof(buf) ? sizeof(buf) : count;
+                       len = __read(fd, buf, size);
+                       if (len < 0)
+                               break;
+                       count -= len;
+               }
+       } else {
+               num = 0;
+               rate = 0;
+       }
+
+       while (1) {
+               len = __read(fd, &hdr, sizeof(hdr));
+               if (len < 0) {
+                       fprintf(stderr, "Unable to read frame header"
+                                       " (error %d)\n", errno);
+                       break;
+               }
+
+               if (len == 0)
+                       break;
+
+               if ((size_t) len < sizeof(hdr) || hdr.syncword != 0x9c) {
+                       fprintf(stderr, "Corrupted SBC stream "
+                                       "(len %zd syncword 0x%02x)\n",
+                                       len, hdr.syncword);
+                       break;
+               }
+
+               count = calc_frame_len(&hdr);
+               len = count + 4;
+
+               p1 = -1;
+               p2 = -1;
+               for (n = 0; n < SIZE; n++) {
+                       if (p1 < 0 && (bitpool[n] == 0 || bitpool[n] == hdr.bitpool))
+                               p1 = n;
+                       if (p2 < 0 && (frame_len[n] == 0 || frame_len[n] == len))
+                               p2 = n;
+               }
+               if (p1 >= 0)
+                       bitpool[p1] = hdr.bitpool;
+               if (p2 >= 0)
+                       frame_len[p2] = len;
+
+               while (count) {
+                       size = count > sizeof(buf) ? sizeof(buf) : count;
+
+                       len = __read(fd, buf, size);
+                       if (len != size) {
+                               fprintf(stderr, "Unable to read frame data "
+                                               "(error %d)\n", errno);
+                               break;
+                       }
+
+                       count -= len;
+               }
+
+               rate += calc_bit_rate(&hdr);
+               num++;
+       }
+
+       printf("Subbands\t\t%d\n", subbands);
+       printf("Block length\t\t%d\n", blocks);
+       printf("Sampling frequency\t%s\n", freq2str(freq));
+       printf("Channel mode\t\t%s\n", mode2str(hdr.channel_mode));
+       printf("Allocation method\t%s\n", method ? "SNR" : "Loudness");
+       printf("Bitpool\t\t\t%d", bitpool[0]);
+       for (n = 1; n < SIZE; n++)
+               if (bitpool[n] > 0)
+                       printf(", %d", bitpool[n]);
+       printf("\n");
+       printf("Number of frames\t%d\n", num);
+       printf("Frame length\t\t%d", frame_len[0]);
+       for (n = 1; n < SIZE; n++)
+               if (frame_len[n] > 0)
+                       printf(", %d", frame_len[n]);
+       printf(" Bytes\n");
+       if (num > 0)
+               printf("Bit rate\t\t%.3f kbps\n", rate / num);
+
+       if (fd > fileno(stderr))
+               close(fd);
+
+       printf("\n");
+
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       int i;
+
+       if (argc < 2) {
+               fprintf(stderr, "Usage: sbcinfo <file>\n");
+               exit(1);
+       }
+
+       for (i = 0; i < argc - 1; i++)
+               if (analyze_file(argv[i + 1]) < 0)
+                       exit(1);
+
+       return 0;
+}
diff --git a/sbc/sbctester.c b/sbc/sbctester.c
new file mode 100644 (file)
index 0000000..b1e3608
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2008-2010  Nokia Corporation
+ *  Copyright (C) 2007-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2007-2008  Frederic Dalleau <fdalleau@free.fr>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sndfile.h>
+#include <math.h>
+#include <string.h>
+
+#define MAXCHANNELS 2
+#define DEFACCURACY 7
+
+static double sampletobits(short sample16, int verbose)
+{
+       double bits = 0;
+       unsigned short bit;
+       int i;
+
+       if (verbose)
+               printf("===> sampletobits(%hd, %04hX)\n", sample16, sample16);
+
+       /* Bit 0 is MSB */
+       if (sample16 < 0)
+               bits = -1;
+
+       if (verbose)
+               printf("%d", (sample16 < 0) ? 1 : 0);
+
+       /* Bit 15 is LSB */
+       for (i = 1; i < 16; i++) {
+               bit = (unsigned short) sample16;
+               bit >>= 15 - i;
+               bit %= 2;
+
+               if (verbose)
+                       printf("%d", bit);
+
+               if (bit)
+                       bits += (1.0 / pow(2.0, i));
+       }
+
+       if (verbose)
+               printf("\n");
+
+       return bits;
+}
+
+static int calculate_rms_level(SNDFILE * sndref, SF_INFO * infosref,
+                               SNDFILE * sndtst, SF_INFO * infostst,
+                                               int accuracy, char *csvname)
+{
+       short refsample[MAXCHANNELS], tstsample[MAXCHANNELS];
+       double refbits, tstbits;
+       double rms_accu[MAXCHANNELS];
+       double rms_level[MAXCHANNELS];
+       double rms_limit = 1.0 / (pow(2.0, accuracy - 1) * pow(12.0, 0.5));
+       FILE *csv = NULL;
+       int i, j, r1, r2, verdict;
+
+       if (csvname)
+               csv = fopen(csvname, "wt");
+
+       if (csv) {
+               fprintf(csv, "num;");
+               for (j = 0; j < infostst->channels; j++)
+                       fprintf(csv, "ref channel %d;tst channel %d;", j, j);
+               fprintf(csv, "\r\n");
+       }
+
+       sf_seek(sndref, 0, SEEK_SET);
+       sf_seek(sndtst, 0, SEEK_SET);
+
+       memset(rms_accu, 0, sizeof(rms_accu));
+       memset(rms_level, 0, sizeof(rms_level));
+
+       for (i = 0; i < infostst->frames; i++) {
+               if (csv)
+                       fprintf(csv, "%d;", i);
+
+               r1 = sf_read_short(sndref, refsample, infostst->channels);
+               if (r1 != infostst->channels) {
+                       printf("Failed to read reference data: %s "
+                                       "(r1=%d, channels=%d)",
+                                       sf_strerror(sndref), r1,
+                                       infostst->channels);
+                       if (csv)
+                               fclose(csv);
+                       return -1;
+               }
+
+               r2 = sf_read_short(sndtst, tstsample, infostst->channels);
+               if (r2 != infostst->channels) {
+                       printf("Failed to read test data: %s "
+                                       "(r2=%d, channels=%d)\n",
+                                       sf_strerror(sndtst), r2,
+                                       infostst->channels);
+                       if (csv)
+                               fclose(csv);
+                       return -1;
+               }
+
+               for (j = 0; j < infostst->channels; j++) {
+                       if (csv)
+                               fprintf(csv, "%d;%d;", refsample[j],
+                                               tstsample[j]);
+
+                       refbits = sampletobits(refsample[j], 0);
+                       tstbits = sampletobits(tstsample[j], 0);
+
+                       rms_accu[j] += pow(tstbits - refbits, 2.0);
+               }
+
+               if (csv)
+                       fprintf(csv, "\r\n");
+       }
+
+       printf("Limit: %f\n", rms_limit);
+
+       for (j = 0; j < infostst->channels; j++) {
+               printf("Channel %d\n", j);
+               printf("Accumulated %f\n", rms_accu[j]);
+               rms_accu[j] /= (double) infostst->frames;
+               printf("Accumulated / %f = %f\n", (double) infostst->frames,
+                               rms_accu[j]);
+               rms_level[j] = sqrt(rms_accu[j]);
+               printf("Level = %f (%f x %f = %f)\n",
+                               rms_level[j], rms_level[j], rms_level[j],
+                                               rms_level[j] * rms_level[j]);
+       }
+
+       verdict = 1;
+
+       for (j = 0; j < infostst->channels; j++) {
+               printf("Channel %d: %f\n", j, rms_level[j]);
+
+               if (rms_level[j] > rms_limit)
+                       verdict = 0;
+       }
+
+       printf("%s return %d\n", __FUNCTION__, verdict);
+
+       return verdict;
+}
+
+static int check_absolute_diff(SNDFILE * sndref, SF_INFO * infosref,
+                               SNDFILE * sndtst, SF_INFO * infostst,
+                               int accuracy)
+{
+       short refsample[MAXCHANNELS], tstsample[MAXCHANNELS];
+       short refmax[MAXCHANNELS], tstmax[MAXCHANNELS];
+       double refbits, tstbits;
+       double rms_absolute = 1.0 / (pow(2, accuracy - 2));
+       double calc_max[MAXCHANNELS];
+       int calc_count = 0;
+       short r1, r2;
+       double cur_diff;
+       int i, j, verdict;
+
+       memset(&refmax, 0, sizeof(refmax));
+       memset(&tstmax, 0, sizeof(tstmax));
+       memset(&calc_max, 0, sizeof(calc_max));
+       memset(&refsample, 0, sizeof(refsample));
+       memset(&tstsample, 0, sizeof(tstsample));
+
+       sf_seek(sndref, 0, SEEK_SET);
+       sf_seek(sndtst, 0, SEEK_SET);
+
+       verdict = 1;
+
+       printf("Absolute max: %f\n", rms_absolute);
+       for (i = 0; i < infostst->frames; i++) {
+               r1 = sf_read_short(sndref, refsample, infostst->channels);
+
+               if (r1 != infostst->channels) {
+                       printf("Failed to read reference data: %s "
+                                       "(r1=%d, channels=%d)",
+                                       sf_strerror(sndref), r1,
+                                       infostst->channels);
+                       return -1;
+               }
+
+               r2 = sf_read_short(sndtst, tstsample, infostst->channels);
+               if (r2 != infostst->channels) {
+                       printf("Failed to read test data: %s "
+                                       "(r2=%d, channels=%d)\n",
+                                       sf_strerror(sndtst), r2,
+                                       infostst->channels);
+                       return -1;
+               }
+
+               for (j = 0; j < infostst->channels; j++) {
+                       refbits = sampletobits(refsample[j], 0);
+                       tstbits = sampletobits(tstsample[j], 0);
+
+                       cur_diff = fabs(tstbits - refbits);
+
+                       if (cur_diff > rms_absolute) {
+                               calc_count++;
+                               /* printf("Channel %d exceeded : fabs(%f - %f) = %f > %f\n", j, tstbits, refbits, cur_diff, rms_absolute); */
+                               verdict = 0;
+                       }
+
+                       if (cur_diff > calc_max[j]) {
+                               calc_max[j] = cur_diff;
+                               refmax[j] = refsample[j];
+                               tstmax[j] = tstsample[j];
+                       }
+               }
+       }
+
+       for (j = 0; j < infostst->channels; j++) {
+               printf("Calculated max: %f (%hd-%hd=%hd)\n",
+                       calc_max[j], tstmax[j], refmax[j],
+                       tstmax[j] - refmax[j]);
+       }
+
+       printf("%s return %d\n", __FUNCTION__, verdict);
+
+       return verdict;
+}
+
+static void usage()
+{
+       printf("SBC conformance test ver %s\n", VERSION);
+       printf("Copyright (c) 2007-2010  Marcel Holtmann\n");
+       printf("Copyright (c) 2007-2008  Frederic Dalleau\n\n");
+
+       printf("Usage:\n"
+               "\tsbctester reference.wav checkfile.wav\n"
+               "\tsbctester integer\n"
+               "\n");
+
+       printf("To test the encoder:\n");
+       printf("\tUse a reference codec to encode original.wav to reference.sbc\n");
+       printf("\tUse sbcenc to encode original.wav to checkfile.sbc\n");
+       printf("\tDecode both file using the reference decoder\n");
+       printf("\tRun sbctester with these two wav files to get the result\n\n");
+
+       printf("\tA file called out.csv is generated to use the data in a\n");
+       printf("\tspreadsheet application or database.\n\n");
+}
+
+int main(int argc, char *argv[])
+{
+       SNDFILE *sndref = NULL;
+       SNDFILE *sndtst = NULL;
+       SF_INFO infosref;
+       SF_INFO infostst;
+       char *ref;
+       char *tst;
+       int pass_rms, pass_absolute, pass, accuracy;
+
+       if (argc == 2) {
+               double db;
+
+               printf("Test sampletobits\n");
+               db = sampletobits((short) atoi(argv[1]), 1);
+               printf("db = %f\n", db);
+               exit(0);
+       }
+
+       if (argc < 3) {
+               usage();
+               exit(1);
+       }
+
+       ref = argv[1];
+       tst = argv[2];
+
+       printf("opening reference %s\n", ref);
+
+       sndref = sf_open(ref, SFM_READ, &infosref);
+       if (!sndref) {
+               printf("Failed to open reference file\n");
+               exit(1);
+       }
+
+       printf("opening testfile %s\n", tst);
+       sndtst = sf_open(tst, SFM_READ, &infostst);
+       if (!sndtst) {
+               printf("Failed to open test file\n");
+               sf_close(sndref);
+               exit(1);
+       }
+
+       printf("reference:\n\t%d frames,\n\t%d hz,\n\t%d channels\n",
+               (int) infosref.frames, (int) infosref.samplerate,
+               (int) infosref.channels);
+       printf("testfile:\n\t%d frames,\n\t%d hz,\n\t%d channels\n",
+               (int) infostst.frames, (int) infostst.samplerate,
+               (int) infostst.channels);
+
+       /* check number of channels */
+       if (infosref.channels > 2 || infostst.channels > 2) {
+               printf("Too many channels\n");
+               goto error;
+       }
+
+       /* compare number of samples */
+       if (infosref.samplerate != infostst.samplerate ||
+                               infosref.channels != infostst.channels) {
+               printf("Cannot compare files with different charasteristics\n");
+               goto error;
+       }
+
+       accuracy = DEFACCURACY;
+       printf("Accuracy: %d\n", accuracy);
+
+       /* Condition 1 rms level */
+       pass_rms = calculate_rms_level(sndref, &infosref, sndtst, &infostst,
+                                       accuracy, "out.csv");
+       if (pass_rms < 0)
+               goto error;
+
+       /* Condition 2 absolute difference */
+       pass_absolute = check_absolute_diff(sndref, &infosref, sndtst,
+                                               &infostst, accuracy);
+       if (pass_absolute < 0)
+               goto error;
+
+       /* Verdict */
+       pass = pass_rms && pass_absolute;
+       printf("Verdict: %s\n", pass ? "pass" : "fail");
+
+       return 0;
+
+error:
+       sf_close(sndref);
+       sf_close(sndtst);
+
+       exit(1);
+}
diff --git a/scripts/bluetooth-hid2hci.rules b/scripts/bluetooth-hid2hci.rules
new file mode 100644 (file)
index 0000000..1b231d1
--- /dev/null
@@ -0,0 +1,36 @@
+# Variety of Dell Bluetooth devices
+#
+# it looks like a bit of an odd rule, because it is matching
+# on a mouse device that is self powered, but that is where
+# a HID report needs to be sent to switch modes.
+#
+# Known supported devices:
+#   413c:8154
+#   413c:8158
+#   413c:8162
+ACTION=="add", ENV{ID_VENDOR}=="413c", ENV{ID_CLASS}=="mouse", ATTRS{bmAttributes}=="e0", KERNEL=="mouse*", RUN+="/usr/sbin/hid2hci --method dell -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+
+# Logitech devices
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c703" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c704" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c705" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c70a" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c70b" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c70c" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c70e" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c713" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c714" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c71b" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="046d", ENV{ID_MODEL}=="c71c" RUN+="/usr/sbin/hid2hci --method logitech -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+
+# CSR devices (in HID mode)
+ACTION=="add", ENV{ID_VENDOR}=="0a12", ENV{ID_MODEL}=="1000" RUN+="/usr/sbin/hid2hci --method csr -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="0458", ENV{ID_MODEL}=="1000" RUN+="/usr/sbin/hid2hci --method csr -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+ACTION=="add", ENV{ID_VENDOR}=="05ac", ENV{ID_MODEL}=="1000" RUN+="/usr/sbin/hid2hci --method csr -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hci"
+
+# CSR devices (in HCI mode)
+#ACTION=="add", ENV{ID_VENDOR}=="0a12", ENV{ID_MODEL}=="0001" RUN+="/usr/sbin/hid2hci --method csr -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hid"
+#ACTION=="add", ENV{ID_VENDOR}=="0458", ENV{ID_MODEL}=="003f" RUN+="/usr/sbin/hid2hci --method csr -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hid"
+#ACTION=="add", ENV{ID_VENDOR}=="05ac", ENV{ID_MODEL}=="8203" RUN+="/usr/sbin/hid2hci --method csr -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hid"
+#ACTION=="add", ENV{ID_VENDOR}=="05ac", ENV{ID_MODEL}=="8204" RUN+="/usr/sbin/hid2hci --method csr -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hid"
+#ACTION=="add", ENV{ID_VENDOR}=="05ac", ENV{ID_MODEL}=="8207" RUN+="/usr/sbin/hid2hci --method csr -v $env{ID_VENDOR} -p $env{ID_MODEL} --mode hid"
diff --git a/scripts/bluetooth-serial.rules b/scripts/bluetooth-serial.rules
new file mode 100644 (file)
index 0000000..072335f
--- /dev/null
@@ -0,0 +1,35 @@
+# Brain Boxes BL-620 Bluetooth Adapter
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="Brain Boxes", ATTRS{prod_id2}=="Bluetooth PC Card", ENV{HCIOPTS}="bboxes", RUN+="bluetooth_serial"
+
+# Xircom CreditCard Bluetooth Adapter
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="Xircom", ATTRS{prod_id3}=="CBT", ENV{HCIOPTS}="xircom", RUN+="bluetooth_serial"
+
+# Xircom RealPort2 Bluetooth Adapter
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="Xircom", ATTRS{prod_id3}=="CBT", ENV{HCIOPTS}="xircom", RUN+="bluetooth_serial"
+
+# IBM Bluetooth PC Card II
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="IBM", ATTRS{prod_id2}=="Bluetooth PC Card II", ENV{HCIOPTS}="tdk", RUN+="bluetooth_serial"
+
+# TDK Bluetooth PC Card
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="TDK", ATTRS{prod_id2}=="Bluetooth PC Card II", ENV{HCIOPTS}="tdk", RUN+="bluetooth_serial"
+
+# AmbiCom BT2000C Bluetooth PC/CF Card
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="AmbiCom BT2000C", ATTRS{prod_id2}=="Bluetooth PC/CF Card", ENV{HCIOPTS}="bt2000c", RUN+="bluetooth_serial"
+
+# COM One Platinium Bluetooth PC Card
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="COM1 SA", ATTRS{prod_id2}=="MC310 CARD", ENV{HCIOPTS}="comone", RUN+="bluetooth_serial"
+
+# Sphinx PICO Card
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="SPHINX", ATTRS{prod_id2}=="BT-CARD", ENV{HCIOPTS}="picocard", RUN+="bluetooth_serial"
+
+# H-Soft blue+Card
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="H-Soft", ATTRS{prod_id2}=="Blue+CARD", ENV{HCIOPTS}="$sysfs{manf_id},$sysfs{card_id}", RUN+="bluetooth_serial"
+
+# Compaq iPAQ Bluetooth Sleeve, Belkin F8T020, any other muppet who used an OXCF950 and didn't bother to program it appropriately.
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="CF CARD", ATTRS{prod_id2}=="GENERIC", ENV{HCIOPTS}="$sysfs{manf_id},$sysfs{card_id}", RUN+="bluetooth_serial"
+
+# Zoom Bluetooth Card and Sitecom CN-504 Card
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="PCMCIA", ATTRS{prod_id2}=="Bluetooth Card", ENV{HCIOPTS}="zoom", RUN+="bluetooth_serial"
+
+# CC&C BT0100M
+SUBSYSTEM=="tty", SUBSYSTEMS=="pcmcia", ATTRS{prod_id1}=="Bluetooth BT0100M", ENV{HCIOPTS}="bcsp 115200", RUN+="bluetooth_serial"
diff --git a/scripts/bluetooth.rules.in b/scripts/bluetooth.rules.in
new file mode 100644 (file)
index 0000000..64df69d
--- /dev/null
@@ -0,0 +1,4 @@
+# Run helper every time a Bluetooth device appears
+# On remove actions, bluetoothd should go away by itself
+ACTION=="add", SUBSYSTEM=="bluetooth", RUN+="@prefix@/sbin/bluetoothd --udev"
+ACTION=="change", SUBSYSTEM=="bluetooth", RUN+="@prefix@/sbin/bluetoothd --udev"
diff --git a/scripts/bluetooth_serial b/scripts/bluetooth_serial
new file mode 100644 (file)
index 0000000..e5be6c2
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# bluetooth_serial
+#
+# Bluetooth serial PCMCIA card initialization
+#
+
+start_serial()
+{
+       if [ ! -x /bin/setserial -o ! -x /usr/sbin/hciattach ]; then
+               logger "$0: setserial or hciattach not executable, cannot start $DEVNAME"
+               return 1
+       fi
+
+       if [ "$BAUDBASE" != "" ]; then
+               /bin/setserial $DEVNAME baud_base $BAUDBASE
+       fi
+
+       /usr/sbin/hciattach $DEVNAME $HCIOPTS 2>&1 | logger -t hciattach
+}
+
+stop_serial()
+{
+       [ -x /bin/fuser ] || return 1 
+
+       /bin/fuser -k -HUP $DEVNAME > /dev/null
+}
+
+case "$ACTION" in
+   add)
+       start_serial
+       ;;
+   remove)
+       stop_serial
+       ;;
+   *)
+       logger "Unknown action received $0: $ACTION"
+       ;;
+esac
diff --git a/serial/main.c b/serial/main.c
new file mode 100644 (file)
index 0000000..38ded03
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "manager.h"
+
+static DBusConnection *connection;
+
+static int serial_init(void)
+{
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (connection == NULL)
+               return -EIO;
+
+       if (serial_manager_init(connection) < 0) {
+               dbus_connection_unref(connection);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void serial_exit(void)
+{
+       serial_manager_exit();
+
+       dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(serial, VERSION,
+                       BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, serial_init, serial_exit)
diff --git a/serial/manager.c b/serial/manager.c
new file mode 100644 (file)
index 0000000..eba8a91
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/rfcomm.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/dbus-common.h"
+#include "adapter.h"
+#include "device.h"
+
+#include "log.h"
+#include "textfile.h"
+
+#include "error.h"
+#include "port.h"
+#include "proxy.h"
+#include "storage.h"
+#include "manager.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+
+#define RFCOMM_UUID_STR                "00000003-0000-1000-8000-00805F9B34FB"
+
+static DBusConnection *connection = NULL;
+
+static int serial_probe(struct btd_device *device, const char *uuid)
+{
+       struct btd_adapter *adapter = device_get_adapter(device);
+       const gchar *path = device_get_path(device);
+       sdp_list_t *protos;
+       int ch;
+       bdaddr_t src, dst;
+       const sdp_record_t *rec;
+
+       DBG("path %s: %s", path, uuid);
+
+       rec = btd_device_get_record(device, uuid);
+       if (!rec)
+               return -EINVAL;
+
+       if (sdp_get_access_protos(rec, &protos) < 0)
+               return -EINVAL;
+
+       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 < 1 || ch > 30) {
+               error("Channel out of range: %d", ch);
+               return -EINVAL;
+       }
+
+       adapter_get_address(adapter, &src);
+       device_get_address(device, &dst);
+
+       return port_register(connection, path, &src, &dst, uuid, ch);
+}
+
+static void serial_remove(struct btd_device *device)
+{
+       const gchar *path = device_get_path(device);
+
+       DBG("path %s", path);
+
+       port_unregister(path);
+}
+
+
+static int port_probe(struct btd_device *device, GSList *uuids)
+{
+       while (uuids) {
+               serial_probe(device, uuids->data);
+               uuids = uuids->next;
+       }
+
+       return 0;
+}
+
+static void port_remove(struct btd_device *device)
+{
+       return serial_remove(device);
+}
+
+static struct btd_device_driver serial_port_driver = {
+       .name   = "serial-port",
+       .uuids  = BTD_UUIDS(RFCOMM_UUID_STR),
+       .probe  = port_probe,
+       .remove = port_remove,
+};
+
+static int proxy_probe(struct btd_adapter *adapter)
+{
+       const char *path = adapter_get_path(adapter);
+
+       DBG("path %s", path);
+
+       return proxy_register(connection, adapter);
+}
+
+static void proxy_remove(struct btd_adapter *adapter)
+{
+       const char *path = adapter_get_path(adapter);
+
+       DBG("path %s", path);
+
+       proxy_unregister(adapter);
+}
+
+static struct btd_adapter_driver serial_proxy_driver = {
+       .name   = "serial-proxy",
+       .probe  = proxy_probe,
+       .remove = proxy_remove,
+};
+
+int serial_manager_init(DBusConnection *conn)
+{
+       connection = dbus_connection_ref(conn);
+
+       btd_register_adapter_driver(&serial_proxy_driver);
+       btd_register_device_driver(&serial_port_driver);
+
+       return 0;
+}
+
+void serial_manager_exit(void)
+{
+       btd_unregister_device_driver(&serial_port_driver);
+
+       dbus_connection_unref(connection);
+       connection = NULL;
+}
diff --git a/serial/manager.h b/serial/manager.h
new file mode 100644 (file)
index 0000000..c8b96e8
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 serial_manager_init(DBusConnection *conn);
+void serial_manager_exit(void);
diff --git a/serial/port.c b/serial/port.c
new file mode 100644 (file)
index 0000000..7d56faa
--- /dev/null
@@ -0,0 +1,622 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/dbus-common.h"
+
+#include "log.h"
+#include "glib-helper.h"
+#include "btio.h"
+
+#include "error.h"
+#include "manager.h"
+#include "adapter.h"
+#include "device.h"
+#include "storage.h"
+#include "port.h"
+
+#define SERIAL_PORT_INTERFACE  "org.bluez.Serial"
+
+#define MAX_OPEN_TRIES         5
+#define OPEN_WAIT              300     /* ms. udev node creation retry wait */
+
+struct serial_device {
+       DBusConnection  *conn;          /* for name listener handling */
+       bdaddr_t        src;            /* Source (local) address */
+       bdaddr_t        dst;            /* Destination address */
+       char            *path;          /* Device path */
+       GSList          *ports;         /* Available ports */
+};
+
+struct serial_port {
+       DBusMessage     *msg;           /* for name listener handling */
+       int16_t         id;             /* RFCOMM device id */
+       uint8_t         channel;        /* RFCOMM channel */
+       char            *uuid;          /* service identification */
+       char            *dev;           /* RFCOMM device name */
+       int             fd;             /* Opened file descriptor */
+       GIOChannel      *io;            /* BtIO channel */
+       guint           listener_id;
+       struct serial_device *device;
+};
+
+static GSList *devices = NULL;
+
+static struct serial_device *find_device(GSList *devices, const char *path)
+{
+       GSList *l;
+
+       for (l = devices; l != NULL; l = l->next) {
+               struct serial_device *device = l->data;
+
+               if (!strcmp(device->path, path))
+                       return device;
+       }
+
+       return NULL;
+}
+
+static struct serial_port *find_port(GSList *ports, const char *pattern)
+{
+       GSList *l;
+       int channel;
+       char *endptr = NULL;
+
+       channel = strtol(pattern, &endptr, 10);
+
+       for (l = ports; l != NULL; l = l->next) {
+               struct serial_port *port = l->data;
+               char *uuid_str;
+               int ret;
+
+               if (port->uuid && !strcasecmp(port->uuid, pattern))
+                       return port;
+
+               if (endptr && *endptr == '\0' && port->channel == channel)
+                       return port;
+
+               if (port->dev && !strcmp(port->dev, pattern))
+                       return port;
+
+               if (!port->uuid)
+                       continue;
+
+               uuid_str = bt_name2string(pattern);
+               if (!uuid_str)
+                       continue;
+
+               ret = strcasecmp(port->uuid, uuid_str);
+               g_free(uuid_str);
+               if (ret == 0)
+                       return port;
+       }
+
+       return NULL;
+}
+
+static int port_release(struct serial_port *port)
+{
+       struct rfcomm_dev_req req;
+       int rfcomm_ctl;
+       int err = 0;
+
+       if (port->id < 0) {
+               if (port->io) {
+                       g_io_channel_shutdown(port->io, TRUE, NULL);
+                       g_io_channel_unref(port->io);
+                       port->io = NULL;
+               } else
+                       bt_cancel_discovery(&port->device->src,
+                                               &port->device->dst);
+
+               return 0;
+       }
+
+       DBG("Serial port %s released", port->dev);
+
+       rfcomm_ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM);
+       if (rfcomm_ctl < 0)
+               return -errno;
+
+       if (port->fd >= 0) {
+               close(port->fd);
+               port->fd = -1;
+       }
+
+       memset(&req, 0, sizeof(req));
+       req.dev_id = port->id;
+
+       /*
+        * We are hitting a kernel bug inside RFCOMM code when
+        * RFCOMM_HANGUP_NOW bit is set on request's flags passed to
+        * ioctl(RFCOMMRELEASEDEV)!
+        */
+       req.flags = (1 << RFCOMM_HANGUP_NOW);
+
+       if (ioctl(rfcomm_ctl, RFCOMMRELEASEDEV, &req) < 0) {
+               err = errno;
+               error("Can't release device %s: %s (%d)",
+                               port->dev, strerror(err), err);
+       }
+
+       g_free(port->dev);
+       port->dev = NULL;
+       port->id = -1;
+       close(rfcomm_ctl);
+       return -err;
+}
+
+static void serial_port_free(struct serial_port *port)
+{
+       struct serial_device *device = port->device;
+
+       if (device && port->listener_id > 0)
+               g_dbus_remove_watch(device->conn, port->listener_id);
+
+       port_release(port);
+
+       g_free(port->uuid);
+       g_free(port);
+}
+
+static void serial_device_free(struct serial_device *device)
+{
+       g_free(device->path);
+       if (device->conn)
+               dbus_connection_unref(device->conn);
+       g_free(device);
+}
+
+static void port_owner_exited(DBusConnection *conn, void *user_data)
+{
+       struct serial_port *port = user_data;
+
+       port_release(port);
+
+       port->listener_id = 0;
+}
+
+static void path_unregister(void *data)
+{
+       struct serial_device *device = data;
+
+       DBG("Unregistered interface %s on path %s", SERIAL_PORT_INTERFACE,
+               device->path);
+
+       devices = g_slist_remove(devices, device);
+       serial_device_free(device);
+}
+
+void port_release_all(void)
+{
+       g_slist_foreach(devices, (GFunc) serial_device_free, NULL);
+       g_slist_free(devices);
+}
+
+static void open_notify(int fd, int err, struct serial_port *port)
+{
+       struct serial_device *device = port->device;
+       DBusMessage *reply;
+
+       if (err < 0) {
+               /* Max tries exceeded */
+               port_release(port);
+               reply = btd_error_failed(port->msg, strerror(-err));
+       } else {
+               port->fd = fd;
+               reply = g_dbus_create_reply(port->msg,
+                               DBUS_TYPE_STRING, &port->dev,
+                               DBUS_TYPE_INVALID);
+       }
+
+       /* Reply to the requestor */
+       g_dbus_send_message(device->conn, reply);
+}
+
+static gboolean open_continue(gpointer user_data)
+{
+       struct serial_port *port = user_data;
+       int fd;
+       static int ntries = MAX_OPEN_TRIES;
+
+       if (!port->listener_id)
+               return FALSE; /* Owner exited */
+
+       fd = open(port->dev, O_RDONLY | O_NOCTTY);
+       if (fd < 0) {
+               int err = -errno;
+               error("Could not open %s: %s (%d)",
+                               port->dev, strerror(-err), -err);
+               if (!--ntries) {
+                       /* Reporting error */
+                       open_notify(fd, err, port);
+                       ntries = MAX_OPEN_TRIES;
+                       return FALSE;
+               }
+               return TRUE;
+       }
+
+       /* Connection succeeded */
+       open_notify(fd, 0, port);
+       return FALSE;
+}
+
+static int port_open(struct serial_port *port)
+{
+       int fd;
+
+       fd = open(port->dev, O_RDONLY | O_NOCTTY);
+       if (fd < 0) {
+               g_timeout_add(OPEN_WAIT, open_continue, port);
+               return -EINPROGRESS;
+       }
+
+       return fd;
+}
+
+static void rfcomm_connect_cb(GIOChannel *chan, GError *conn_err,
+                                                       gpointer user_data)
+{
+       struct serial_port *port = user_data;
+       struct serial_device *device = port->device;
+       struct rfcomm_dev_req req;
+       int sk, fd;
+       DBusMessage *reply;
+
+       /* Owner exited? */
+       if (!port->listener_id)
+               return;
+
+       if (conn_err) {
+               error("%s", conn_err->message);
+               reply = btd_error_failed(port->msg, conn_err->message);
+               goto fail;
+       }
+
+       memset(&req, 0, sizeof(req));
+       req.dev_id = -1;
+       req.flags = (1 << RFCOMM_REUSE_DLC);
+       bacpy(&req.src, &device->src);
+       bacpy(&req.dst, &device->dst);
+       req.channel = port->channel;
+
+       g_io_channel_unref(port->io);
+       port->io = NULL;
+
+       sk = g_io_channel_unix_get_fd(chan);
+       port->id = ioctl(sk, RFCOMMCREATEDEV, &req);
+       if (port->id < 0) {
+               int err = -errno;
+               error("ioctl(RFCOMMCREATEDEV): %s (%d)", strerror(-err), -err);
+               reply = btd_error_failed(port->msg, strerror(-err));
+               g_io_channel_shutdown(chan, TRUE, NULL);
+               goto fail;
+       }
+
+       port->dev = g_strdup_printf("/dev/rfcomm%d", port->id);
+
+       DBG("Serial port %s created", port->dev);
+
+       g_io_channel_shutdown(chan, TRUE, NULL);
+
+       /* Addressing connect port */
+       fd = port_open(port);
+       if (fd < 0)
+               /* Open in progress: Wait the callback */
+               return;
+
+       open_notify(fd, 0, port);
+       return;
+
+fail:
+       g_dbus_send_message(device->conn, reply);
+       g_dbus_remove_watch(device->conn, port->listener_id);
+       port->listener_id = 0;
+}
+
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+       struct serial_port *port = user_data;
+       struct serial_device *device = port->device;
+       sdp_record_t *record = NULL;
+       sdp_list_t *protos;
+       DBusMessage *reply;
+       GError *gerr = NULL;
+
+       if (!port->listener_id) {
+               reply = NULL;
+               goto failed;
+       }
+
+       if (err < 0) {
+               error("Unable to get service record: %s (%d)", strerror(-err),
+                       -err);
+               reply = btd_error_failed(port->msg, strerror(-err));
+               goto failed;
+       }
+
+       if (!recs || !recs->data) {
+               error("No record found");
+               reply = btd_error_failed(port->msg, "No record found");
+               goto failed;
+       }
+
+       record = recs->data;
+
+       if (sdp_get_access_protos(record, &protos) < 0) {
+               error("Unable to get access protos from port record");
+               reply = btd_error_failed(port->msg, "Invalid channel");
+               goto failed;
+       }
+
+       port->channel = sdp_get_proto_port(protos, RFCOMM_UUID);
+
+       sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+       sdp_list_free(protos, NULL);
+
+       port->io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, port,
+                               NULL, &gerr,
+                               BT_IO_OPT_SOURCE_BDADDR, &device->src,
+                               BT_IO_OPT_DEST_BDADDR, &device->dst,
+                               BT_IO_OPT_CHANNEL, port->channel,
+                               BT_IO_OPT_INVALID);
+       if (!port->io) {
+               error("%s", gerr->message);
+               reply = btd_error_failed(port->msg, gerr->message);
+               g_error_free(gerr);
+               goto failed;
+       }
+
+       return;
+
+failed:
+       g_dbus_remove_watch(device->conn, port->listener_id);
+       port->listener_id = 0;
+       g_dbus_send_message(device->conn, reply);
+}
+
+static int connect_port(struct serial_port *port)
+{
+       struct serial_device *device = port->device;
+       uuid_t uuid;
+       int err;
+
+       if (!port->uuid)
+               goto connect;
+
+       err = bt_string2uuid(&uuid, port->uuid);
+       if (err < 0)
+               return err;
+
+       sdp_uuid128_to_uuid(&uuid);
+
+       return bt_search_service(&device->src, &device->dst, &uuid,
+                               get_record_cb, port, NULL);
+
+connect:
+       port->io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, port,
+                               NULL, NULL,
+                               BT_IO_OPT_SOURCE_BDADDR, &device->src,
+                               BT_IO_OPT_DEST_BDADDR, &device->dst,
+                               BT_IO_OPT_CHANNEL, port->channel,
+                               BT_IO_OPT_INVALID);
+       if (port->io)
+               return 0;
+
+       return -errno;
+}
+
+static struct serial_port *create_port(struct serial_device *device,
+                                       const char *uuid, uint8_t channel)
+{
+       struct serial_port *port;
+
+       port = g_new0(struct serial_port, 1);
+       port->uuid = g_strdup(uuid);
+       port->channel = channel;
+       port->device = device;
+       port->id = -1;
+       port->fd = -1;
+
+       device->ports = g_slist_append(device->ports, port);
+
+       return port;
+}
+
+static DBusMessage *port_connect(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct serial_device *device = user_data;
+       struct serial_port *port;
+       const char *pattern;
+       int err;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return NULL;
+
+       port = find_port(device->ports, pattern);
+       if (!port) {
+               char *endptr = NULL;
+               int channel;
+
+               channel = strtol(pattern, &endptr, 10);
+               if ((endptr && *endptr != '\0') || channel < 1 || channel > 30)
+                       return btd_error_does_not_exist(msg);
+
+               port = create_port(device, NULL, channel);
+       }
+
+       if (port->listener_id)
+               return btd_error_failed(msg, "Port already in use");
+
+       port->listener_id = g_dbus_add_disconnect_watch(conn,
+                                               dbus_message_get_sender(msg),
+                                               port_owner_exited, port,
+                                               NULL);
+       port->msg = dbus_message_ref(msg);
+
+       err = connect_port(port);
+       if (err < 0) {
+               error("%s", strerror(-err));
+               g_dbus_remove_watch(conn, port->listener_id);
+               port->listener_id = 0;
+
+               return btd_error_failed(msg, strerror(-err));
+       }
+
+       return NULL;
+}
+
+static DBusMessage *port_disconnect(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct serial_device *device = user_data;
+       struct serial_port *port;
+       const char *dev, *owner, *caller;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &dev,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return NULL;
+
+       port = find_port(device->ports, dev);
+       if (!port)
+               return btd_error_does_not_exist(msg);
+
+       if (!port->listener_id)
+               return btd_error_not_connected(msg);
+
+       owner = dbus_message_get_sender(port->msg);
+       caller = dbus_message_get_sender(msg);
+       if (!g_str_equal(owner, caller))
+               return btd_error_not_authorized(msg);
+
+       port_release(port);
+
+       g_dbus_remove_watch(conn, port->listener_id);
+       port->listener_id = 0;
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static GDBusMethodTable port_methods[] = {
+       { "Connect",    "s", "s", port_connect, G_DBUS_METHOD_FLAG_ASYNC },
+       { "Disconnect", "s", "",  port_disconnect },
+       { }
+};
+
+static struct serial_device *create_serial_device(DBusConnection *conn,
+                                       const char *path, bdaddr_t *src,
+                                       bdaddr_t *dst)
+{
+       struct serial_device *device;
+
+       device = g_new0(struct serial_device, 1);
+       device->conn = dbus_connection_ref(conn);
+       bacpy(&device->dst, dst);
+       bacpy(&device->src, src);
+       device->path = g_strdup(path);
+
+       if (!g_dbus_register_interface(conn, path,
+                               SERIAL_PORT_INTERFACE,
+                               port_methods, NULL, NULL,
+                               device, path_unregister)) {
+               error("D-Bus failed to register %s interface",
+                               SERIAL_PORT_INTERFACE);
+               serial_device_free(device);
+               return NULL;
+       }
+
+       DBG("Registered interface %s on path %s",
+               SERIAL_PORT_INTERFACE, path);
+
+       return device;
+}
+
+int port_register(DBusConnection *conn, const char *path, bdaddr_t *src,
+                       bdaddr_t *dst, const char *uuid, uint8_t channel)
+{
+       struct serial_device *device;
+       struct serial_port *port;
+
+       device = find_device(devices, path);
+       if (!device) {
+               device = create_serial_device(conn, path, src, dst);
+               if (!device)
+                       return -1;
+               devices = g_slist_append(devices, device);
+       }
+
+       if (find_port(device->ports, uuid))
+               return 0;
+
+       port = g_new0(struct serial_port, 1);
+       port->uuid = g_strdup(uuid);
+       port->channel = channel;
+       port->device = device;
+       port->id = -1;
+       port->fd = -1;
+
+       device->ports = g_slist_append(device->ports, port);
+
+       return 0;
+}
+
+int port_unregister(const char *path)
+{
+       struct serial_device *device;
+
+       device = find_device(devices, path);
+       if (!device)
+               return -ENOENT;
+
+       g_slist_foreach(device->ports, (GFunc) serial_port_free, NULL);
+       g_slist_free(device->ports);
+
+       g_dbus_unregister_interface(device->conn, path, SERIAL_PORT_INTERFACE);
+
+       return 0;
+}
diff --git a/serial/port.h b/serial/port.h
new file mode 100644 (file)
index 0000000..74ac9f0
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 port_release_all(void);
+
+int port_register(DBusConnection *conn, const char *path, bdaddr_t *src,
+                 bdaddr_t *dst, const char *name, uint8_t channel);
+
+int port_unregister(const char *path);
diff --git a/serial/proxy.c b/serial/proxy.c
new file mode 100644 (file)
index 0000000..46561d0
--- /dev/null
@@ -0,0 +1,1278 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/rfcomm.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/dbus-common.h"
+#include "../src/adapter.h"
+
+#include "log.h"
+#include "textfile.h"
+
+#include "error.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "proxy.h"
+
+#define SERIAL_PROXY_INTERFACE "org.bluez.SerialProxy"
+#define SERIAL_MANAGER_INTERFACE "org.bluez.SerialProxyManager"
+#define BUF_SIZE               1024
+
+typedef enum {
+       TTY_PROXY,
+       UNIX_SOCKET_PROXY,
+       TCP_SOCKET_PROXY,
+       UNKNOWN_PROXY_TYPE = 0xFF
+} proxy_type_t;
+
+struct serial_adapter {
+       struct btd_adapter      *btd_adapter;   /* Adapter pointer */
+       DBusConnection          *conn;          /* Adapter connection */
+       GSList                  *proxies;       /* Proxies list */
+};
+
+struct serial_proxy {
+       bdaddr_t        src;            /* Local address */
+       bdaddr_t        dst;            /* Remote address */
+       char            *path;          /* Proxy path */
+       char            *uuid128;       /* UUID 128 */
+       char            *address;       /* TTY or Unix socket name */
+       char            *owner;         /* Application bus name */
+       guint           watch;          /* Application watch */
+       short int       port;           /* TCP port */
+       proxy_type_t    type;           /* TTY or Unix socket */
+       struct termios  sys_ti;         /* Default TTY setting */
+       struct termios  proxy_ti;       /* Proxy TTY settings */
+       uint8_t         channel;        /* RFCOMM channel */
+       uint32_t        record_id;      /* Service record id */
+       GIOChannel      *io;            /* Server listen */
+       GIOChannel      *rfcomm;        /* Remote RFCOMM channel*/
+       GIOChannel      *local;         /* Local channel: TTY or Unix socket */
+       struct serial_adapter *adapter; /* Adapter pointer */
+};
+
+static GSList *adapters = NULL;
+static int sk_counter = 0;
+
+static void disable_proxy(struct serial_proxy *prx)
+{
+       if (prx->rfcomm) {
+               g_io_channel_shutdown(prx->rfcomm, TRUE, NULL);
+               g_io_channel_unref(prx->rfcomm);
+               prx->rfcomm = NULL;
+       }
+
+       if (prx->local) {
+               g_io_channel_shutdown(prx->local, TRUE, NULL);
+               g_io_channel_unref(prx->local);
+               prx->local = NULL;
+       }
+
+       remove_record_from_server(prx->record_id);
+       prx->record_id = 0;
+
+       g_io_channel_unref(prx->io);
+       prx->io = NULL;
+}
+
+static void proxy_free(struct serial_proxy *prx)
+{
+       g_free(prx->owner);
+       g_free(prx->path);
+       g_free(prx->address);
+       g_free(prx->uuid128);
+       g_free(prx);
+}
+
+static void add_lang_attr(sdp_record_t *r)
+{
+       sdp_lang_attr_t base_lang;
+       sdp_list_t *langs = 0;
+
+       /* UTF-8 MIBenum (http://www.iana.org/assignments/character-sets) */
+       base_lang.code_ISO639 = (0x65 << 8) | 0x6e;
+       base_lang.encoding = 106;
+       base_lang.base_offset = SDP_PRIMARY_LANG_BASE;
+       langs = sdp_list_append(0, &base_lang);
+       sdp_set_lang_attr(r, langs);
+       sdp_list_free(langs, 0);
+}
+
+static sdp_record_t *proxy_record_new(const char *uuid128, uint8_t channel)
+{
+       sdp_list_t *apseq, *aproto, *profiles, *proto[2], *root, *svclass_id;
+       uuid_t uuid, root_uuid, l2cap, rfcomm;
+       sdp_profile_desc_t profile;
+       sdp_record_t *record;
+       sdp_data_t *ch;
+
+       record = sdp_record_alloc();
+       if (!record)
+               return NULL;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(record, root);
+       sdp_list_free(root, NULL);
+
+       bt_string2uuid(&uuid, uuid128);
+       sdp_uuid128_to_uuid(&uuid);
+       svclass_id = sdp_list_append(NULL, &uuid);
+       sdp_set_service_classes(record, svclass_id);
+       sdp_list_free(svclass_id, NULL);
+
+       sdp_uuid16_create(&profile.uuid, SERIAL_PORT_PROFILE_ID);
+       profile.version = 0x0100;
+       profiles = sdp_list_append(NULL, &profile);
+       sdp_set_profile_descs(record, profiles);
+       sdp_list_free(profiles, NULL);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap);
+       apseq = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
+       proto[1] = sdp_list_append(NULL, &rfcomm);
+       ch = sdp_data_alloc(SDP_UINT8, &channel);
+       proto[1] = sdp_list_append(proto[1], ch);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(NULL, apseq);
+       sdp_set_access_protos(record, aproto);
+
+       add_lang_attr(record);
+
+       sdp_set_info_attr(record, "Serial Proxy", NULL, "Serial Proxy");
+
+       sdp_data_free(ch);
+       sdp_list_free(proto[0], NULL);
+       sdp_list_free(proto[1], NULL);
+       sdp_list_free(apseq, NULL);
+       sdp_list_free(aproto, NULL);
+
+       return record;
+}
+
+static int channel_write(GIOChannel *chan, char *buf, size_t size)
+{
+       size_t wbytes;
+       ssize_t written;
+       int fd;
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       wbytes = written = 0;
+       while (wbytes < size) {
+               written = write(fd, buf + wbytes, size - wbytes);
+
+               if (written)
+                       return -errno;
+
+               wbytes += written;
+       }
+
+       return 0;
+}
+
+static gboolean forward_data(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       char buf[BUF_SIZE];
+       struct serial_proxy *prx = data;
+       GIOChannel *dest;
+       ssize_t rbytes;
+       int fd, err;
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       dest = (chan == prx->rfcomm) ? prx->local : prx->rfcomm;
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       if (cond & (G_IO_HUP | G_IO_ERR)) {
+               /* Try forward remaining data */
+               do {
+                       rbytes = read(fd, buf, sizeof(buf));
+                       if (rbytes <= 0)
+                               break;
+
+                       err = channel_write(dest, buf, rbytes);
+               } while (err == 0);
+
+               g_io_channel_shutdown(prx->local, TRUE, NULL);
+               g_io_channel_unref(prx->local);
+               prx->local = NULL;
+
+               g_io_channel_shutdown(prx->rfcomm, TRUE, NULL);
+               g_io_channel_unref(prx->rfcomm);
+               prx->rfcomm = NULL;
+
+               return FALSE;
+       }
+
+       rbytes = read(fd, buf, sizeof(buf));
+       if (rbytes < 0)
+               return FALSE;
+
+       err = channel_write(dest, buf, rbytes);
+       if (err != 0)
+               return FALSE;
+
+       return TRUE;
+}
+
+static inline int unix_socket_connect(const char *address)
+{
+       struct sockaddr_un addr;
+       int err, sk;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sun_family = PF_UNIX;
+
+       if (strncmp("x00", address, 3) == 0) {
+               /*
+                * Abstract namespace: first byte NULL, x00
+                * must be removed from the original address.
+                */
+               strncpy(addr.sun_path + 1, address + 3,
+                                               sizeof(addr.sun_path) - 2);
+       } else {
+               /* Filesystem address */
+               strncpy(addr.sun_path, address, sizeof(addr.sun_path) - 1);
+       }
+
+       /* Unix socket */
+       sk = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (sk < 0) {
+               err = errno;
+               error("Unix socket(%s) create failed: %s(%d)",
+                               address, strerror(err), err);
+               return -err;
+       }
+
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               err = errno;
+               error("Unix socket(%s) connect failed: %s(%d)",
+                               address, strerror(err), err);
+               close(sk);
+               errno = err;
+               return -err;
+       }
+
+       return sk;
+}
+
+static int tcp_socket_connect(const char *address)
+{
+       struct sockaddr_in addr;
+       int err, sk;
+       unsigned short int port;
+
+       memset(&addr, 0, sizeof(addr));
+
+       if (strncmp(address, "localhost", 9) != 0) {
+               error("Address should have the form localhost:port.");
+               return -1;
+       }
+       port = atoi(strchr(address, ':') + 1);
+       if (port <= 0) {
+               error("Invalid port '%d'.", port);
+               return -1;
+       }
+       addr.sin_family = AF_INET;
+       addr.sin_addr.s_addr = inet_addr("127.0.0.1");
+       addr.sin_port = htons(port);
+
+       sk = socket(PF_INET, SOCK_STREAM, 0);
+       if (sk < 0) {
+               err = errno;
+               error("TCP socket(%s) create failed %s(%d)", address,
+                                                       strerror(err), err);
+               return -err;
+       }
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               err = errno;
+               error("TCP socket(%s) connect failed: %s(%d)",
+                                               address, strerror(err), err);
+               close(sk);
+               errno = err;
+               return -err;
+       }
+       return sk;
+}
+
+static inline int tty_open(const char *tty, struct termios *ti)
+{
+       int err, sk;
+
+       sk = open(tty, O_RDWR | O_NOCTTY);
+       if (sk < 0) {
+               err = errno;
+               error("Can't open TTY %s: %s(%d)", tty, strerror(err), err);
+               return -err;
+       }
+
+       if (ti && tcsetattr(sk, TCSANOW, ti) < 0) {
+               err = errno;
+               error("Can't change serial settings: %s(%d)",
+                               strerror(err), err);
+               close(sk);
+               errno = err;
+               return -err;
+       }
+
+       return sk;
+}
+
+static void connect_event_cb(GIOChannel *chan, GError *conn_err, gpointer data)
+{
+       struct serial_proxy *prx = data;
+       int sk;
+
+       if (conn_err) {
+               error("%s", conn_err->message);
+               goto drop;
+       }
+
+       /* Connect local */
+       switch (prx->type) {
+       case UNIX_SOCKET_PROXY:
+               sk = unix_socket_connect(prx->address);
+               break;
+       case TTY_PROXY:
+               sk = tty_open(prx->address, &prx->proxy_ti);
+               break;
+       case TCP_SOCKET_PROXY:
+               sk = tcp_socket_connect(prx->address);
+               break;
+       default:
+               sk = -1;
+       }
+
+       if (sk < 0)
+               goto drop;
+
+       prx->local = g_io_channel_unix_new(sk);
+
+       g_io_add_watch(prx->rfcomm,
+                       G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                       forward_data, prx);
+
+       g_io_add_watch(prx->local,
+                       G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                       forward_data, prx);
+
+       return;
+
+drop:
+       g_io_channel_shutdown(prx->rfcomm, TRUE, NULL);
+       g_io_channel_unref(prx->rfcomm);
+       prx->rfcomm = NULL;
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+       struct serial_proxy *prx = user_data;
+       GError *err = NULL;
+
+       if (derr) {
+               error("Access denied: %s", derr->message);
+               goto reject;
+       }
+
+       if (!bt_io_accept(prx->rfcomm, connect_event_cb, prx, NULL,
+                                                       &err)) {
+               error("bt_io_accept: %s", err->message);
+               g_error_free(err);
+               goto reject;
+       }
+
+       return;
+
+reject:
+       g_io_channel_shutdown(prx->rfcomm, TRUE, NULL);
+       g_io_channel_unref(prx->rfcomm);
+       prx->rfcomm = NULL;
+}
+
+static void confirm_event_cb(GIOChannel *chan, gpointer user_data)
+{
+       struct serial_proxy *prx = user_data;
+       int perr;
+       char address[18];
+       GError *err = NULL;
+
+       bt_io_get(chan, BT_IO_RFCOMM, &err,
+                       BT_IO_OPT_DEST_BDADDR, &prx->dst,
+                       BT_IO_OPT_DEST, address,
+                       BT_IO_OPT_INVALID);
+       if (err) {
+               error("%s", err->message);
+               g_error_free(err);
+               goto drop;
+       }
+
+       if (prx->rfcomm) {
+               error("Refusing connect from %s: Proxy already in use",
+                               address);
+               goto drop;
+       }
+
+       DBG("Serial Proxy: incoming connect from %s", address);
+
+       prx->rfcomm = g_io_channel_ref(chan);
+
+       perr = btd_request_authorization(&prx->src, &prx->dst,
+                                       prx->uuid128, auth_cb, prx);
+       if (perr < 0) {
+               error("Refusing connect from %s: %s (%d)", address,
+                               strerror(-perr), -perr);
+               g_io_channel_unref(prx->rfcomm);
+               prx->rfcomm = NULL;
+               goto drop;
+       }
+
+       return;
+
+drop:
+       g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static int enable_proxy(struct serial_proxy *prx)
+{
+       sdp_record_t *record;
+       GError *gerr = NULL;
+       int err;
+
+       if (prx->io)
+               return -EALREADY;
+
+       /* Listen */
+       prx->io = bt_io_listen(BT_IO_RFCOMM, NULL, confirm_event_cb, prx,
+                               NULL, &gerr,
+                               BT_IO_OPT_SOURCE_BDADDR, &prx->src,
+                               BT_IO_OPT_INVALID);
+       if (!prx->io)
+               goto failed;
+
+       bt_io_get(prx->io, BT_IO_RFCOMM, &gerr,
+                       BT_IO_OPT_CHANNEL, &prx->channel,
+                       BT_IO_OPT_INVALID);
+       if (gerr) {
+               g_io_channel_unref(prx->io);
+               prx->io = NULL;
+               goto failed;
+       }
+
+       DBG("Allocated channel %d", prx->channel);
+
+       g_io_channel_set_close_on_unref(prx->io, TRUE);
+
+       record = proxy_record_new(prx->uuid128, prx->channel);
+       if (!record) {
+               g_io_channel_unref(prx->io);
+               return -ENOMEM;
+       }
+
+       err = add_record_to_server(&prx->src, record);
+       if (err < 0) {
+               sdp_record_free(record);
+               g_io_channel_unref(prx->io);
+               return err;
+       }
+
+       prx->record_id = record->handle;
+
+       return 0;
+
+failed:
+       error("%s", gerr->message);
+       g_error_free(gerr);
+       return -EIO;
+
+}
+static DBusMessage *proxy_enable(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       struct serial_proxy *prx = data;
+       int err;
+
+       err = enable_proxy(prx);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *proxy_disable(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       struct serial_proxy *prx = data;
+
+       if (!prx->io)
+               return btd_error_failed(msg, "Not enabled");
+
+       /* Remove the watches and unregister the record */
+       disable_proxy(prx);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *proxy_get_info(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       struct serial_proxy *prx = data;
+       DBusMessage *reply;
+       DBusMessageIter iter, dict;
+       dbus_bool_t boolean;
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       dbus_message_iter_init_append(reply, &iter);
+
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                       DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+       dict_append_entry(&dict, "uuid", DBUS_TYPE_STRING, &prx->uuid128);
+
+       dict_append_entry(&dict, "address", DBUS_TYPE_STRING, &prx->address);
+
+       if (prx->channel)
+               dict_append_entry(&dict, "channel",
+                                       DBUS_TYPE_BYTE, &prx->channel);
+
+       boolean = (prx->io ? TRUE : FALSE);
+       dict_append_entry(&dict, "enabled", DBUS_TYPE_BOOLEAN, &boolean);
+
+       boolean = (prx->rfcomm ? TRUE : FALSE);
+       dict_append_entry(&dict, "connected", DBUS_TYPE_BOOLEAN, &boolean);
+
+       /* If connected: append the remote address */
+       if (boolean) {
+               char bda[18];
+               const char *pstr = bda;
+
+               ba2str(&prx->dst, bda);
+               dict_append_entry(&dict, "address", DBUS_TYPE_STRING, &pstr);
+       }
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static struct {
+       const char      *str;
+       speed_t         speed;
+} supported_speed[]  = {
+       {"50",          B50     },
+       {"300",         B300    },
+       {"600",         B600    },
+       {"1200",        B1200   },
+       {"1800",        B1800   },
+       {"2400",        B2400   },
+       {"4800",        B4800   },
+       {"9600",        B9600   },
+       {"19200",       B19200  },
+       {"38400",       B38400  },
+       {"57600",       B57600  },
+       {"115200",      B115200 },
+       { NULL,         B0      }
+};
+
+static speed_t str2speed(const char *str, speed_t *speed)
+{
+       int i;
+
+       for (i = 0; supported_speed[i].str; i++) {
+               if (strcmp(supported_speed[i].str, str) != 0)
+                       continue;
+
+               if (speed)
+                       *speed = supported_speed[i].speed;
+
+               return supported_speed[i].speed;
+       }
+
+       return B0;
+}
+
+static int set_parity(const char *str, tcflag_t *ctrl)
+{
+       if (strcasecmp("even", str) == 0) {
+               *ctrl |= PARENB;
+               *ctrl &= ~PARODD;
+       } else if (strcasecmp("odd", str) == 0) {
+               *ctrl |= PARENB;
+               *ctrl |= PARODD;
+       } else if (strcasecmp("mark", str) == 0)
+               *ctrl |= PARENB;
+       else if ((strcasecmp("none", str) == 0) ||
+                       (strcasecmp("space", str) == 0))
+               *ctrl &= ~PARENB;
+       else
+               return -1;
+
+       return 0;
+}
+
+static int set_databits(uint8_t databits, tcflag_t *ctrl)
+{
+       if (databits < 5 || databits > 8)
+               return -EINVAL;
+
+       *ctrl &= ~CSIZE;
+       switch (databits) {
+       case 5:
+               *ctrl |= CS5;
+               break;
+       case 6:
+               *ctrl |= CS6;
+               break;
+       case 7:
+               *ctrl |= CS7;
+               break;
+       case 8:
+               *ctrl |= CS8;
+               break;
+       }
+
+       return 0;
+}
+
+static int set_stopbits(uint8_t stopbits, tcflag_t *ctrl)
+{
+       /* 1.5 will not be allowed */
+       switch (stopbits) {
+       case 1:
+               *ctrl &= ~CSTOPB;
+               return 0;
+       case 2:
+               *ctrl |= CSTOPB;
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static DBusMessage *proxy_set_serial_params(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct serial_proxy *prx = data;
+       const char *ratestr, *paritystr;
+       uint8_t databits, stopbits;
+       tcflag_t ctrl;          /* Control mode flags */
+       speed_t speed = B0;     /* In/Out speed */
+
+       /* Don't allow change TTY settings if it is open */
+       if (prx->local)
+               return btd_error_not_authorized(msg);
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_STRING, &ratestr,
+                               DBUS_TYPE_BYTE, &databits,
+                               DBUS_TYPE_BYTE, &stopbits,
+                               DBUS_TYPE_STRING, &paritystr,
+                               DBUS_TYPE_INVALID))
+               return NULL;
+
+       if (str2speed(ratestr, &speed)  == B0)
+               return btd_error_invalid_args(msg);
+
+       ctrl = prx->proxy_ti.c_cflag;
+       if (set_databits(databits, &ctrl) < 0)
+               return btd_error_invalid_args(msg);
+
+       if (set_stopbits(stopbits, &ctrl) < 0)
+               return btd_error_invalid_args(msg);
+
+       if (set_parity(paritystr, &ctrl) < 0)
+               return btd_error_invalid_args(msg);
+
+       prx->proxy_ti.c_cflag = ctrl;
+       prx->proxy_ti.c_cflag |= (CLOCAL | CREAD);
+       cfsetispeed(&prx->proxy_ti, speed);
+       cfsetospeed(&prx->proxy_ti, speed);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static GDBusMethodTable proxy_methods[] = {
+       { "Enable",                     "",     "",     proxy_enable },
+       { "Disable",                    "",     "",     proxy_disable },
+       { "GetInfo",                    "",     "a{sv}",proxy_get_info },
+       { "SetSerialParameters",        "syys", "",     proxy_set_serial_params },
+       { },
+};
+
+static void proxy_path_unregister(gpointer data)
+{
+       struct serial_proxy *prx = data;
+       int sk;
+
+       DBG("Unregistered proxy: %s", prx->address);
+
+       if (prx->type != TTY_PROXY)
+               goto done;
+
+       /* Restore the initial TTY configuration */
+       sk = open(prx->address, O_RDWR | O_NOCTTY);
+       if (sk >= 0) {
+               tcsetattr(sk, TCSAFLUSH, &prx->sys_ti);
+               close(sk);
+       }
+done:
+
+       proxy_free(prx);
+}
+
+static int register_proxy_object(struct serial_proxy *prx)
+{
+       struct serial_adapter *adapter = prx->adapter;
+       char path[MAX_PATH_LENGTH + 1];
+
+       snprintf(path, MAX_PATH_LENGTH, "%s/proxy%d",
+                       adapter_get_path(adapter->btd_adapter), sk_counter++);
+
+       if (!g_dbus_register_interface(adapter->conn, path,
+                                       SERIAL_PROXY_INTERFACE,
+                                       proxy_methods, NULL, NULL,
+                                       prx, proxy_path_unregister)) {
+               error("D-Bus failed to register %s path", path);
+               return -1;
+       }
+
+       prx->path = g_strdup(path);
+       adapter->proxies = g_slist_append(adapter->proxies, prx);
+
+       DBG("Registered proxy: %s", path);
+
+       return 0;
+}
+
+static int proxy_tty_register(struct serial_adapter *adapter,
+                               const char *uuid128, const char *address,
+                               struct termios *ti,
+                               struct serial_proxy **proxy)
+{
+       struct termios sys_ti;
+       struct serial_proxy *prx;
+       int sk, ret;
+
+       sk = open(address, O_RDONLY | O_NOCTTY);
+       if (sk < 0) {
+               error("Cant open TTY: %s(%d)", strerror(errno), errno);
+               return -EINVAL;
+       }
+
+       prx = g_new0(struct serial_proxy, 1);
+       prx->address = g_strdup(address);
+       prx->uuid128 = g_strdup(uuid128);
+       prx->type = TTY_PROXY;
+       adapter_get_address(adapter->btd_adapter, &prx->src);
+       prx->adapter = adapter;
+
+       /* Current TTY settings */
+       memset(&sys_ti, 0, sizeof(sys_ti));
+       tcgetattr(sk, &sys_ti);
+       memcpy(&prx->sys_ti, &sys_ti, sizeof(sys_ti));
+       close(sk);
+
+       if (!ti) {
+               /* Use current settings */
+               memcpy(&prx->proxy_ti, &sys_ti, sizeof(sys_ti));
+       } else {
+               /* New TTY settings: user provided */
+               memcpy(&prx->proxy_ti, ti, sizeof(*ti));
+       }
+
+       ret = register_proxy_object(prx);
+       if (ret < 0) {
+               proxy_free(prx);
+               return ret;
+       }
+
+       *proxy = prx;
+
+       return ret;
+}
+
+static int proxy_socket_register(struct serial_adapter *adapter,
+                               const char *uuid128, const char *address,
+                               struct serial_proxy **proxy)
+{
+       struct serial_proxy *prx;
+       int ret;
+
+       prx = g_new0(struct serial_proxy, 1);
+       prx->address = g_strdup(address);
+       prx->uuid128 = g_strdup(uuid128);
+       prx->type = UNIX_SOCKET_PROXY;
+       adapter_get_address(adapter->btd_adapter, &prx->src);
+       prx->adapter = adapter;
+
+       ret = register_proxy_object(prx);
+       if (ret < 0) {
+               proxy_free(prx);
+               return ret;
+       }
+
+       *proxy = prx;
+
+       return ret;
+}
+
+static int proxy_tcp_register(struct serial_adapter *adapter,
+                               const char *uuid128, const char *address,
+                               struct serial_proxy **proxy)
+{
+       struct serial_proxy *prx;
+       int ret;
+
+       prx = g_new0(struct serial_proxy, 1);
+       prx->address = g_strdup(address);
+       prx->uuid128 = g_strdup(uuid128);
+       prx->type = TCP_SOCKET_PROXY;
+       adapter_get_address(adapter->btd_adapter, &prx->src);
+       prx->adapter = adapter;
+
+       ret = register_proxy_object(prx);
+       if (ret < 0) {
+               proxy_free(prx);
+               return ret;
+       }
+
+       *proxy = prx;
+
+       return ret;
+}
+
+static proxy_type_t addr2type(const char *address)
+{
+       struct stat st;
+
+       if (stat(address, &st) < 0) {
+               /*
+                * Unix socket: if the sun_path starts with null byte
+                * it refers to abstract namespace. 'x00' will be used
+                * to represent the null byte.
+                */
+               if (strncmp("localhost:", address, 10) == 0)
+                       return TCP_SOCKET_PROXY;
+               if (strncmp("x00", address, 3) != 0)
+                       return UNKNOWN_PROXY_TYPE;
+               else
+                       return UNIX_SOCKET_PROXY;
+       } else {
+               /* Filesystem: char device or unix socket */
+               if (S_ISCHR(st.st_mode) && strncmp("/dev/", address, 4) == 0)
+                       return TTY_PROXY;
+               else if (S_ISSOCK(st.st_mode))
+                       return UNIX_SOCKET_PROXY;
+               else
+                       return UNKNOWN_PROXY_TYPE;
+       }
+}
+
+static int proxy_addrcmp(gconstpointer proxy, gconstpointer addr)
+{
+       const struct serial_proxy *prx = proxy;
+       const char *address = addr;
+
+       return strcmp(prx->address, address);
+}
+
+static int proxy_pathcmp(gconstpointer proxy, gconstpointer p)
+{
+       const struct serial_proxy *prx = proxy;
+       const char *path = p;
+
+       return strcmp(prx->path, path);
+}
+
+static int register_proxy(struct serial_adapter *adapter,
+                               const char *uuid_str, const char *address,
+                               struct serial_proxy **proxy)
+{
+       proxy_type_t type;
+       int err;
+
+       type = addr2type(address);
+       if (type == UNKNOWN_PROXY_TYPE)
+               return -EINVAL;
+
+       /* Only one proxy per address(TTY or unix socket) is allowed */
+       if (g_slist_find_custom(adapter->proxies, address, proxy_addrcmp))
+               return -EALREADY;
+
+       switch (type) {
+       case UNIX_SOCKET_PROXY:
+               err = proxy_socket_register(adapter, uuid_str, address, proxy);
+               break;
+       case TTY_PROXY:
+               err = proxy_tty_register(adapter, uuid_str, address, NULL,
+                                       proxy);
+               break;
+       case TCP_SOCKET_PROXY:
+               err = proxy_tcp_register(adapter, uuid_str, address, proxy);
+               break;
+       default:
+               err = -EINVAL;
+       }
+
+       if (err < 0)
+               return err;
+
+       g_dbus_emit_signal(adapter->conn,
+                               adapter_get_path(adapter->btd_adapter),
+                               SERIAL_MANAGER_INTERFACE, "ProxyCreated",
+                               DBUS_TYPE_STRING, &(*proxy)->path,
+                               DBUS_TYPE_INVALID);
+
+       return 0;
+}
+
+static void unregister_proxy(struct serial_proxy *proxy)
+{
+       struct serial_adapter *adapter = proxy->adapter;
+       char *path = g_strdup(proxy->path);
+
+       if (proxy->watch > 0)
+               g_dbus_remove_watch(adapter->conn, proxy->watch);
+
+       g_dbus_emit_signal(adapter->conn,
+                       adapter_get_path(adapter->btd_adapter),
+                       SERIAL_MANAGER_INTERFACE, "ProxyRemoved",
+                       DBUS_TYPE_STRING, &path,
+                       DBUS_TYPE_INVALID);
+
+       adapter->proxies = g_slist_remove(adapter->proxies, proxy);
+
+       g_dbus_unregister_interface(adapter->conn, path,
+                                       SERIAL_PROXY_INTERFACE);
+
+       g_free(path);
+}
+
+static void watch_proxy(DBusConnection *connection, void *user_data)
+{
+       struct serial_proxy *proxy = user_data;
+
+       proxy->watch = 0;
+       unregister_proxy(proxy);
+}
+
+static DBusMessage *create_proxy(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       struct serial_adapter *adapter = data;
+       struct serial_proxy *proxy;
+       const char *pattern, *address;
+       char *uuid_str;
+       int err;
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_STRING, &pattern,
+                               DBUS_TYPE_STRING, &address,
+                               DBUS_TYPE_INVALID))
+               return NULL;
+
+       uuid_str = bt_name2string(pattern);
+       if (!uuid_str)
+               return btd_error_invalid_args(msg);
+
+       err = register_proxy(adapter, uuid_str, address, &proxy);
+       g_free(uuid_str);
+
+       if (err == -EINVAL)
+               return btd_error_invalid_args(msg);
+       else if (err == -EALREADY)
+               return btd_error_already_exists(msg);
+       else if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       proxy->owner = g_strdup(dbus_message_get_sender(msg));
+       proxy->watch = g_dbus_add_disconnect_watch(conn, proxy->owner,
+                                               watch_proxy,
+                                               proxy, NULL);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &proxy->path,
+                                       DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *list_proxies(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       struct serial_adapter *adapter = data;
+       const GSList *l;
+       DBusMessage *reply;
+       DBusMessageIter iter, iter_array;
+
+       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_TYPE_STRING_AS_STRING, &iter_array);
+
+       for (l = adapter->proxies; l; l = l->next) {
+               struct serial_proxy *prx = l->data;
+
+               dbus_message_iter_append_basic(&iter_array,
+                               DBUS_TYPE_STRING, &prx->path);
+       }
+
+       dbus_message_iter_close_container(&iter, &iter_array);
+
+       return reply;
+}
+
+static DBusMessage *remove_proxy(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       struct serial_adapter *adapter = data;
+       struct serial_proxy *prx;
+       const char *path, *sender;
+       GSList *l;
+
+       if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_STRING, &path,
+                               DBUS_TYPE_INVALID))
+               return NULL;
+
+       l = g_slist_find_custom(adapter->proxies, path, proxy_pathcmp);
+       if (!l)
+               return btd_error_does_not_exist(msg);
+
+       prx = l->data;
+
+       sender = dbus_message_get_sender(msg);
+       if (g_strcmp0(prx->owner, sender) != 0)
+               return btd_error_not_authorized(msg);
+
+       unregister_proxy(prx);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static void manager_path_unregister(void *data)
+{
+       struct serial_adapter *adapter = data;
+       GSList *l;
+
+       /* Remove proxy objects */
+       for (l = adapter->proxies; l; l = l->next) {
+               struct serial_proxy *prx = l->data;
+               char *path = g_strdup(prx->path);
+
+               g_dbus_unregister_interface(adapter->conn, path,
+                                       SERIAL_PROXY_INTERFACE);
+               g_free(path);
+       }
+
+       if (adapter->conn)
+               dbus_connection_unref(adapter->conn);
+
+       adapters = g_slist_remove(adapters, adapter);
+       g_slist_free(adapter->proxies);
+       btd_adapter_unref(adapter->btd_adapter);
+       g_free(adapter);
+}
+
+static GDBusMethodTable manager_methods[] = {
+       { "CreateProxy",                "ss",   "s",    create_proxy },
+       { "ListProxies",                "",     "as",   list_proxies },
+       { "RemoveProxy",                "s",    "",     remove_proxy },
+       { },
+};
+
+static GDBusSignalTable manager_signals[] = {
+       { "ProxyCreated",               "s"     },
+       { "ProxyRemoved",               "s"     },
+       { }
+};
+
+static struct serial_adapter *find_adapter(GSList *list,
+                                       struct btd_adapter *btd_adapter)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct serial_adapter *adapter = l->data;
+
+               if (adapter->btd_adapter == btd_adapter)
+                       return adapter;
+       }
+
+       return NULL;
+}
+
+static void serial_proxy_init(struct serial_adapter *adapter)
+{
+       GKeyFile *config;
+       GError *gerr = NULL;
+       const char *file = CONFIGDIR "/serial.conf";
+       char **group_list;
+       int i;
+
+       config = g_key_file_new();
+
+       if (!g_key_file_load_from_file(config, file, 0, &gerr)) {
+               error("Parsing %s failed: %s", file, gerr->message);
+               g_error_free(gerr);
+               g_key_file_free(config);
+               return;
+       }
+
+       group_list = g_key_file_get_groups(config, NULL);
+
+       for (i = 0; group_list[i] != NULL; i++) {
+               char *group_str = group_list[i], *uuid_str, *address;
+               int err;
+               struct serial_proxy *prx;
+
+               /* string length of "Proxy" is 5 */
+               if (strlen(group_str) < 5 || strncmp(group_str, "Proxy", 5))
+                       continue;
+
+               uuid_str = g_key_file_get_string(config, group_str, "UUID",
+                                                                       &gerr);
+               if (gerr) {
+                       DBG("%s: %s", file, gerr->message);
+                       g_error_free(gerr);
+                       g_key_file_free(config);
+                       g_strfreev(group_list);
+                       return;
+               }
+
+               address = g_key_file_get_string(config, group_str, "Address",
+                                                                       &gerr);
+               if (gerr) {
+                       DBG("%s: %s", file, gerr->message);
+                       g_error_free(gerr);
+                       g_key_file_free(config);
+                       g_free(uuid_str);
+                       g_strfreev(group_list);
+                       return;
+               }
+
+               err = register_proxy(adapter, uuid_str, address, &prx);
+               if (err == -EINVAL)
+                       error("Invalid address.");
+               else if (err == -EALREADY)
+                       DBG("Proxy already exists.");
+               else if (err < 0)
+                       error("Proxy creation failed (%s)", strerror(-err));
+               else {
+                       err = enable_proxy(prx);
+                       if (err < 0)
+                               error("Proxy enable failed (%s)",
+                                               strerror(-err));
+               }
+
+               g_free(uuid_str);
+               g_free(address);
+       }
+
+       g_strfreev(group_list);
+       g_key_file_free(config);
+}
+
+int proxy_register(DBusConnection *conn, struct btd_adapter *btd_adapter)
+{
+       struct serial_adapter *adapter;
+       const char *path;
+
+       adapter = find_adapter(adapters, btd_adapter);
+       if (adapter)
+               return -EINVAL;
+
+       adapter = g_new0(struct serial_adapter, 1);
+       adapter->conn = dbus_connection_ref(conn);
+       adapter->btd_adapter = btd_adapter_ref(btd_adapter);
+
+       path = adapter_get_path(btd_adapter);
+
+       if (!g_dbus_register_interface(conn, path,
+                                       SERIAL_MANAGER_INTERFACE,
+                                       manager_methods, manager_signals, NULL,
+                                       adapter, manager_path_unregister)) {
+               error("Failed to register %s interface to %s",
+                               SERIAL_MANAGER_INTERFACE, path);
+               return -1;
+       }
+
+       adapters = g_slist_append(adapters, adapter);
+
+       DBG("Registered interface %s on path %s",
+               SERIAL_MANAGER_INTERFACE, path);
+
+       serial_proxy_init(adapter);
+
+       return 0;
+}
+
+void proxy_unregister(struct btd_adapter *btd_adapter)
+{
+       struct serial_adapter *adapter;
+
+       adapter = find_adapter(adapters, btd_adapter);
+       if (!adapter)
+               return;
+
+       g_dbus_unregister_interface(adapter->conn,
+                       adapter_get_path(btd_adapter),
+                       SERIAL_MANAGER_INTERFACE);
+}
diff --git a/serial/proxy.h b/serial/proxy.h
new file mode 100644 (file)
index 0000000..7871665
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 proxy_register(DBusConnection *conn, struct btd_adapter *btd_adapter);
+void proxy_unregister(struct btd_adapter *btd_adapter);
diff --git a/serial/serial.conf b/serial/serial.conf
new file mode 100644 (file)
index 0000000..43ee6af
--- /dev/null
@@ -0,0 +1,10 @@
+# Configuration file for serial
+
+# There could be multiple proxy sections, the format is [Proxy <user chosen name>]
+#[Proxy DUN]
+
+# UUID for DUN proxy service
+#UUID=00001103-0000-1000-8000-00805F9B34FB
+
+# Address for device node
+#Address=/dev/ttyx
diff --git a/src/adapter.c b/src/adapter.c
new file mode 100644 (file)
index 0000000..2e1df76
--- /dev/null
@@ -0,0 +1,3945 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "textfile.h"
+
+#include "hcid.h"
+#include "sdpd.h"
+#include "adapter.h"
+#include "manager.h"
+#include "device.h"
+#include "dbus-common.h"
+#include "event.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "agent.h"
+#include "storage.h"
+#include "attrib-server.h"
+#include "att.h"
+
+/* Flags Descriptions */
+#define EIR_LIM_DISC                0x01 /* LE Limited Discoverable Mode */
+#define EIR_GEN_DISC                0x02 /* LE General Discoverable Mode */
+#define EIR_BREDR_UNSUP             0x04 /* BR/EDR Not Supported */
+#define EIR_SIM_CONTROLLER          0x08 /* Simultaneous LE and BR/EDR to Same
+                                           Device Capable (Controller) */
+#define EIR_SIM_HOST                0x10 /* Simultaneous LE and BR/EDR to Same
+                                           Device Capable (Host) */
+
+#define ADV_TYPE_IND           0x00
+#define ADV_TYPE_DIRECT_IND    0x01
+
+#define IO_CAPABILITY_DISPLAYONLY      0x00
+#define IO_CAPABILITY_DISPLAYYESNO     0x01
+#define IO_CAPABILITY_KEYBOARDONLY     0x02
+#define IO_CAPABILITY_NOINPUTNOOUTPUT  0x03
+#define IO_CAPABILITY_INVALID          0xFF
+
+/* Limited Discoverable bit mask in CoD */
+#define LIMITED_BIT                    0x002000
+#define check_address(address) bachk(address)
+
+static DBusConnection *connection = NULL;
+static GSList *adapter_drivers = NULL;
+
+static GSList *ops_candidates = NULL;
+
+const struct btd_adapter_ops *adapter_ops = NULL;
+
+struct session_req {
+       struct btd_adapter      *adapter;
+       DBusConnection          *conn;          /* Connection reference */
+       DBusMessage             *msg;           /* Unreplied message ref */
+       char                    *owner;         /* Bus name of the owner */
+       guint                   id;             /* Listener id */
+       uint8_t                 mode;           /* Requested mode */
+       int                     refcount;       /* Session refcount */
+       gboolean                got_reply;      /* Agent reply received */
+};
+
+struct service_auth {
+       service_auth_cb cb;
+       void *user_data;
+       struct btd_device *device;
+       struct btd_adapter *adapter;
+};
+
+struct btd_adapter {
+       uint16_t dev_id;
+       int up;
+       char *path;                     /* adapter object path */
+       bdaddr_t bdaddr;                /* adapter Bluetooth Address */
+       uint32_t dev_class;             /* Class of Device */
+       guint discov_timeout_id;        /* discoverable timeout id */
+       guint stop_discov_id;           /* stop inquiry/scanning id */
+       uint32_t discov_timeout;        /* discoverable time(sec) */
+       guint pairable_timeout_id;      /* pairable timeout id */
+       uint32_t pairable_timeout;      /* pairable time(sec) */
+       uint8_t scan_mode;              /* scan mode: SCAN_DISABLED, SCAN_PAGE,
+                                        * SCAN_INQUIRY */
+#ifdef __TIZEN_PATCH__
+       // Adding Limited state for setting limited discoverable mode
+       gboolean limited;               /* limited discoverable state */
+#endif
+       uint8_t mode;                   /* off, connectable, discoverable,
+                                        * limited */
+       uint8_t global_mode;            /* last valid global mode */
+       struct session_req *pending_mode;
+       int state;                      /* standard inq, periodic inq, name
+                                        * resolving, suspended discovery */
+       GSList *found_devices;
+       GSList *oor_devices;            /* out of range device list */
+       struct agent *agent;            /* For the new API */
+       guint auth_idle_id;             /* Ongoing authorization */
+       GSList *connections;            /* Connected devices */
+       GSList *devices;                /* Devices structure pointers */
+       GSList *mode_sessions;          /* Request Mode sessions */
+       GSList *disc_sessions;          /* Discovery sessions */
+       guint scheduler_id;             /* Scheduler handle */
+       sdp_list_t *services;           /* Services associated to adapter */
+
+       struct hci_dev dev;             /* hci info */
+       gboolean pairable;              /* pairable state */
+       gboolean initialized;
+
+       gboolean off_requested;         /* DEVDOWN ioctl was called */
+
+       gint ref;
+
+       GSList *powered_callbacks;
+
+       gboolean name_stored;
+
+       GSList *loaded_drivers;
+};
+
+static void adapter_set_pairable_timeout(struct btd_adapter *adapter,
+                                       guint interval);
+
+static int found_device_cmp(const struct remote_dev_info *d1,
+                       const struct remote_dev_info *d2)
+{
+       int ret;
+
+       if (bacmp(&d2->bdaddr, BDADDR_ANY)) {
+               ret = bacmp(&d1->bdaddr, &d2->bdaddr);
+               if (ret)
+                       return ret;
+       }
+
+       if (d2->name_status != NAME_ANY) {
+               ret = (d1->name_status - d2->name_status);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void dev_info_free(struct remote_dev_info *dev)
+{
+       g_free(dev->name);
+       g_free(dev->alias);
+       g_slist_foreach(dev->services, (GFunc) g_free, NULL);
+       g_slist_free(dev->services);
+       g_strfreev(dev->uuids);
+       g_free(dev);
+}
+
+/*
+ * Device name expansion
+ *   %d - device id
+ */
+static char *expand_name(char *dst, int size, char *str, int dev_id)
+{
+       register int sp, np, olen;
+       char *opt, buf[10];
+
+       if (!str || !dst)
+               return NULL;
+
+       sp = np = 0;
+       while (np < size - 1 && str[sp]) {
+               switch (str[sp]) {
+               case '%':
+                       opt = NULL;
+
+                       switch (str[sp+1]) {
+                       case 'd':
+                               sprintf(buf, "%d", dev_id);
+                               opt = buf;
+                               break;
+
+                       case 'h':
+                               opt = main_opts.host_name;
+                               break;
+
+                       case '%':
+                               dst[np++] = str[sp++];
+                               /* fall through */
+                       default:
+                               sp++;
+                               continue;
+                       }
+
+                       if (opt) {
+                               /* substitute */
+                               olen = strlen(opt);
+                               if (np + olen < size - 1)
+                                       memcpy(dst + np, opt, olen);
+                               np += olen;
+                       }
+                       sp += 2;
+                       continue;
+
+               case '\\':
+                       sp++;
+                       /* fall through */
+               default:
+                       dst[np++] = str[sp++];
+                       break;
+               }
+       }
+       dst[np] = '\0';
+       return dst;
+}
+
+int btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major,
+                                                               uint8_t minor)
+{
+       return adapter_ops->set_dev_class(adapter->dev_id, major, minor);
+}
+
+static int pending_remote_name_cancel(struct btd_adapter *adapter)
+{
+       struct remote_dev_info *dev, match;
+       int err;
+
+       /* find the pending remote name request */
+       memset(&match, 0, sizeof(struct remote_dev_info));
+       bacpy(&match.bdaddr, BDADDR_ANY);
+       match.name_status = NAME_REQUESTED;
+
+       dev = adapter_search_found_devices(adapter, &match);
+       if (!dev) /* no pending request */
+               return -ENODATA;
+
+       err = adapter_ops->cancel_resolve_name(adapter->dev_id, &dev->bdaddr);
+       if (err < 0)
+               error("Remote name cancel failed: %s(%d)",
+                                               strerror(errno), errno);
+       return err;
+}
+
+int adapter_resolve_names(struct btd_adapter *adapter)
+{
+       struct remote_dev_info *dev, match;
+       int err;
+
+       /* Do not attempt to resolve more names if on suspended state */
+       if (adapter->state & STATE_SUSPENDED)
+               return 0;
+
+       memset(&match, 0, sizeof(struct remote_dev_info));
+       bacpy(&match.bdaddr, BDADDR_ANY);
+       match.name_status = NAME_REQUIRED;
+
+       dev = adapter_search_found_devices(adapter, &match);
+       if (!dev)
+               return -ENODATA;
+
+       /* send at least one request or return failed if the list is empty */
+       do {
+               /* flag to indicate the current remote name requested */
+               dev->name_status = NAME_REQUESTED;
+
+               err = adapter_ops->resolve_name(adapter->dev_id, &dev->bdaddr);
+
+               if (!err)
+                       break;
+
+               error("Unable to send HCI remote name req: %s (%d)",
+                                               strerror(errno), errno);
+
+               /* if failed, request the next element */
+               /* remove the element from the list */
+               adapter_remove_found_device(adapter, &dev->bdaddr);
+
+               /* get the next element */
+               dev = adapter_search_found_devices(adapter, &match);
+       } while (dev);
+
+       return err;
+}
+
+static const char *mode2str(uint8_t mode)
+{
+       switch(mode) {
+       case MODE_OFF:
+               return "off";
+       case MODE_CONNECTABLE:
+               return "connectable";
+       case MODE_DISCOVERABLE:
+               return "discoverable";
+       default:
+               return "unknown";
+       }
+}
+
+static uint8_t get_mode(const bdaddr_t *bdaddr, const char *mode)
+{
+       if (strcasecmp("off", mode) == 0)
+               return MODE_OFF;
+       else if (strcasecmp("connectable", mode) == 0)
+               return MODE_CONNECTABLE;
+       else if (strcasecmp("discoverable", mode) == 0)
+               return MODE_DISCOVERABLE;
+       else if (strcasecmp("on", mode) == 0) {
+               char onmode[14], srcaddr[18];
+
+               ba2str(bdaddr, srcaddr);
+               if (read_on_mode(srcaddr, onmode, sizeof(onmode)) < 0)
+                       return MODE_CONNECTABLE;
+
+               return get_mode(bdaddr, onmode);
+       } else
+               return MODE_UNKNOWN;
+}
+
+static void adapter_set_limited_discoverable(struct btd_adapter *adapter,
+                                                       gboolean limited)
+{
+       DBG("%s", limited ? "TRUE" : "FALSE");
+
+       adapter_ops->set_limited_discoverable(adapter->dev_id, limited);
+}
+
+static void adapter_remove_discov_timeout(struct btd_adapter *adapter)
+{
+       if (!adapter)
+               return;
+
+       if (adapter->discov_timeout_id == 0)
+               return;
+
+       g_source_remove(adapter->discov_timeout_id);
+       adapter->discov_timeout_id = 0;
+}
+
+static gboolean discov_timeout_handler(gpointer user_data)
+{
+       struct btd_adapter *adapter = user_data;
+
+       adapter->discov_timeout_id = 0;
+
+       adapter_ops->set_discoverable(adapter->dev_id, FALSE);
+
+       return FALSE;
+}
+
+static void adapter_set_discov_timeout(struct btd_adapter *adapter,
+                                       guint interval)
+{
+       if (adapter->discov_timeout_id) {
+               g_source_remove(adapter->discov_timeout_id);
+               adapter->discov_timeout_id = 0;
+       }
+
+       if (interval == 0) {
+               adapter_set_limited_discoverable(adapter, FALSE);
+               return;
+       }
+
+       /* Set limited discoverable if pairable and interval between 0 to 60
+          sec */
+       if (adapter->pairable && interval <= 60)
+               adapter_set_limited_discoverable(adapter, TRUE);
+       else
+               adapter_set_limited_discoverable(adapter, FALSE);
+
+       adapter->discov_timeout_id = g_timeout_add_seconds(interval,
+                                                       discov_timeout_handler,
+                                                       adapter);
+}
+
+static struct session_req *session_ref(struct session_req *req)
+{
+       req->refcount++;
+
+       DBG("%p: ref=%d", req, req->refcount);
+
+       return req;
+}
+
+static struct session_req *create_session(struct btd_adapter *adapter,
+                                       DBusConnection *conn, DBusMessage *msg,
+                                       uint8_t mode, GDBusWatchFunction cb)
+{
+       const char *sender = dbus_message_get_sender(msg);
+       struct session_req *req;
+
+       req = g_new0(struct session_req, 1);
+       req->adapter = adapter;
+       req->conn = dbus_connection_ref(conn);
+       req->msg = dbus_message_ref(msg);
+       req->mode = mode;
+
+       if (cb == NULL)
+               return session_ref(req);
+
+       req->owner = g_strdup(sender);
+       req->id = g_dbus_add_disconnect_watch(conn, sender, cb, req, NULL);
+
+       info("%s session %p with %s activated",
+               req->mode ? "Mode" : "Discovery", req, sender);
+
+       return session_ref(req);
+}
+
+static int adapter_set_mode(struct btd_adapter *adapter, uint8_t mode)
+{
+       int err;
+
+       if (mode == MODE_CONNECTABLE)
+               err = adapter_ops->set_discoverable(adapter->dev_id, FALSE);
+       else
+               err = adapter_ops->set_discoverable(adapter->dev_id, TRUE);
+
+       if (err < 0)
+               return err;
+
+       if (mode == MODE_CONNECTABLE)
+               return 0;
+
+       adapter_remove_discov_timeout(adapter);
+
+       if (adapter->discov_timeout)
+               adapter_set_discov_timeout(adapter, adapter->discov_timeout);
+
+       return 0;
+}
+
+static struct session_req *find_session_by_msg(GSList *list, const DBusMessage *msg)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct session_req *req = l->data;
+
+               if (req->msg == msg)
+                       return req;
+       }
+
+       return NULL;
+}
+
+static int set_mode(struct btd_adapter *adapter, uint8_t new_mode,
+                       DBusMessage *msg)
+{
+       int err;
+       const char *modestr;
+       gboolean discoverable;
+
+       if (adapter->pending_mode != NULL)
+               return -EALREADY;
+
+       discoverable = new_mode == MODE_DISCOVERABLE;
+
+       if (!adapter->up && new_mode != MODE_OFF) {
+               err = adapter_ops->set_powered(adapter->dev_id, TRUE);
+               if (err < 0)
+                       return err;
+
+               goto done;
+       }
+
+       if (adapter->up && new_mode == MODE_OFF) {
+               err = adapter_ops->set_powered(adapter->dev_id, FALSE);
+               if (err < 0)
+                       return err;
+
+               adapter->off_requested = TRUE;
+
+               goto done;
+       }
+
+       if (new_mode == adapter->mode)
+               return 0;
+
+       err = adapter_set_mode(adapter, new_mode);
+
+       if (err < 0)
+               return err;
+
+done:
+       modestr = mode2str(new_mode);
+       write_device_mode(&adapter->bdaddr, modestr);
+
+       DBG("%s", modestr);
+
+       if (msg != NULL) {
+               struct session_req *req;
+
+               req = find_session_by_msg(adapter->mode_sessions, msg);
+               if (req) {
+                       adapter->pending_mode = req;
+                       session_ref(req);
+               } else
+                       /* Wait for mode change to reply */
+                       adapter->pending_mode = create_session(adapter,
+                                       connection, msg, new_mode, NULL);
+       } else
+               /* Nothing to reply just write the new mode */
+               adapter->mode = new_mode;
+
+       return 0;
+}
+
+static DBusMessage *set_discoverable(DBusConnection *conn, DBusMessage *msg,
+                               gboolean discoverable, void *data)
+{
+       struct btd_adapter *adapter = data;
+       uint8_t mode;
+       int err;
+
+       mode = discoverable ? MODE_DISCOVERABLE : MODE_CONNECTABLE;
+
+       if (mode == adapter->mode) {
+               adapter->global_mode = mode;
+               return dbus_message_new_method_return(msg);
+       }
+
+       err = set_mode(adapter, mode, msg);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       return NULL;
+}
+
+static DBusMessage *set_powered(DBusConnection *conn, DBusMessage *msg,
+                               gboolean powered, void *data)
+{
+       struct btd_adapter *adapter = data;
+       uint8_t mode;
+       int err;
+
+#ifdef __TIZEN_PATCH__
+       if (powered)
+       {
+               mode = adapter->mode ? adapter->mode : get_mode(&adapter->bdaddr, "on");
+
+       }
+       else
+               mode = MODE_OFF;
+#else
+       mode = powered ? get_mode(&adapter->bdaddr, "on") : MODE_OFF;
+#endif
+
+       if (mode == adapter->mode) {
+               adapter->global_mode = mode;
+               return dbus_message_new_method_return(msg);
+       }
+
+       err = set_mode(adapter, mode, msg);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       return NULL;
+}
+
+void btd_adapter_pairable_changed(struct btd_adapter *adapter,
+                                                       gboolean pairable)
+{
+       adapter->pairable = pairable;
+
+       write_device_pairable(&adapter->bdaddr, pairable);
+
+       emit_property_changed(connection, adapter->path,
+                               ADAPTER_INTERFACE, "Pairable",
+                               DBUS_TYPE_BOOLEAN, &pairable);
+
+       if (pairable && adapter->pairable_timeout)
+               adapter_set_pairable_timeout(adapter,
+                                               adapter->pairable_timeout);
+}
+
+static DBusMessage *set_pairable(DBusConnection *conn, DBusMessage *msg,
+                               gboolean pairable, void *data)
+{
+       struct btd_adapter *adapter = data;
+       int err;
+
+       if (adapter->scan_mode == SCAN_DISABLED)
+               return btd_error_not_ready(msg);
+
+       if (pairable == adapter->pairable)
+               goto done;
+
+       if (!(adapter->scan_mode & SCAN_INQUIRY))
+               goto store;
+
+#ifndef __TIZEN_PATCH__
+       err = set_mode(adapter, MODE_DISCOVERABLE, NULL);
+       if (err < 0 && msg)
+               return btd_error_failed(msg, strerror(-err));
+#endif
+
+store:
+       adapter_ops->set_pairable(adapter->dev_id, pairable);
+
+done:
+       return msg ? dbus_message_new_method_return(msg) : NULL;
+}
+
+static gboolean pairable_timeout_handler(void *data)
+{
+       set_pairable(NULL, NULL, FALSE, data);
+
+       return FALSE;
+}
+
+static void adapter_set_pairable_timeout(struct btd_adapter *adapter,
+                                       guint interval)
+{
+       if (adapter->pairable_timeout_id) {
+               g_source_remove(adapter->pairable_timeout_id);
+               adapter->pairable_timeout_id = 0;
+       }
+
+       if (interval == 0)
+               return;
+
+       adapter->pairable_timeout_id = g_timeout_add_seconds(interval,
+                                               pairable_timeout_handler,
+                                               adapter);
+}
+
+static struct session_req *find_session(GSList *list, const char *sender)
+{
+       GSList *l;
+
+       for (l = list; l; l = l->next) {
+               struct session_req *req = l->data;
+
+               if (g_str_equal(req->owner, sender))
+                       return req;
+       }
+
+       return NULL;
+}
+
+static uint8_t get_needed_mode(struct btd_adapter *adapter, uint8_t mode)
+{
+       GSList *l;
+
+       if (adapter->global_mode > mode)
+               mode = adapter->global_mode;
+
+       for (l = adapter->mode_sessions; l; l = l->next) {
+               struct session_req *req = l->data;
+
+               if (req->mode > mode)
+                       mode = req->mode;
+       }
+
+       return mode;
+}
+
+static GSList *remove_bredr(GSList *all)
+{
+       GSList *l, *le;
+
+       for (l = all, le = NULL; l; l = l->next) {
+               struct remote_dev_info *dev = l->data;
+               if (dev->le == FALSE) {
+                       dev_info_free(dev);
+                       continue;
+               }
+
+               le = g_slist_append(le, dev);
+       }
+
+       g_slist_free(all);
+
+       return le;
+}
+
+static void stop_discovery(struct btd_adapter *adapter, gboolean suspend)
+{
+       pending_remote_name_cancel(adapter);
+
+       if (suspend == FALSE)
+               adapter->found_devices = remove_bredr(adapter->found_devices);
+
+       if (adapter->oor_devices) {
+               g_slist_free(adapter->oor_devices);
+               adapter->oor_devices = NULL;
+       }
+
+       /* Reset if suspended, otherwise remove timer (software scheduler)
+          or request inquiry to stop */
+       if (adapter->state & STATE_SUSPENDED) {
+               adapter->state &= ~STATE_SUSPENDED;
+               return;
+       }
+
+       if (adapter->scheduler_id) {
+               g_source_remove(adapter->scheduler_id);
+               adapter->scheduler_id = 0;
+               return;
+       }
+
+       if (adapter->state & STATE_LE_SCAN)
+               adapter_ops->stop_scanning(adapter->dev_id);
+       else
+               adapter_ops->stop_inquiry(adapter->dev_id);
+}
+
+static void session_remove(struct session_req *req)
+{
+       struct btd_adapter *adapter = req->adapter;
+
+       /* Ignore set_mode session */
+       if (req->owner == NULL)
+               return;
+
+       DBG("%s session %p with %s deactivated",
+               req->mode ? "Mode" : "Discovery", req, req->owner);
+
+       if (req->mode) {
+               uint8_t mode;
+
+               adapter->mode_sessions = g_slist_remove(adapter->mode_sessions,
+                                                       req);
+
+               mode = get_needed_mode(adapter, adapter->global_mode);
+
+               if (mode == adapter->mode)
+                       return;
+
+               DBG("Switching to '%s' mode", mode2str(mode));
+
+               set_mode(adapter, mode, NULL);
+       } else {
+               adapter->disc_sessions = g_slist_remove(adapter->disc_sessions,
+                                                       req);
+
+               if (adapter->disc_sessions)
+                       return;
+
+               DBG("Stopping discovery");
+
+               stop_discovery(adapter, FALSE);
+       }
+}
+
+static void session_free(struct session_req *req)
+{
+       if (req->id)
+               g_dbus_remove_watch(req->conn, req->id);
+
+       session_remove(req);
+
+       if (req->msg) {
+               dbus_message_unref(req->msg);
+               if (!req->got_reply && req->mode && req->adapter->agent)
+                       agent_cancel(req->adapter->agent);
+       }
+
+       if (req->conn)
+               dbus_connection_unref(req->conn);
+       g_free(req->owner);
+       g_free(req);
+}
+
+static void session_owner_exit(DBusConnection *conn, void *user_data)
+{
+       struct session_req *req = user_data;
+
+       req->id = 0;
+
+       session_free(req);
+}
+
+static void session_unref(struct session_req *req)
+{
+       req->refcount--;
+
+       DBG("%p: ref=%d", req, req->refcount);
+
+       if (req->refcount)
+               return;
+
+       session_free(req);
+}
+
+static void confirm_mode_cb(struct agent *agent, DBusError *derr, void *data)
+{
+       struct session_req *req = data;
+       int err;
+       DBusMessage *reply;
+
+       req->got_reply = TRUE;
+
+       if (derr && dbus_error_is_set(derr)) {
+               reply = dbus_message_new_error(req->msg, derr->name,
+                                               derr->message);
+               g_dbus_send_message(req->conn, reply);
+               session_unref(req);
+               return;
+       }
+
+       err = set_mode(req->adapter, req->mode, req->msg);
+       if (err < 0)
+               reply = btd_error_failed(req->msg, strerror(-err));
+       else if (!req->adapter->pending_mode)
+               reply = dbus_message_new_method_return(req->msg);
+       else
+               reply = NULL;
+
+       if (reply) {
+               /*
+                * Send reply immediately only if there was an error changing
+                * mode, or change is not needed. Otherwise, reply is sent in
+                * set_mode_complete.
+                */
+               g_dbus_send_message(req->conn, reply);
+
+               dbus_message_unref(req->msg);
+               req->msg = NULL;
+       }
+
+       if (!find_session(req->adapter->mode_sessions, req->owner))
+               session_unref(req);
+}
+
+static DBusMessage *set_discoverable_timeout(DBusConnection *conn,
+                                                       DBusMessage *msg,
+                                                       uint32_t timeout,
+                                                       void *data)
+{
+       struct btd_adapter *adapter = data;
+       const char *path;
+
+       if (adapter->discov_timeout == timeout && timeout == 0)
+               return dbus_message_new_method_return(msg);
+
+       if (adapter->scan_mode & SCAN_INQUIRY)
+               adapter_set_discov_timeout(adapter, timeout);
+
+       adapter->discov_timeout = timeout;
+
+       write_discoverable_timeout(&adapter->bdaddr, timeout);
+
+       path = dbus_message_get_path(msg);
+
+       emit_property_changed(conn, path,
+                               ADAPTER_INTERFACE, "DiscoverableTimeout",
+                               DBUS_TYPE_UINT32, &timeout);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_pairable_timeout(DBusConnection *conn,
+                                               DBusMessage *msg,
+                                               uint32_t timeout,
+                                               void *data)
+{
+       struct btd_adapter *adapter = data;
+       const char *path;
+
+       if (adapter->pairable_timeout == timeout && timeout == 0)
+               return dbus_message_new_method_return(msg);
+
+       if (adapter->pairable)
+               adapter_set_pairable_timeout(adapter, timeout);
+
+       adapter->pairable_timeout = timeout;
+
+       write_pairable_timeout(&adapter->bdaddr, timeout);
+
+       path = dbus_message_get_path(msg);
+
+       emit_property_changed(conn, path,
+                               ADAPTER_INTERFACE, "PairableTimeout",
+                               DBUS_TYPE_UINT32, &timeout);
+
+       return dbus_message_new_method_return(msg);
+}
+
+void btd_adapter_class_changed(struct btd_adapter *adapter, uint32_t new_class)
+{
+       uint8_t class[3];
+
+       class[2] = (new_class >> 16) & 0xff;
+       class[1] = (new_class >> 8) & 0xff;
+       class[0] = new_class & 0xff;
+
+       write_local_class(&adapter->bdaddr, class);
+
+       adapter->dev_class = new_class;
+
+       if (main_opts.attrib_server) {
+               /* Removes service class */
+               class[1] = class[1] & 0x1f;
+               attrib_gap_set(GATT_CHARAC_APPEARANCE, class, 2);
+       }
+
+       emit_property_changed(connection, adapter->path,
+                               ADAPTER_INTERFACE, "Class",
+                               DBUS_TYPE_UINT32, &new_class);
+}
+
+void adapter_update_local_name(struct btd_adapter *adapter, const char *name)
+{
+       struct hci_dev *dev = &adapter->dev;
+
+       if (strncmp(name, dev->name, MAX_NAME_LENGTH) == 0)
+               return;
+
+       strncpy(dev->name, name, MAX_NAME_LENGTH);
+
+       if (main_opts.attrib_server)
+               attrib_gap_set(GATT_CHARAC_DEVICE_NAME,
+                       (const uint8_t *) dev->name, strlen(dev->name));
+
+       if (!adapter->name_stored) {
+               char *name_ptr = dev->name;
+
+               write_local_name(&adapter->bdaddr, dev->name);
+
+               if (connection)
+                       emit_property_changed(connection, adapter->path,
+                                               ADAPTER_INTERFACE, "Name",
+                                               DBUS_TYPE_STRING, &name_ptr);
+       }
+
+       adapter->name_stored = FALSE;
+}
+
+static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg,
+                                       const char *name, void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct hci_dev *dev = &adapter->dev;
+       char *name_ptr = dev->name;
+
+       if (!g_utf8_validate(name, -1, NULL)) {
+               error("Name change failed: supplied name isn't valid UTF-8");
+               return btd_error_invalid_args(msg);
+       }
+
+       if (strncmp(name, dev->name, MAX_NAME_LENGTH) == 0)
+               goto done;
+
+       strncpy(dev->name, name, MAX_NAME_LENGTH);
+       write_local_name(&adapter->bdaddr, name);
+       emit_property_changed(connection, adapter->path,
+                                       ADAPTER_INTERFACE, "Name",
+                                       DBUS_TYPE_STRING, &name_ptr);
+
+       if (adapter->up) {
+               int err = adapter_ops->set_name(adapter->dev_id, name);
+               if (err < 0)
+                       return btd_error_failed(msg, strerror(-err));
+
+               adapter->name_stored = TRUE;
+       }
+
+done:
+       return dbus_message_new_method_return(msg);
+}
+
+struct btd_device *adapter_find_device(struct btd_adapter *adapter,
+                                                       const char *dest)
+{
+       struct btd_device *device;
+       GSList *l;
+
+       if (!adapter)
+               return NULL;
+
+       l = g_slist_find_custom(adapter->devices, dest,
+                                       (GCompareFunc) device_address_cmp);
+       if (!l)
+               return NULL;
+
+       device = l->data;
+
+       return device;
+}
+
+static void adapter_update_devices(struct btd_adapter *adapter)
+{
+       char **devices;
+       int i;
+       GSList *l;
+
+       /* Devices */
+       devices = g_new0(char *, g_slist_length(adapter->devices) + 1);
+       for (i = 0, l = adapter->devices; l; l = l->next, i++) {
+               struct btd_device *dev = l->data;
+               devices[i] = (char *) device_get_path(dev);
+       }
+
+       emit_array_property_changed(connection, adapter->path,
+                                       ADAPTER_INTERFACE, "Devices",
+                                       DBUS_TYPE_OBJECT_PATH, &devices, i);
+       g_free(devices);
+}
+
+static void adapter_emit_uuids_updated(struct btd_adapter *adapter)
+{
+       char **uuids;
+       int i;
+       sdp_list_t *list;
+
+       uuids = g_new0(char *, sdp_list_len(adapter->services) + 1);
+
+       for (i = 0, list = adapter->services; list; list = list->next) {
+               char *uuid;
+               sdp_record_t *rec = list->data;
+
+               uuid = bt_uuid2string(&rec->svclass);
+               if (uuid)
+                       uuids[i++] = uuid;
+       }
+
+       emit_array_property_changed(connection, adapter->path,
+                       ADAPTER_INTERFACE, "UUIDs", DBUS_TYPE_STRING, &uuids, i);
+
+       g_strfreev(uuids);
+}
+
+static uint8_t get_uuid_mask(uuid_t *uuid)
+{
+       if (uuid->type != SDP_UUID16)
+               return 0;
+
+       switch (uuid->value.uuid16) {
+       case DIALUP_NET_SVCLASS_ID:
+       case CIP_SVCLASS_ID:
+               return 0x42;    /* Telephony & Networking */
+       case IRMC_SYNC_SVCLASS_ID:
+       case OBEX_OBJPUSH_SVCLASS_ID:
+       case OBEX_FILETRANS_SVCLASS_ID:
+       case IRMC_SYNC_CMD_SVCLASS_ID:
+       case PBAP_PSE_SVCLASS_ID:
+               return 0x10;    /* Object Transfer */
+       case HEADSET_SVCLASS_ID:
+       case HANDSFREE_SVCLASS_ID:
+               return 0x20;    /* Audio */
+       case CORDLESS_TELEPHONY_SVCLASS_ID:
+       case INTERCOM_SVCLASS_ID:
+       case FAX_SVCLASS_ID:
+       case SAP_SVCLASS_ID:
+       /*
+        * Setting the telephony bit for the handsfree audio gateway
+        * role is not required by the HFP specification, but the
+        * Nokia 616 carkit is just plain broken! It will refuse
+        * pairing without this bit set.
+        */
+       case HANDSFREE_AGW_SVCLASS_ID:
+               return 0x40;    /* Telephony */
+       case AUDIO_SOURCE_SVCLASS_ID:
+       case VIDEO_SOURCE_SVCLASS_ID:
+               return 0x08;    /* Capturing */
+       case AUDIO_SINK_SVCLASS_ID:
+       case VIDEO_SINK_SVCLASS_ID:
+               return 0x04;    /* Rendering */
+       case PANU_SVCLASS_ID:
+       case NAP_SVCLASS_ID:
+       case GN_SVCLASS_ID:
+               return 0x02;    /* Networking */
+       default:
+               return 0;
+       }
+}
+
+static int uuid_cmp(const void *a, const void *b)
+{
+       const sdp_record_t *rec = a;
+       const uuid_t *uuid = b;
+
+       return sdp_uuid_cmp(&rec->svclass, uuid);
+}
+
+void adapter_service_insert(struct btd_adapter *adapter, void *r)
+{
+       sdp_record_t *rec = r;
+       gboolean new_uuid;
+
+       if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL)
+               new_uuid = TRUE;
+       else
+               new_uuid = FALSE;
+
+       adapter->services = sdp_list_insert_sorted(adapter->services, rec,
+                                                               record_sort);
+
+       if (new_uuid) {
+               uint8_t svc_hint = get_uuid_mask(&rec->svclass);
+               adapter_ops->add_uuid(adapter->dev_id, &rec->svclass, svc_hint);
+       }
+
+       adapter_emit_uuids_updated(adapter);
+}
+
+void adapter_service_remove(struct btd_adapter *adapter, void *r)
+{
+       sdp_record_t *rec = r;
+
+       adapter->services = sdp_list_remove(adapter->services, rec);
+
+       if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL)
+               adapter_ops->remove_uuid(adapter->dev_id, &rec->svclass);
+
+       adapter_emit_uuids_updated(adapter);
+}
+#ifdef __TIZEN_PATCH__
+sdp_list_t *adapter_get_services(struct btd_adapter *adapter)
+{
+       return adapter->services;
+}
+
+#endif
+static struct btd_device *adapter_create_device(DBusConnection *conn,
+                                               struct btd_adapter *adapter,
+                                               const char *address,
+                                               device_type_t type)
+{
+       struct btd_device *device;
+       const char *path;
+
+       DBG("%s", address);
+
+       device = device_create(conn, adapter, address, type);
+       if (!device)
+               return NULL;
+
+       device_set_temporary(device, TRUE);
+
+       adapter->devices = g_slist_append(adapter->devices, device);
+
+       path = device_get_path(device);
+       g_dbus_emit_signal(conn, adapter->path,
+                       ADAPTER_INTERFACE, "DeviceCreated",
+                       DBUS_TYPE_OBJECT_PATH, &path,
+                       DBUS_TYPE_INVALID);
+
+       adapter_update_devices(adapter);
+
+       return device;
+}
+
+void adapter_remove_device(DBusConnection *conn, struct btd_adapter *adapter,
+                                               struct btd_device *device,
+                                               gboolean remove_storage)
+{
+       const gchar *dev_path = device_get_path(device);
+       struct agent *agent;
+
+       adapter->devices = g_slist_remove(adapter->devices, device);
+       adapter->connections = g_slist_remove(adapter->connections, device);
+
+       adapter_update_devices(adapter);
+
+       g_dbus_emit_signal(conn, adapter->path,
+                       ADAPTER_INTERFACE, "DeviceRemoved",
+                       DBUS_TYPE_OBJECT_PATH, &dev_path,
+                       DBUS_TYPE_INVALID);
+
+       agent = device_get_agent(device);
+
+       if (agent && device_is_authorizing(device))
+               agent_cancel(agent);
+
+       device_remove(device, remove_storage);
+}
+
+struct btd_device *adapter_get_device(DBusConnection *conn,
+                                               struct btd_adapter *adapter,
+                                               const gchar *address)
+{
+       struct btd_device *device;
+
+       DBG("%s", address);
+
+       if (!adapter)
+               return NULL;
+
+       device = adapter_find_device(adapter, address);
+       if (device)
+               return device;
+
+       return adapter_create_device(conn, adapter, address,
+                                               DEVICE_TYPE_BREDR);
+}
+
+static gboolean stop_scanning(gpointer user_data)
+{
+       struct btd_adapter *adapter = user_data;
+
+       adapter_ops->stop_scanning(adapter->dev_id);
+
+       return FALSE;
+}
+
+static int start_discovery(struct btd_adapter *adapter)
+{
+       int err, type;
+
+       /* Do not start if suspended */
+       if (adapter->state & STATE_SUSPENDED)
+               return 0;
+
+       /* Postpone discovery if still resolving names */
+       if (adapter->state & STATE_RESOLVNAME)
+               return 1;
+
+       pending_remote_name_cancel(adapter);
+
+       type = adapter_get_discover_type(adapter) & ~DISC_RESOLVNAME;
+
+       switch (type) {
+       case DISC_STDINQ:
+       case DISC_INTERLEAVE:
+               err = adapter_ops->start_inquiry(adapter->dev_id,
+                                                       0x08, FALSE);
+               break;
+       case DISC_PINQ:
+               err = adapter_ops->start_inquiry(adapter->dev_id,
+                                                       0x08, TRUE);
+               break;
+       case DISC_LE:
+               err = adapter_ops->start_scanning(adapter->dev_id);
+               break;
+       default:
+               err = -EINVAL;
+       }
+
+       return err;
+}
+
+static DBusMessage *adapter_start_discovery(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct session_req *req;
+       struct btd_adapter *adapter = data;
+       const char *sender = dbus_message_get_sender(msg);
+       int err;
+       info("adapter_start_discovery");
+       if (!adapter->up)
+               return btd_error_not_ready(msg);
+
+       req = find_session(adapter->disc_sessions, sender);
+       if (req) {
+               session_ref(req);
+               return dbus_message_new_method_return(msg);
+       }
+
+       if (adapter->disc_sessions)
+               goto done;
+
+       g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL);
+       g_slist_free(adapter->found_devices);
+       adapter->found_devices = NULL;
+
+       g_slist_free(adapter->oor_devices);
+       adapter->oor_devices = NULL;
+
+       err = start_discovery(adapter);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+done:
+       req = create_session(adapter, conn, msg, 0,
+                               session_owner_exit);
+
+       adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *adapter_stop_discovery(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct session_req *req;
+       const char *sender = dbus_message_get_sender(msg);
+
+       if (!adapter->up)
+               return btd_error_not_ready(msg);
+
+       req = find_session(adapter->disc_sessions, sender);
+       if (!req)
+               return btd_error_failed(msg, "Invalid discovery session");
+
+       session_unref(req);
+       info("Stopping discovery");
+       return dbus_message_new_method_return(msg);
+}
+
+struct remote_device_list_t {
+       GSList *list;
+       time_t time;
+};
+
+static DBusMessage *get_properties(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       const char *property;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       DBusMessageIter dict;
+       char str[MAX_NAME_LENGTH + 1], srcaddr[18];
+       gboolean value;
+       char **devices, **uuids;
+       int i;
+       GSList *l;
+       sdp_list_t *list;
+               info("Get properties 1\n");
+       ba2str(&adapter->bdaddr, srcaddr);
+
+       if (check_address(srcaddr) < 0)
+               return btd_error_invalid_args(msg);
+
+       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);
+
+       /* Address */
+       property = srcaddr;
+       dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &property);
+
+       /* Name */
+       memset(str, 0, sizeof(str));
+       strncpy(str, (char *) adapter->dev.name, MAX_NAME_LENGTH);
+       property = str;
+
+       dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &property);
+
+       /* Class */
+       dict_append_entry(&dict, "Class",
+                               DBUS_TYPE_UINT32, &adapter->dev_class);
+
+       /* Powered */
+       value = (adapter->up && !adapter->off_requested) ? TRUE : FALSE;
+       dict_append_entry(&dict, "Powered", DBUS_TYPE_BOOLEAN, &value);
+
+       /* Discoverable */
+       value = adapter->scan_mode & SCAN_INQUIRY ? TRUE : FALSE;
+       dict_append_entry(&dict, "Discoverable", DBUS_TYPE_BOOLEAN, &value);
+#ifdef __TIZEN_PATCH__
+       // Adding limited property for setting limited discoverable mode
+       /* Limited */
+       dict_append_entry(&dict, "Limited", DBUS_TYPE_BOOLEAN,
+                               &adapter->limited);
+#endif
+
+       /* Pairable */
+       dict_append_entry(&dict, "Pairable", DBUS_TYPE_BOOLEAN,
+                               &adapter->pairable);
+
+       /* DiscoverableTimeout */
+       dict_append_entry(&dict, "DiscoverableTimeout",
+                               DBUS_TYPE_UINT32, &adapter->discov_timeout);
+
+       /* PairableTimeout */
+       dict_append_entry(&dict, "PairableTimeout",
+                               DBUS_TYPE_UINT32, &adapter->pairable_timeout);
+
+
+       if (adapter->state & (STATE_PINQ | STATE_STDINQ | STATE_LE_SCAN))
+               value = TRUE;
+       else
+               value = FALSE;
+
+       /* Discovering */
+       dict_append_entry(&dict, "Discovering", DBUS_TYPE_BOOLEAN, &value);
+
+       /* Devices */
+       devices = g_new0(char *, g_slist_length(adapter->devices) + 1);
+       for (i = 0, l = adapter->devices; l; l = l->next, i++) {
+               struct btd_device *dev = l->data;
+               devices[i] = (char *) device_get_path(dev);
+       }
+       dict_append_array(&dict, "Devices", DBUS_TYPE_OBJECT_PATH,
+                                                               &devices, i);
+       g_free(devices);
+
+       /* UUIDs */
+       uuids = g_new0(char *, sdp_list_len(adapter->services) + 1);
+
+       for (i = 0, list = adapter->services; list; list = list->next) {
+               sdp_record_t *rec = list->data;
+               char *uuid;
+
+               uuid = bt_uuid2string(&rec->svclass);
+               if (uuid)
+                       uuids[i++] = uuid;
+       }
+
+       dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, &uuids, i);
+
+       g_strfreev(uuids);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static DBusMessage *set_property(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       info(" set_property 1 \n");
+       struct btd_adapter *adapter = data;
+       DBusMessageIter iter;
+       DBusMessageIter sub;
+       const char *property;
+       char srcaddr[18];
+
+       ba2str(&adapter->bdaddr, srcaddr);
+
+       if (!dbus_message_iter_init(msg, &iter))
+               return btd_error_invalid_args(msg);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+               return btd_error_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 btd_error_invalid_args(msg);
+       dbus_message_iter_recurse(&iter, &sub);
+
+       if (g_str_equal("Name", property)) {
+               const char *name;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
+                       return btd_error_invalid_args(msg);
+               dbus_message_iter_get_basic(&sub, &name);
+
+               return set_name(conn, msg, name, data);
+       } else if (g_str_equal("Powered", property)) {
+               gboolean powered;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+                       return btd_error_invalid_args(msg);
+
+               dbus_message_iter_get_basic(&sub, &powered);
+
+               return set_powered(conn, msg, powered, data);
+       } else if (g_str_equal("Discoverable", property)) {
+               gboolean discoverable;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+                       return btd_error_invalid_args(msg);
+
+               dbus_message_iter_get_basic(&sub, &discoverable);
+
+               return set_discoverable(conn, msg, discoverable, data);
+#ifdef __TIZEN_PATCH__
+       // Adding limited property for setting limited discoverable mode
+       } else if (g_str_equal("Limited", property)) {
+               gboolean limited;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+                       return btd_error_invalid_args(msg);
+
+               dbus_message_iter_get_basic(&sub, &limited);
+
+///            return set_limited(conn, msg, limited, data);
+       return btd_error_invalid_args(msg);
+#endif
+       } else if (g_str_equal("DiscoverableTimeout", property)) {
+               uint32_t timeout;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)
+                       return btd_error_invalid_args(msg);
+
+               dbus_message_iter_get_basic(&sub, &timeout);
+
+               return set_discoverable_timeout(conn, msg, timeout, data);
+       } else if (g_str_equal("Pairable", property)) {
+               gboolean pairable;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+                       return btd_error_invalid_args(msg);
+
+               dbus_message_iter_get_basic(&sub, &pairable);
+
+               return set_pairable(conn, msg, pairable, data);
+       } else if (g_str_equal("PairableTimeout", property)) {
+               uint32_t timeout;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)
+                       return btd_error_invalid_args(msg);
+
+               dbus_message_iter_get_basic(&sub, &timeout);
+
+               return set_pairable_timeout(conn, msg, timeout, data);
+       }
+
+       return btd_error_invalid_args(msg);
+}
+
+static DBusMessage *request_session(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct session_req *req;
+       const char *sender = dbus_message_get_sender(msg);
+       uint8_t new_mode;
+       int err;
+
+       if (!adapter->agent)
+               return btd_error_agent_not_available(msg);
+
+       if (!adapter->mode_sessions)
+               adapter->global_mode = adapter->mode;
+
+       new_mode = get_mode(&adapter->bdaddr, "on");
+
+       req = find_session(adapter->mode_sessions, sender);
+       if (req) {
+               session_ref(req);
+               return dbus_message_new_method_return(msg);
+       } else {
+               req = create_session(adapter, conn, msg, new_mode,
+                                       session_owner_exit);
+               adapter->mode_sessions = g_slist_append(adapter->mode_sessions,
+                                                       req);
+       }
+
+       /* No need to change mode */
+       if (adapter->mode >= new_mode)
+               return dbus_message_new_method_return(msg);
+
+       err = agent_confirm_mode_change(adapter->agent, mode2str(new_mode),
+                                       confirm_mode_cb, req, NULL);
+       if (err < 0) {
+               session_unref(req);
+               return btd_error_failed(msg, strerror(-err));
+       }
+
+       return NULL;
+}
+
+static DBusMessage *release_session(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct session_req *req;
+       const char *sender = dbus_message_get_sender(msg);
+
+       req = find_session(adapter->mode_sessions, sender);
+       if (!req)
+               return btd_error_failed(msg, "Invalid Session");
+
+       session_unref(req);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *list_devices(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       DBusMessage *reply;
+       GSList *l;
+       DBusMessageIter iter;
+       DBusMessageIter array_iter;
+       const gchar *dev_path;
+
+       if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING))
+               return btd_error_invalid_args(msg);
+
+       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_TYPE_OBJECT_PATH_AS_STRING, &array_iter);
+
+       for (l = adapter->devices; l; l = l->next) {
+               struct btd_device *device = l->data;
+
+               dev_path = device_get_path(device);
+
+               dbus_message_iter_append_basic(&array_iter,
+                               DBUS_TYPE_OBJECT_PATH, &dev_path);
+       }
+
+       dbus_message_iter_close_container(&iter, &array_iter);
+
+       return reply;
+}
+
+static DBusMessage *cancel_device_creation(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       const gchar *address, *sender = dbus_message_get_sender(msg);
+       struct btd_device *device;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return btd_error_invalid_args(msg);
+
+       if (check_address(address) < 0)
+               return btd_error_invalid_args(msg);
+
+       device = adapter_find_device(adapter, address);
+       if (!device || !device_is_creating(device, NULL))
+               return btd_error_does_not_exist(msg);
+
+       if (!device_is_creating(device, sender))
+               return btd_error_not_authorized(msg);
+
+       device_set_temporary(device, TRUE);
+
+       if (device_is_connected(device)) {
+               device_request_disconnect(device, msg);
+               return NULL;
+       }
+
+       adapter_remove_device(conn, adapter, device, TRUE);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static device_type_t flags2type(uint8_t flags)
+{
+       /* Inferring the remote type based on the EIR Flags field */
+
+       /* For LE only and dual mode the following flags must be zero */
+       if (flags & (EIR_SIM_CONTROLLER | EIR_SIM_HOST))
+               return DEVICE_TYPE_UNKNOWN;
+
+       /* Limited or General discoverable mode bit must be enabled */
+       if (!(flags & (EIR_LIM_DISC | EIR_GEN_DISC)))
+               return DEVICE_TYPE_UNKNOWN;
+
+       if (flags & EIR_BREDR_UNSUP)
+               return DEVICE_TYPE_LE;
+       else
+               return DEVICE_TYPE_DUALMODE;
+}
+
+static gboolean event_is_connectable(uint8_t type)
+{
+       switch (type) {
+       case ADV_TYPE_IND:
+       case ADV_TYPE_DIRECT_IND:
+               return TRUE;
+       default:
+               return FALSE;
+       }
+}
+
+static struct btd_device *create_device_internal(DBusConnection *conn,
+                                               struct btd_adapter *adapter,
+                                               const gchar *address,
+                                               gboolean force, int *err)
+{
+       struct remote_dev_info *dev, match;
+       struct btd_device *device;
+       device_type_t type;
+
+       memset(&match, 0, sizeof(struct remote_dev_info));
+       str2ba(address, &match.bdaddr);
+       match.name_status = NAME_ANY;
+
+       dev = adapter_search_found_devices(adapter, &match);
+       if (dev && dev->flags)
+               type = flags2type(dev->flags);
+       else
+               type = DEVICE_TYPE_BREDR;
+
+       if (!force && type == DEVICE_TYPE_LE &&
+                                       !event_is_connectable(dev->evt_type)) {
+               if (err)
+                       *err = -ENOTCONN;
+
+               return NULL;
+       }
+
+       device = adapter_create_device(conn, adapter, address, type);
+       if (!device && err)
+               *err = -ENOMEM;
+
+       return device;
+}
+
+static DBusMessage *create_device(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct btd_device *device;
+       const gchar *address;
+       DBusMessage *reply;
+       int err;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return btd_error_invalid_args(msg);
+
+       if (check_address(address) < 0)
+               return btd_error_invalid_args(msg);
+
+       if (!adapter->up)
+               return btd_error_not_ready(msg);
+
+       if (adapter_find_device(adapter, address))
+               return btd_error_already_exists(msg);
+
+       DBG("%s", address);
+
+       device = create_device_internal(conn, adapter, address, TRUE, &err);
+       if (!device)
+               goto failed;
+
+       if (device_get_type(device) != DEVICE_TYPE_LE)
+               err = device_browse_sdp(device, conn, msg, NULL, FALSE);
+       else
+               err = device_browse_primary(device, conn, msg, FALSE);
+
+       if (err < 0) {
+               adapter_remove_device(conn, adapter, device, TRUE);
+               return btd_error_failed(msg, strerror(-err));
+       }
+
+       return NULL;
+
+failed:
+       if (err == -ENOTCONN) {
+               /* Device is not connectable */
+               const char *path = device_get_path(device);
+
+               reply = dbus_message_new_method_return(msg);
+
+               dbus_message_append_args(reply,
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID);
+       } else
+               reply = btd_error_failed(msg, strerror(-err));
+
+       return reply;
+}
+
+static uint8_t parse_io_capability(const char *capability)
+{
+       if (g_str_equal(capability, ""))
+               return IO_CAPABILITY_DISPLAYYESNO;
+       if (g_str_equal(capability, "DisplayOnly"))
+               return IO_CAPABILITY_DISPLAYONLY;
+       if (g_str_equal(capability, "DisplayYesNo"))
+               return IO_CAPABILITY_DISPLAYYESNO;
+       if (g_str_equal(capability, "KeyboardOnly"))
+               return IO_CAPABILITY_KEYBOARDONLY;
+       if (g_str_equal(capability, "NoInputNoOutput"))
+               return IO_CAPABILITY_NOINPUTNOOUTPUT;
+       return IO_CAPABILITY_INVALID;
+}
+#ifdef __TIZEN_PATCH__
+DBusMessage *adapter_encrypt_link(DBusConnection *conn,
+                                       DBusMessage *msg,
+                                       const char *address,
+                                       dbus_bool_t encrypt,
+                                       void* data)
+
+{
+       struct btd_adapter *adapter = data;
+       struct btd_device *device;
+       char filename[PATH_MAX + 1];
+       bdaddr_t bdaddr;
+       struct hci_conn_info_req *cr;
+       int opt, dd;
+       int dev_id = 0;
+
+       DBG("handle_authenticate_link_request1");
+        device = adapter_get_device(conn, adapter, address);
+
+       str2ba(address, &bdaddr);
+       /* check if there is a pending discover: requested by D-Bus/non clients */
+       if (0)//todo
+
+               return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", "Discover in progress");
+
+       pending_remote_name_cancel(adapter);
+
+       if (device_get_bonding(device))
+               return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", "Bonding in progress");
+
+
+       /*if (adapter_find_auth_request(adapter, &bdaddr))
+               return in_progress(msg, "Bonding in progress");*/
+
+       /* check if a link key already exists */
+       create_name(filename, PATH_MAX, STORAGEDIR,address,
+                       "linkkeys");
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               return g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".Failed",
+                               "Device open failed");
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               return NULL;
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               free(cr);
+               return g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".Failed",
+                               "Getting connection info failed");
+       }
+
+#if 0
+       if (hci_authenticate_link(dd, htobs(cr->conn_info->handle), 25000) < 0) {
+               free(cr);
+               return g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".Failed",
+                               "Authentication request failed");
+       }
+
+#endif
+       info("Encrypt value   %d",encrypt);
+       if (hci_encrypt_link(dd, htobs(cr->conn_info->handle), encrypt, 25000) < 0) {
+               free(cr);
+               return g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".Failed",
+                               "Encryption request failed");
+       }
+
+       DBG("handle_authenticate_link_request2");
+       free(cr);
+
+       hci_close_dev(dd);
+
+       return dbus_message_new_method_return(msg);
+
+}
+#endif
+
+
+
+
+#ifdef __TIZEN_PATCH__
+static DBusMessage *authenticate_link(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct btd_device *device;
+       const gchar *address, *agent_path, *capability, *sender;
+       uint8_t cap;
+       info("authenticate_link 1\n");
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
+                                       DBUS_TYPE_OBJECT_PATH, &agent_path,
+                                       DBUS_TYPE_STRING, &capability,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return btd_error_invalid_args(msg);
+      info("authenticate_link 2\n");
+       if (check_address(address) < 0)
+               return btd_error_invalid_args(msg);
+       info("authenticate_link 3\n");
+       sender = dbus_message_get_sender(msg);
+       if (adapter->agent &&
+                       agent_matches(adapter->agent, sender, agent_path)) {
+               error("Refusing adapter agent usage as device specific one");
+               return btd_error_invalid_args(msg);
+       }
+       info("authenticate_link 4\n");
+       cap = parse_io_capability(capability);
+       if (cap == IO_CAPABILITY_INVALID)
+               return btd_error_invalid_args(msg);
+       info("authenticate_link 5\n");
+       device = adapter_get_device(conn, adapter, address);
+       if (!device)
+               return g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".Failed",
+                               "Unable to create a new device object");
+       info("authenticate_link 5\n");
+       return device_jsr82_authenticate_link(device, conn, msg, agent_path, cap);
+}
+#endif
+
+#ifdef __TIZEN_PATCH__
+static DBusMessage *encrypt_connection(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       gboolean encrypt = FALSE;
+       DBG("encrypt_link");
+       info("encrypt_connection value  1   %d",encrypt);
+       if (!adapter->up)
+               return btd_error_not_ready(msg);
+       DBusMessageIter iter;
+       DBusMessageIter sub;
+       const char *address;
+
+
+       if (!dbus_message_iter_init(msg, &iter))
+               return btd_error_invalid_args(msg);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+               return btd_error_invalid_args(msg);
+
+       dbus_message_iter_get_basic(&iter, &address);
+       dbus_message_iter_next(&iter);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+               return btd_error_invalid_args(msg);
+       dbus_message_iter_recurse(&iter, &sub);
+
+       if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+               return btd_error_invalid_args(msg);
+
+       dbus_message_iter_get_basic(&sub, &encrypt);
+/*     if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_STRING, &address, DBUS_TYPE_BOOLEAN,&encrypt,
+                               DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);*/
+
+       info("encrypt_connection value  2   %d",encrypt);
+       return adapter_encrypt_link(conn, msg, address, encrypt, data);
+
+}
+#endif
+static DBusMessage *create_paired_device(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct btd_device *device;
+       const gchar *address, *agent_path, *capability, *sender;
+       uint8_t cap;
+       int err;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
+                                       DBUS_TYPE_OBJECT_PATH, &agent_path,
+                                       DBUS_TYPE_STRING, &capability,
+                                       DBUS_TYPE_INVALID) == FALSE)
+               return btd_error_invalid_args(msg);
+
+       if (check_address(address) < 0)
+               return btd_error_invalid_args(msg);
+
+       if (!adapter->up)
+               return btd_error_not_ready(msg);
+
+       sender = dbus_message_get_sender(msg);
+       if (adapter->agent &&
+                       agent_matches(adapter->agent, sender, agent_path)) {
+               error("Refusing adapter agent usage as device specific one");
+               return btd_error_invalid_args(msg);
+       }
+
+       cap = parse_io_capability(capability);
+       if (cap == IO_CAPABILITY_INVALID)
+               return btd_error_invalid_args(msg);
+
+       device = adapter_find_device(adapter, address);
+       if (!device) {
+               device = create_device_internal(conn, adapter, address,
+                                                               FALSE, &err);
+               if (!device)
+                       return btd_error_failed(msg, strerror(-err));
+       }
+
+       if (device_get_type(device) != DEVICE_TYPE_LE)
+               return device_create_bonding(device, conn, msg,
+                                                       agent_path, cap);
+
+       err = device_browse_primary(device, conn, msg, TRUE);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       return NULL;
+}
+
+static gint device_path_cmp(struct btd_device *device, const gchar *path)
+{
+       const gchar *dev_path = device_get_path(device);
+
+       return strcasecmp(dev_path, path);
+}
+
+static DBusMessage *remove_device(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct btd_device *device;
+       const char *path;
+       GSList *l;
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return btd_error_invalid_args(msg);
+
+       l = g_slist_find_custom(adapter->devices,
+                       path, (GCompareFunc) device_path_cmp);
+       if (!l)
+               return btd_error_does_not_exist(msg);
+
+       device = l->data;
+
+       if (device_is_temporary(device) || device_is_busy(device))
+               return g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".DoesNotExist",
+                               "Device creation in progress");
+
+       device_set_temporary(device, TRUE);
+
+       if (!device_is_connected(device)) {
+               adapter_remove_device(conn, adapter, device, TRUE);
+               return dbus_message_new_method_return(msg);
+       }
+
+       device_request_disconnect(device, msg);
+       return NULL;
+}
+
+static DBusMessage *find_device(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct btd_adapter *adapter = data;
+       struct btd_device *device;
+       DBusMessage *reply;
+       const gchar *address;
+       GSList *l;
+       const gchar *dev_path;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
+                                               DBUS_TYPE_INVALID))
+               return btd_error_invalid_args(msg);
+
+       l = g_slist_find_custom(adapter->devices,
+                       address, (GCompareFunc) device_address_cmp);
+       if (!l)
+               return btd_error_does_not_exist(msg);
+
+       device = l->data;
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       dev_path = device_get_path(device);
+
+       dbus_message_append_args(reply,
+                               DBUS_TYPE_OBJECT_PATH, &dev_path,
+                               DBUS_TYPE_INVALID);
+
+       return reply;
+}
+
+static void agent_removed(struct agent *agent, struct btd_adapter *adapter)
+{
+       adapter_ops->set_io_capability(adapter->dev_id,
+                                       IO_CAPABILITY_NOINPUTNOOUTPUT);
+
+       adapter->agent = NULL;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       const char *path, *name, *capability;
+       struct agent *agent;
+       struct btd_adapter *adapter = data;
+       uint8_t cap;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                       DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID))
+               return NULL;
+
+       if (adapter->agent)
+               return btd_error_already_exists(msg);
+
+       cap = parse_io_capability(capability);
+       if (cap == IO_CAPABILITY_INVALID)
+               return btd_error_invalid_args(msg);
+
+       name = dbus_message_get_sender(msg);
+
+       agent = agent_create(adapter, name, path, cap,
+                               (agent_remove_cb) agent_removed, adapter);
+       if (!agent)
+               return btd_error_failed(msg, "Failed to create a new agent");
+
+       adapter->agent = agent;
+
+       DBG("Agent registered for hci%d at %s:%s", adapter->dev_id, name,
+                       path);
+
+       adapter_ops->set_io_capability(adapter->dev_id, cap);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       const char *path, *name;
+       struct btd_adapter *adapter = data;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                               DBUS_TYPE_INVALID))
+               return NULL;
+
+       name = dbus_message_get_sender(msg);
+
+       if (!adapter->agent || !agent_matches(adapter->agent, name, path))
+               return btd_error_does_not_exist(msg);
+
+       agent_free(adapter->agent);
+       adapter->agent = NULL;
+
+       return dbus_message_new_method_return(msg);
+}
+
+static GDBusMethodTable adapter_methods[] = {
+       { "GetProperties",      "",     "a{sv}",get_properties          },
+       { "SetProperty",        "sv",   "",     set_property,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "RequestSession",     "",     "",     request_session,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "ReleaseSession",     "",     "",     release_session         },
+       { "StartDiscovery",     "",     "",     adapter_start_discovery },
+       { "StopDiscovery",      "",     "",     adapter_stop_discovery,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "ListDevices",        "",     "ao",   list_devices,
+                                               G_DBUS_METHOD_FLAG_DEPRECATED},
+       { "CreateDevice",       "s",    "o",    create_device,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "CreatePairedDevice", "sos",  "o",    create_paired_device,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "CancelDeviceCreation","s",   "",     cancel_device_creation,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "RemoveDevice",       "o",    "",     remove_device,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "FindDevice",         "s",    "o",    find_device             },
+       { "RegisterAgent",      "os",   "",     register_agent          },
+       { "UnregisterAgent",    "o",    "",     unregister_agent        },
+
+       #ifdef __TIZEN_PATCH__
+       { "AuthenticateLink","sos",     "o",    authenticate_link, G_DBUS_METHOD_FLAG_ASYNC},
+       { "EncryptLink","sv",   "",     encrypt_connection, G_DBUS_METHOD_FLAG_ASYNC},
+       #endif
+       { }
+};
+
+static GDBusSignalTable adapter_signals[] = {
+       { "PropertyChanged",            "sv"            },
+       { "DeviceCreated",              "o"             },
+       { "DeviceRemoved",              "o"             },
+       { "DeviceFound",                "sa{sv}"        },
+       { "DeviceDisappeared",          "s"             },
+       { }
+};
+
+static void create_stored_device_from_profiles(char *key, char *value,
+                                               void *user_data)
+{
+       struct btd_adapter *adapter = user_data;
+       GSList *uuids = bt_string2list(value);
+       struct btd_device *device;
+
+       if (g_slist_find_custom(adapter->devices,
+                               key, (GCompareFunc) device_address_cmp))
+               return;
+
+       device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
+       if (!device)
+               return;
+
+       device_set_temporary(device, FALSE);
+       adapter->devices = g_slist_append(adapter->devices, device);
+
+       device_probe_drivers(device, uuids);
+
+       g_slist_foreach(uuids, (GFunc) g_free, NULL);
+       g_slist_free(uuids);
+}
+
+struct adapter_keys {
+       struct btd_adapter *adapter;
+       GSList *keys;
+};
+
+static struct link_key_info *get_key_info(const char *addr, const char *value)
+{
+       struct link_key_info *info;
+       char tmp[3];
+       long int l;
+       int i;
+
+       if (strlen(value) < 36) {
+               error("Unexpectedly short (%zu) link key line", strlen(value));
+               return NULL;
+       }
+
+       info = g_new0(struct link_key_info, 1);
+
+       str2ba(addr, &info->bdaddr);
+
+       memset(tmp, 0, sizeof(tmp));
+
+       for (i = 0; i < 16; i++) {
+               memcpy(tmp, value + (i * 2), 2);
+               info->key[i] = (uint8_t) strtol(tmp, NULL, 16);
+       }
+
+       memcpy(tmp, value + 33, 2);
+       info->type = (uint8_t) strtol(tmp, NULL, 10);
+
+       memcpy(tmp, value + 35, 2);
+       l = strtol(tmp, NULL, 10);
+       if (l < 0)
+               l = 0;
+       info->pin_len = l;
+
+       return info;
+}
+
+static void create_stored_device_from_linkkeys(char *key, char *value,
+                                                       void *user_data)
+{
+       struct adapter_keys *keys = user_data;
+       struct btd_adapter *adapter = keys->adapter;
+       struct btd_device *device;
+       struct link_key_info *info;
+
+       info = get_key_info(key, value);
+       if (info)
+               keys->keys = g_slist_append(keys->keys, info);
+
+       if (g_slist_find_custom(adapter->devices, key,
+                                       (GCompareFunc) device_address_cmp))
+               return;
+
+       device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
+       if (device) {
+               device_set_temporary(device, FALSE);
+               adapter->devices = g_slist_append(adapter->devices, device);
+       }
+}
+
+static void create_stored_device_from_blocked(char *key, char *value,
+                                                       void *user_data)
+{
+       struct btd_adapter *adapter = user_data;
+       struct btd_device *device;
+
+       if (g_slist_find_custom(adapter->devices,
+                               key, (GCompareFunc) device_address_cmp))
+               return;
+
+       device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
+       if (device) {
+               device_set_temporary(device, FALSE);
+               adapter->devices = g_slist_append(adapter->devices, device);
+       }
+}
+
+static void create_stored_device_from_types(char *key, char *value,
+                                                       void *user_data)
+{
+       GSList *l;
+       struct btd_adapter *adapter = user_data;
+       struct btd_device *device;
+       uint8_t type;
+
+       type = strtol(value, NULL, 16);
+
+       l = g_slist_find_custom(adapter->devices,
+                               key, (GCompareFunc) device_address_cmp);
+       if (l) {
+               device = l->data;
+               device_set_type(device, type);
+               return;
+       }
+
+       device = device_create(connection, adapter, key, type);
+       if (device) {
+               device_set_temporary(device, FALSE);
+               adapter->devices = g_slist_append(adapter->devices, device);
+       }
+}
+
+static GSList *string_to_primary_list(char *str)
+{
+       GSList *l = NULL;
+       char **services;
+       int i;
+
+       if (str == NULL)
+               return NULL;
+
+       services = g_strsplit(str, " ", 0);
+       if (services == NULL)
+               return NULL;
+
+       for (i = 0; services[i]; i++) {
+               struct att_primary *prim;
+               int ret;
+
+               prim = g_new0(struct att_primary, 1);
+
+               ret = sscanf(services[i], "%04hX#%04hX#%s", &prim->start,
+                                                       &prim->end, prim->uuid);
+
+               if (ret < 3) {
+                       g_free(prim);
+                       continue;
+               }
+
+               l = g_slist_append(l, prim);
+       }
+
+       g_strfreev(services);
+
+       return l;
+}
+
+static void create_stored_device_from_primary(char *key, char *value,
+                                                       void *user_data)
+{
+       struct btd_adapter *adapter = user_data;
+       struct btd_device *device;
+       GSList *services, *uuids, *l;
+
+       l = g_slist_find_custom(adapter->devices,
+                               key, (GCompareFunc) device_address_cmp);
+       if (l)
+               device = l->data;
+       else {
+               device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
+               if (!device)
+                       return;
+
+               device_set_temporary(device, FALSE);
+               adapter->devices = g_slist_append(adapter->devices, device);
+       }
+
+       services = string_to_primary_list(value);
+       if (services == NULL)
+               return;
+
+       for (l = services, uuids = NULL; l; l = l->next) {
+               struct att_primary *prim = l->data;
+               uuids = g_slist_append(uuids, prim->uuid);
+
+               device_add_primary(device, prim);
+       }
+
+       device_probe_drivers(device, uuids);
+
+       g_slist_free(services);
+       g_slist_free(uuids);
+}
+
+static void load_devices(struct btd_adapter *adapter)
+{
+       char filename[PATH_MAX + 1];
+       char srcaddr[18];
+       struct adapter_keys keys = { adapter, NULL };
+       int err;
+
+       ba2str(&adapter->bdaddr, srcaddr);
+
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "profiles");
+       textfile_foreach(filename, create_stored_device_from_profiles,
+                                                               adapter);
+
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "primary");
+       textfile_foreach(filename, create_stored_device_from_primary,
+                                                               adapter);
+
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "linkkeys");
+       textfile_foreach(filename, create_stored_device_from_linkkeys, &keys);
+
+       err = adapter_ops->load_keys(adapter->dev_id, keys.keys,
+                                                       main_opts.debug_keys);
+       if (err < 0) {
+               error("Unable to load keys to adapter_ops: %s (%d)",
+                                                       strerror(-err), -err);
+               g_slist_foreach(keys.keys, (GFunc) g_free, NULL);
+               g_slist_free(keys.keys);
+       }
+
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "blocked");
+       textfile_foreach(filename, create_stored_device_from_blocked, adapter);
+
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "types");
+       textfile_foreach(filename, create_stored_device_from_types, adapter);
+}
+
+int btd_adapter_block_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
+{
+       return adapter_ops->block_device(adapter->dev_id, bdaddr);
+}
+
+int btd_adapter_unblock_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
+{
+       return adapter_ops->unblock_device(adapter->dev_id, bdaddr);
+}
+
+static void clear_blocked(struct btd_adapter *adapter)
+{
+       int err;
+
+       err = adapter_ops->unblock_device(adapter->dev_id, BDADDR_ANY);
+       if (err < 0)
+               error("Clearing blocked list failed: %s (%d)",
+                                               strerror(-err), -err);
+}
+
+static void probe_driver(struct btd_adapter *adapter, gpointer user_data)
+{
+       struct btd_adapter_driver *driver = user_data;
+       int err;
+
+       if (!adapter->up)
+               return;
+
+       if (driver->probe == NULL)
+               return;
+
+       err = driver->probe(adapter);
+       if (err < 0) {
+               error("%s: %s (%d)", driver->name, strerror(-err), -err);
+               return;
+       }
+
+       adapter->loaded_drivers = g_slist_prepend(adapter->loaded_drivers,
+                                                                       driver);
+}
+
+static void load_drivers(struct btd_adapter *adapter)
+{
+       GSList *l;
+
+       for (l = adapter_drivers; l; l = l->next)
+               probe_driver(adapter, l->data);
+}
+
+static void load_connections(struct btd_adapter *adapter)
+{
+       GSList *l, *conns;
+       int err;
+
+       err = adapter_ops->get_conn_list(adapter->dev_id, &conns);
+       if (err < 0) {
+               error("Unable to fetch existing connections: %s (%d)",
+                                                       strerror(-err), -err);
+               return;
+       }
+
+       for (l = conns; l != NULL; l = g_slist_next(l)) {
+               bdaddr_t *bdaddr = l->data;
+               struct btd_device *device;
+               char address[18];
+
+               ba2str(bdaddr, address);
+               DBG("Adding existing connection to %s", address);
+
+               device = adapter_get_device(connection, adapter, address);
+               if (device)
+                       adapter_add_connection(adapter, device);
+       }
+
+       g_slist_foreach(conns, (GFunc) g_free, NULL);
+       g_slist_free(conns);
+}
+
+static int get_discoverable_timeout(const char *src)
+{
+       int timeout;
+
+       if (read_discoverable_timeout(src, &timeout) == 0)
+               return timeout;
+
+       return main_opts.discovto;
+}
+
+static int get_pairable_timeout(const char *src)
+{
+       int timeout;
+
+       if (read_pairable_timeout(src, &timeout) == 0)
+               return timeout;
+
+       return main_opts.pairto;
+}
+
+static void call_adapter_powered_callbacks(struct btd_adapter *adapter,
+                                               gboolean powered)
+{
+       GSList *l;
+
+       for (l = adapter->powered_callbacks; l; l = l->next) {
+               btd_adapter_powered_cb cb = l->data;
+
+               cb(adapter, powered);
+       }
+}
+
+static void emit_device_disappeared(gpointer data, gpointer user_data)
+{
+       struct remote_dev_info *dev = data;
+       struct btd_adapter *adapter = user_data;
+       char address[18];
+       const char *paddr = address;
+
+       ba2str(&dev->bdaddr, address);
+
+       g_dbus_emit_signal(connection, adapter->path,
+                       ADAPTER_INTERFACE, "DeviceDisappeared",
+                       DBUS_TYPE_STRING, &paddr,
+                       DBUS_TYPE_INVALID);
+
+       adapter->found_devices = g_slist_remove(adapter->found_devices, dev);
+}
+
+static void update_oor_devices(struct btd_adapter *adapter)
+{
+       g_slist_foreach(adapter->oor_devices, emit_device_disappeared, adapter);
+       g_slist_foreach(adapter->oor_devices, (GFunc) dev_info_free, NULL);
+       g_slist_free(adapter->oor_devices);
+       adapter->oor_devices =  g_slist_copy(adapter->found_devices);
+}
+
+static gboolean bredr_capable(struct btd_adapter *adapter)
+{
+       struct hci_dev *dev = &adapter->dev;
+
+       return (dev->features[4] & LMP_NO_BREDR) == 0 ? TRUE : FALSE;
+}
+
+static gboolean le_capable(struct btd_adapter *adapter)
+{
+       struct hci_dev *dev = &adapter->dev;
+
+       return (dev->features[4] & LMP_LE &&
+                       dev->extfeatures[0] & LMP_HOST_LE) ? TRUE : FALSE;
+}
+
+int adapter_get_discover_type(struct btd_adapter *adapter)
+{
+       gboolean le, bredr;
+       int type;
+
+       le = le_capable(adapter);
+       bredr = bredr_capable(adapter);
+
+       if (le)
+               type = bredr ? DISC_INTERLEAVE : DISC_LE;
+       else
+               type = main_opts.discov_interval ? DISC_STDINQ :
+                                                       DISC_PINQ;
+
+       if (main_opts.name_resolv)
+               type |= DISC_RESOLVNAME;
+
+       return type;
+}
+
+void btd_adapter_get_mode(struct btd_adapter *adapter, uint8_t *mode,
+                                       uint8_t *on_mode, gboolean *pairable)
+{
+       char str[14], address[18];
+
+       ba2str(&adapter->bdaddr, address);
+
+       if (mode) {
+               if (main_opts.remember_powered == FALSE)
+                       *mode = main_opts.mode;
+               else if (read_device_mode(address, str, sizeof(str)) < 0)
+                       *mode = main_opts.mode;
+               else
+                       *mode = get_mode(&adapter->bdaddr, str);
+       }
+
+       if (on_mode) {
+               if (main_opts.remember_powered == FALSE) {
+                       if (adapter->initialized)
+                               *on_mode = get_mode(&adapter->bdaddr, "on");
+                       else {
+                               *on_mode = main_opts.mode;
+                               adapter->initialized = TRUE;
+                       }
+               } else if (read_on_mode(address, str, sizeof(str)) < 0)
+                       *on_mode = main_opts.mode;
+               else
+                       *on_mode = get_mode(&adapter->bdaddr, str);
+       }
+
+       if (pairable)
+               *pairable = adapter->pairable;
+}
+
+void btd_adapter_start(struct btd_adapter *adapter)
+{
+       char address[18];
+       uint8_t cls[3];
+       gboolean powered;
+
+       ba2str(&adapter->bdaddr, address);
+
+       adapter->dev_class = 0;
+       adapter->off_requested = FALSE;
+       adapter->up = TRUE;
+       adapter->discov_timeout = get_discoverable_timeout(address);
+       adapter->pairable_timeout = get_pairable_timeout(address);
+       adapter->state = STATE_IDLE;
+       adapter->mode = MODE_CONNECTABLE;
+
+       if (main_opts.le)
+               adapter_ops->enable_le(adapter->dev_id);
+
+       adapter_ops->set_name(adapter->dev_id, adapter->dev.name);
+
+       if (read_local_class(&adapter->bdaddr, cls) < 0) {
+               uint32_t class = htobl(main_opts.class);
+               memcpy(cls, &class, 3);
+       }
+
+       btd_adapter_set_class(adapter, cls[1], cls[0]);
+
+       powered = TRUE;
+       emit_property_changed(connection, adapter->path,
+                                       ADAPTER_INTERFACE, "Powered",
+                                       DBUS_TYPE_BOOLEAN, &powered);
+
+       call_adapter_powered_callbacks(adapter, TRUE);
+
+       adapter_ops->disable_cod_cache(adapter->dev_id);
+
+       info("Adapter %s has been enabled", adapter->path);
+}
+
+static void reply_pending_requests(struct btd_adapter *adapter)
+{
+       GSList *l;
+
+       if (!adapter)
+               return;
+
+       /* pending bonding */
+       for (l = adapter->devices; l; l = l->next) {
+               struct btd_device *device = l->data;
+
+               if (device_is_bonding(device, NULL))
+                       device_cancel_bonding(device,
+                                               HCI_OE_USER_ENDED_CONNECTION);
+       }
+}
+
+static void remove_driver(gpointer data, gpointer user_data)
+{
+       struct btd_adapter_driver *driver = data;
+       struct btd_adapter *adapter = user_data;
+
+       if (driver->remove)
+               driver->remove(adapter);
+}
+
+static void unload_drivers(struct btd_adapter *adapter)
+{
+       g_slist_foreach(adapter->loaded_drivers, remove_driver, adapter);
+       g_slist_free(adapter->loaded_drivers);
+       adapter->loaded_drivers = NULL;
+}
+
+static void set_mode_complete(struct btd_adapter *adapter)
+{
+       struct session_req *pending;
+       const char *modestr;
+       int err;
+
+       DBG("");
+
+       if (adapter->pending_mode == NULL)
+               return;
+
+       pending = adapter->pending_mode;
+       adapter->pending_mode = NULL;
+
+       err = (pending->mode != adapter->mode) ? -EINVAL : 0;
+
+       if (pending->msg != NULL) {
+               DBusMessage *msg = pending->msg;
+               DBusMessage *reply;
+
+               if (err < 0)
+                       reply = btd_error_failed(msg, strerror(-err));
+               else {
+                       if (strcmp(dbus_message_get_member(msg),
+                                               "SetProperty") == 0)
+                               adapter->global_mode = adapter->mode;
+                       reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+               }
+
+               g_dbus_send_message(connection, reply);
+       }
+
+       modestr = mode2str(adapter->mode);
+
+       DBG("%s", modestr);
+
+       /* restore if the mode doesn't matches the pending */
+       if (err != 0) {
+               write_device_mode(&adapter->bdaddr, modestr);
+               error("unable to set mode: %s", mode2str(pending->mode));
+       }
+
+       session_unref(pending);
+}
+
+int btd_adapter_stop(struct btd_adapter *adapter)
+{
+       gboolean powered, discoverable, pairable;
+#ifdef __TIZEN_PATCH__
+       gboolean limited;
+#endif
+
+       /* cancel pending timeout */
+       if (adapter->discov_timeout_id) {
+               g_source_remove(adapter->discov_timeout_id);
+               adapter->discov_timeout_id = 0;
+       }
+
+       /* check pending requests */
+       reply_pending_requests(adapter);
+
+       stop_discovery(adapter, FALSE);
+
+       if (adapter->disc_sessions) {
+               g_slist_foreach(adapter->disc_sessions, (GFunc) session_free,
+                               NULL);
+               g_slist_free(adapter->disc_sessions);
+               adapter->disc_sessions = NULL;
+       }
+
+       while (adapter->connections) {
+               struct btd_device *device = adapter->connections->data;
+               adapter_remove_connection(adapter, device);
+       }
+
+       if (adapter->scan_mode == (SCAN_PAGE | SCAN_INQUIRY)) {
+               discoverable = FALSE;
+               emit_property_changed(connection, adapter->path,
+                                       ADAPTER_INTERFACE, "Discoverable",
+                                       DBUS_TYPE_BOOLEAN, &discoverable);
+       }
+
+       if ((adapter->scan_mode & SCAN_PAGE) && adapter->pairable == TRUE) {
+               pairable = FALSE;
+               emit_property_changed(connection, adapter->path,
+                                       ADAPTER_INTERFACE, "Pairable",
+                                       DBUS_TYPE_BOOLEAN, &pairable);
+       }
+
+       powered = FALSE;
+       emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE,
+                               "Powered", DBUS_TYPE_BOOLEAN, &powered);
+
+       adapter->up = 0;
+       adapter->scan_mode = SCAN_DISABLED;
+       adapter->mode = MODE_OFF;
+       adapter->state = STATE_IDLE;
+       adapter->off_requested = FALSE;
+       adapter->name_stored = FALSE;
+
+       call_adapter_powered_callbacks(adapter, FALSE);
+
+       info("Adapter %s has been disabled", adapter->path);
+
+       set_mode_complete(adapter);
+
+       return 0;
+}
+
+int adapter_update_ssp_mode(struct btd_adapter *adapter, uint8_t mode)
+{
+       struct hci_dev *dev = &adapter->dev;
+
+       dev->ssp_mode = mode;
+
+       return 0;
+}
+
+static void adapter_free(gpointer user_data)
+{
+       struct btd_adapter *adapter = user_data;
+
+       agent_free(adapter->agent);
+       adapter->agent = NULL;
+
+       DBG("%p", adapter);
+
+       if (adapter->auth_idle_id)
+               g_source_remove(adapter->auth_idle_id);
+
+       sdp_list_free(adapter->services, NULL);
+
+       g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL);
+       g_slist_free(adapter->found_devices);
+
+       g_slist_free(adapter->oor_devices);
+
+       g_free(adapter->path);
+       g_free(adapter);
+}
+
+struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter)
+{
+       adapter->ref++;
+
+       DBG("%p: ref=%d", adapter, adapter->ref);
+
+       return adapter;
+}
+
+void btd_adapter_unref(struct btd_adapter *adapter)
+{
+       gchar *path;
+
+       adapter->ref--;
+
+       DBG("%p: ref=%d", adapter, adapter->ref);
+
+       if (adapter->ref > 0)
+               return;
+
+       path = g_strdup(adapter->path);
+
+       g_dbus_unregister_interface(connection, path, ADAPTER_INTERFACE);
+
+       g_free(path);
+}
+
+gboolean adapter_init(struct btd_adapter *adapter)
+{
+       struct hci_version ver;
+       struct hci_dev *dev;
+       int err;
+
+       /* adapter_ops makes sure that newly registered adapters always
+        * start off as powered */
+       adapter->up = TRUE;
+
+       adapter_ops->read_bdaddr(adapter->dev_id, &adapter->bdaddr);
+
+       if (bacmp(&adapter->bdaddr, BDADDR_ANY) == 0) {
+               error("No address available for hci%d", adapter->dev_id);
+               return FALSE;
+       }
+
+       err = adapter_ops->read_local_version(adapter->dev_id, &ver);
+       if (err < 0) {
+               error("Can't read version info for hci%d: %s (%d)",
+                                       adapter->dev_id, strerror(-err), -err);
+               return FALSE;
+       }
+
+       dev = &adapter->dev;
+
+       dev->hci_rev = ver.hci_rev;
+       dev->lmp_ver = ver.lmp_ver;
+       dev->lmp_subver = ver.lmp_subver;
+       dev->manufacturer = ver.manufacturer;
+
+       err = adapter_ops->read_local_features(adapter->dev_id, dev->features);
+       if (err < 0) {
+               error("Can't read features for hci%d: %s (%d)",
+                                       adapter->dev_id, strerror(-err), -err);
+               return FALSE;
+       }
+
+       if (read_local_name(&adapter->bdaddr, adapter->dev.name) < 0)
+               expand_name(adapter->dev.name, MAX_NAME_LENGTH, main_opts.name,
+                                                       adapter->dev_id);
+
+       if (main_opts.attrib_server)
+               attrib_gap_set(GATT_CHARAC_DEVICE_NAME,
+                       (const uint8_t *) dev->name, strlen(dev->name));
+
+       sdp_init_services_list(&adapter->bdaddr);
+       load_drivers(adapter);
+       clear_blocked(adapter);
+       load_devices(adapter);
+
+       /* Set pairable mode */
+       if (read_device_pairable(&adapter->bdaddr, &adapter->pairable) < 0)
+               adapter->pairable = TRUE;
+
+       /* retrieve the active connections: address the scenario where
+        * the are active connections before the daemon've started */
+       load_connections(adapter);
+
+       return TRUE;
+}
+
+struct btd_adapter *adapter_create(DBusConnection *conn, int id)
+{
+       char path[MAX_PATH_LENGTH];
+       struct btd_adapter *adapter;
+       const char *base_path = manager_get_base_path();
+
+       if (!connection)
+               connection = conn;
+
+       adapter = g_try_new0(struct btd_adapter, 1);
+       if (!adapter) {
+               error("adapter_create: failed to alloc memory for hci%d", id);
+               return NULL;
+       }
+
+       adapter->dev_id = id;
+
+       snprintf(path, sizeof(path), "%s/hci%d", base_path, id);
+       adapter->path = g_strdup(path);
+
+       if (!g_dbus_register_interface(conn, path, ADAPTER_INTERFACE,
+                                       adapter_methods, adapter_signals, NULL,
+                                       adapter, adapter_free)) {
+               error("Adapter interface init failed on path %s", path);
+               adapter_free(adapter);
+               return NULL;
+       }
+
+       return btd_adapter_ref(adapter);
+}
+
+void adapter_remove(struct btd_adapter *adapter)
+{
+       GSList *l;
+
+       DBG("Removing adapter %s", adapter->path);
+
+       for (l = adapter->devices; l; l = l->next)
+               device_remove(l->data, FALSE);
+       g_slist_free(adapter->devices);
+
+       unload_drivers(adapter);
+
+       /* Return adapter to down state if it was not up on init */
+       adapter_ops->restore_powered(adapter->dev_id);
+
+       btd_adapter_unref(adapter);
+}
+
+uint16_t adapter_get_dev_id(struct btd_adapter *adapter)
+{
+       return adapter->dev_id;
+}
+
+const gchar *adapter_get_path(struct btd_adapter *adapter)
+{
+       if (!adapter)
+               return NULL;
+
+       return adapter->path;
+}
+
+void adapter_get_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
+{
+       bacpy(bdaddr, &adapter->bdaddr);
+}
+
+void adapter_set_state(struct btd_adapter *adapter, int state)
+{
+       const char *path = adapter->path;
+       gboolean discov_active;
+       int previous, type;
+
+       if (adapter->state == state)
+               return;
+
+       previous = adapter->state;
+       adapter->state = state;
+
+       type = adapter_get_discover_type(adapter);
+
+       switch (state) {
+       case STATE_STDINQ:
+       case STATE_PINQ:
+               discov_active = TRUE;
+
+               /* Started a new session while resolving names ? */
+               if (previous & STATE_RESOLVNAME)
+                       return;
+               break;
+       case STATE_LE_SCAN:
+               /* Scanning enabled */
+               adapter->stop_discov_id = g_timeout_add(5120,
+                                               stop_scanning, adapter);
+
+               /* For dual mode: don't send "Discovering = TRUE"  */
+               if (bredr_capable(adapter) == TRUE)
+                       return;
+
+               /* LE only */
+               discov_active = TRUE;
+
+               break;
+       case STATE_IDLE:
+               /*
+                * Interleave: from inquiry to scanning. Interleave is not
+                * applicable to requests triggered by external applications.
+                */
+               if (adapter->disc_sessions && (type & DISC_INTERLEAVE) &&
+                                               (previous & STATE_STDINQ)) {
+                       adapter_ops->start_scanning(adapter->dev_id);
+                       return;
+               }
+               /* BR/EDR only: inquiry finished */
+               discov_active = FALSE;
+               break;
+       default:
+               discov_active = FALSE;
+               break;
+       }
+
+       if (discov_active == FALSE) {
+               if (type & DISC_RESOLVNAME) {
+                       if (adapter_resolve_names(adapter) == 0) {
+                               adapter->state |= STATE_RESOLVNAME;
+                               return;
+                       }
+               }
+
+               update_oor_devices(adapter);
+       } else if (adapter->disc_sessions && main_opts.discov_interval)
+                       adapter->scheduler_id = g_timeout_add_seconds(
+                                               main_opts.discov_interval,
+                                               (GSourceFunc) start_discovery,
+                                               adapter);
+
+       emit_property_changed(connection, path,
+                               ADAPTER_INTERFACE, "Discovering",
+                               DBUS_TYPE_BOOLEAN, &discov_active);
+}
+
+int adapter_get_state(struct btd_adapter *adapter)
+{
+       return adapter->state;
+}
+
+struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter,
+                                               struct remote_dev_info *match)
+{
+       GSList *l;
+
+       l = g_slist_find_custom(adapter->found_devices, match,
+                                       (GCompareFunc) found_device_cmp);
+       if (l)
+               return l->data;
+
+       return NULL;
+}
+
+static int dev_rssi_cmp(struct remote_dev_info *d1, struct remote_dev_info *d2)
+{
+       int rssi1, rssi2;
+
+       rssi1 = d1->rssi < 0 ? -d1->rssi : d1->rssi;
+       rssi2 = d2->rssi < 0 ? -d2->rssi : d2->rssi;
+
+       return rssi1 - rssi2;
+}
+
+static void append_dict_valist(DBusMessageIter *iter,
+                                       const char *first_key,
+                                       va_list var_args)
+{
+       DBusMessageIter dict;
+       const char *key;
+       int type;
+       int n_elements;
+       void *val;
+
+       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);
+
+       key = first_key;
+       while (key) {
+               type = va_arg(var_args, int);
+               val = va_arg(var_args, void *);
+               if (type == DBUS_TYPE_ARRAY) {
+                       n_elements = va_arg(var_args, int);
+                       if (n_elements > 0)
+                               dict_append_array(&dict, key, DBUS_TYPE_STRING,
+                                               val, n_elements);
+               } else
+                       dict_append_entry(&dict, key, type, val);
+               key = va_arg(var_args, char *);
+       }
+
+       dbus_message_iter_close_container(iter, &dict);
+}
+
+static void emit_device_found(const char *path, const char *address,
+                               const char *first_key, ...)
+{
+       DBusMessage *signal;
+       DBusMessageIter iter;
+       va_list var_args;
+
+       signal = dbus_message_new_signal(path, ADAPTER_INTERFACE,
+                                       "DeviceFound");
+       if (!signal) {
+               error("Unable to allocate new %s.DeviceFound signal",
+                               ADAPTER_INTERFACE);
+               return;
+       }
+       dbus_message_iter_init_append(signal, &iter);
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &address);
+
+       va_start(var_args, first_key);
+       append_dict_valist(&iter, first_key, var_args);
+       va_end(var_args);
+
+       g_dbus_send_message(connection, signal);
+}
+
+static char **strlist2array(GSList *list)
+{
+       GSList *l;
+       unsigned int i, n;
+       char **array;
+
+       if (list == NULL)
+               return NULL;
+
+       n = g_slist_length(list);
+       array = g_new0(char *, n + 1);
+
+       for (l = list, i = 0; l; l = l->next, i++)
+               array[i] = g_strdup((const gchar *) l->data);
+
+       return array;
+}
+
+void adapter_emit_device_found(struct btd_adapter *adapter,
+                                               struct remote_dev_info *dev)
+{
+       struct btd_device *device;
+       char peer_addr[18], local_addr[18];
+       const char *icon, *paddr = peer_addr;
+       dbus_bool_t paired = FALSE;
+       dbus_int16_t rssi = dev->rssi;
+       char *alias;
+       size_t uuid_count;
+
+       ba2str(&dev->bdaddr, peer_addr);
+       ba2str(&adapter->bdaddr, local_addr);
+
+       device = adapter_find_device(adapter, paddr);
+       if (device)
+               paired = device_is_paired(device);
+
+       /* The uuids string array is updated only if necessary */
+       uuid_count = g_slist_length(dev->services);
+       if (dev->services && dev->uuid_count != uuid_count) {
+               g_strfreev(dev->uuids);
+               dev->uuids = strlist2array(dev->services);
+               dev->uuid_count = uuid_count;
+       }
+
+       if (dev->le) {
+               gboolean broadcaster;
+
+               if (dev->flags & (EIR_LIM_DISC | EIR_GEN_DISC))
+                       broadcaster = FALSE;
+               else
+                       broadcaster = TRUE;
+
+               emit_device_found(adapter->path, paddr,
+                               "Address", DBUS_TYPE_STRING, &paddr,
+                               "RSSI", DBUS_TYPE_INT16, &rssi,
+                               "Name", DBUS_TYPE_STRING, &dev->name,
+                               "Paired", DBUS_TYPE_BOOLEAN, &paired,
+                               "Broadcaster", DBUS_TYPE_BOOLEAN, &broadcaster,
+                               "UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, uuid_count,
+                               NULL);
+               return;
+       }
+
+       icon = class_to_icon(dev->class);
+
+       if (!dev->alias) {
+               if (!dev->name) {
+                       alias = g_strdup(peer_addr);
+                       g_strdelimit(alias, ":", '-');
+               } else
+                       alias = g_strdup(dev->name);
+       } else
+               alias = g_strdup(dev->alias);
+
+       emit_device_found(adapter->path, paddr,
+                       "Address", DBUS_TYPE_STRING, &paddr,
+                       "Class", DBUS_TYPE_UINT32, &dev->class,
+                       "Icon", DBUS_TYPE_STRING, &icon,
+                       "RSSI", DBUS_TYPE_INT16, &rssi,
+                       "Name", DBUS_TYPE_STRING, &dev->name,
+                       "Alias", DBUS_TYPE_STRING, &alias,
+                       "LegacyPairing", DBUS_TYPE_BOOLEAN, &dev->legacy,
+                       "Paired", DBUS_TYPE_BOOLEAN, &paired,
+                       "UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, uuid_count,
+                       NULL);
+
+       g_free(alias);
+}
+
+static struct remote_dev_info *get_found_dev(struct btd_adapter *adapter,
+                                               const bdaddr_t *bdaddr,
+                                               gboolean *new_dev)
+{
+       struct remote_dev_info *dev, match;
+
+       memset(&match, 0, sizeof(struct remote_dev_info));
+       bacpy(&match.bdaddr, bdaddr);
+       match.name_status = NAME_ANY;
+
+       dev = adapter_search_found_devices(adapter, &match);
+       if (dev) {
+               *new_dev = FALSE;
+               /* Out of range list update */
+               adapter->oor_devices = g_slist_remove(adapter->oor_devices,
+                                                       dev);
+       } else {
+               *new_dev = TRUE;
+               dev = g_new0(struct remote_dev_info, 1);
+               bacpy(&dev->bdaddr, bdaddr);
+               adapter->found_devices = g_slist_prepend(adapter->found_devices,
+                                                                       dev);
+       }
+
+       return dev;
+}
+
+static void remove_same_uuid(gpointer data, gpointer user_data)
+{
+       struct remote_dev_info *dev = user_data;
+       GSList *l;
+
+       for (l = dev->services; l; l = l->next) {
+               char *current_uuid = l->data;
+               char *new_uuid = data;
+
+               if (strcmp(current_uuid, new_uuid) == 0) {
+                       g_free(current_uuid);
+                       dev->services = g_slist_delete_link(dev->services, l);
+                       break;
+               }
+       }
+}
+
+static void dev_prepend_uuid(gpointer data, gpointer user_data)
+{
+       struct remote_dev_info *dev = user_data;
+       char *new_uuid = data;
+
+       dev->services = g_slist_prepend(dev->services, g_strdup(new_uuid));
+}
+
+void adapter_update_device_from_info(struct btd_adapter *adapter,
+                                       bdaddr_t bdaddr, int8_t rssi,
+                                       uint8_t evt_type, const char *name,
+                                       GSList *services, int flags)
+{
+       struct remote_dev_info *dev;
+       gboolean new_dev;
+
+       dev = get_found_dev(adapter, &bdaddr, &new_dev);
+
+       if (new_dev) {
+               dev->le = TRUE;
+               dev->evt_type = evt_type;
+       } else if (dev->rssi == rssi)
+               return;
+
+       dev->rssi = rssi;
+
+       adapter->found_devices = g_slist_sort(adapter->found_devices,
+                                               (GCompareFunc) dev_rssi_cmp);
+
+       g_slist_foreach(services, remove_same_uuid, dev);
+       g_slist_foreach(services, dev_prepend_uuid, dev);
+
+       if (flags >= 0)
+               dev->flags = flags;
+
+       if (name) {
+               g_free(dev->name);
+               dev->name = g_strdup(name);
+       }
+
+       /* FIXME: check if other information was changed before emitting the
+        * signal */
+       adapter_emit_device_found(adapter, dev);
+}
+
+void adapter_update_found_devices(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                               int8_t rssi, uint32_t class, const char *name,
+                               const char *alias, gboolean legacy,
+                               GSList *services, name_status_t name_status)
+{
+       struct remote_dev_info *dev;
+       gboolean new_dev;
+
+       dev = get_found_dev(adapter, bdaddr, &new_dev);
+
+       if (new_dev) {
+               if (name)
+                       dev->name = g_strdup(name);
+
+               if (alias)
+                       dev->alias = g_strdup(alias);
+
+               dev->le = FALSE;
+               dev->class = class;
+               dev->legacy = legacy;
+               dev->name_status = name_status;
+       } else if (dev->rssi == rssi)
+               return;
+
+       dev->rssi = rssi;
+
+       adapter->found_devices = g_slist_sort(adapter->found_devices,
+                                               (GCompareFunc) dev_rssi_cmp);
+
+       g_slist_foreach(services, remove_same_uuid, dev);
+       g_slist_foreach(services, dev_prepend_uuid, dev);
+
+       adapter_emit_device_found(adapter, dev);
+}
+
+int adapter_remove_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr)
+{
+       struct remote_dev_info *dev, match;
+
+       memset(&match, 0, sizeof(struct remote_dev_info));
+       bacpy(&match.bdaddr, bdaddr);
+
+       dev = adapter_search_found_devices(adapter, &match);
+       if (!dev)
+               return -1;
+
+       dev->name_status = NAME_NOT_REQUIRED;
+
+       return 0;
+}
+
+void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode)
+{
+       const gchar *path = adapter_get_path(adapter);
+       gboolean discoverable, pairable;
+
+       DBG("old 0x%02x new 0x%02x", adapter->scan_mode, scan_mode);
+#ifdef __TIZEN_PATCH__
+       gboolean limited;
+#endif
+
+       if (adapter->scan_mode == scan_mode)
+               return;
+
+       adapter_remove_discov_timeout(adapter);
+
+       switch (scan_mode) {
+       case SCAN_DISABLED:
+               adapter->mode = MODE_OFF;
+               discoverable = FALSE;
+               pairable = FALSE;
+#ifdef __TIZEN_PATCH__
+               limited = FALSE;
+#endif
+               break;
+       case SCAN_PAGE:
+               adapter->mode = MODE_CONNECTABLE;
+               discoverable = FALSE;
+               pairable = adapter->pairable;
+#ifdef __TIZEN_PATCH__
+               limited = FALSE;
+#endif
+               break;
+       case (SCAN_PAGE | SCAN_INQUIRY):
+               adapter->mode = MODE_DISCOVERABLE;
+               discoverable = TRUE;
+               pairable = adapter->pairable;
+               if (adapter->discov_timeout != 0)
+                       adapter_set_discov_timeout(adapter,
+                                               adapter->discov_timeout);
+               break;
+       case SCAN_INQUIRY:
+               /* Address the scenario where a low-level application like
+                * hciconfig changed the scan mode */
+               if (adapter->discov_timeout != 0)
+                       adapter_set_discov_timeout(adapter,
+                                               adapter->discov_timeout);
+
+               /* ignore, this event should not be sent */
+       default:
+               /* ignore, reserved */
+               return;
+       }
+
+       /* If page scanning gets toggled emit the Pairable property */
+       if ((adapter->scan_mode & SCAN_PAGE) != (scan_mode & SCAN_PAGE))
+               emit_property_changed(connection, adapter->path,
+                                       ADAPTER_INTERFACE, "Pairable",
+                                       DBUS_TYPE_BOOLEAN, &pairable);
+
+       if (!discoverable)
+               adapter_set_limited_discoverable(adapter, FALSE);
+
+       emit_property_changed(connection, path,
+                               ADAPTER_INTERFACE, "Discoverable",
+                               DBUS_TYPE_BOOLEAN, &discoverable);
+
+       adapter->scan_mode = scan_mode;
+
+       set_mode_complete(adapter);
+}
+
+struct agent *adapter_get_agent(struct btd_adapter *adapter)
+{
+       if (!adapter || !adapter->agent)
+               return NULL;
+
+       return adapter->agent;
+}
+
+void adapter_add_connection(struct btd_adapter *adapter,
+                                               struct btd_device *device)
+{
+       if (g_slist_find(adapter->connections, device)) {
+               error("Device is already marked as connected");
+               return;
+       }
+
+       device_add_connection(device, connection);
+
+       adapter->connections = g_slist_append(adapter->connections, device);
+}
+
+void adapter_remove_connection(struct btd_adapter *adapter,
+                                               struct btd_device *device)
+{
+       bdaddr_t bdaddr;
+
+       DBG("");
+
+       if (!g_slist_find(adapter->connections, device)) {
+               error("No matching connection for device");
+               return;
+       }
+
+       device_remove_connection(device, connection);
+
+       adapter->connections = g_slist_remove(adapter->connections, device);
+
+       /* clean pending HCI cmds */
+       device_get_address(device, &bdaddr);
+
+       if (device_is_authenticating(device))
+               device_cancel_authentication(device, TRUE);
+
+       if (device_is_temporary(device)) {
+               const char *path = device_get_path(device);
+
+               DBG("Removing temporary device %s", path);
+               adapter_remove_device(connection, adapter, device, TRUE);
+       }
+}
+
+gboolean adapter_has_discov_sessions(struct btd_adapter *adapter)
+{
+       if (!adapter || !adapter->disc_sessions)
+               return FALSE;
+
+       return TRUE;
+}
+
+void adapter_suspend_discovery(struct btd_adapter *adapter)
+{
+       if (adapter->disc_sessions == NULL ||
+                       adapter->state & STATE_SUSPENDED)
+               return;
+
+       DBG("Suspending discovery");
+
+       stop_discovery(adapter, TRUE);
+       adapter->state |= STATE_SUSPENDED;
+}
+
+void adapter_resume_discovery(struct btd_adapter *adapter)
+{
+       if (adapter->disc_sessions == NULL)
+               return;
+
+       DBG("Resuming discovery");
+
+       adapter->state &= ~STATE_SUSPENDED;
+       start_discovery(adapter);
+}
+
+int btd_register_adapter_driver(struct btd_adapter_driver *driver)
+{
+       adapter_drivers = g_slist_append(adapter_drivers, driver);
+
+       if (driver->probe == NULL)
+               return 0;
+
+       manager_foreach_adapter(probe_driver, driver);
+
+       return 0;
+}
+
+static void unload_driver(struct btd_adapter *adapter, gpointer data)
+{
+       adapter->loaded_drivers = g_slist_remove(adapter->loaded_drivers, data);
+}
+
+void btd_unregister_adapter_driver(struct btd_adapter_driver *driver)
+{
+       adapter_drivers = g_slist_remove(adapter_drivers, driver);
+
+       manager_foreach_adapter(unload_driver, driver);
+}
+
+static void agent_auth_cb(struct agent *agent, DBusError *derr,
+                                                       void *user_data)
+{
+       struct service_auth *auth = user_data;
+
+       device_set_authorizing(auth->device, FALSE);
+
+       auth->cb(derr, auth->user_data);
+}
+
+static gboolean auth_idle_cb(gpointer user_data)
+{
+       struct service_auth *auth = user_data;
+       struct btd_adapter *adapter = auth->adapter;
+
+       adapter->auth_idle_id = 0;
+
+       auth->cb(NULL, auth->user_data);
+
+       return FALSE;
+}
+
+static int adapter_authorize(struct btd_adapter *adapter, const bdaddr_t *dst,
+                                       const char *uuid, service_auth_cb cb,
+                                       void *user_data)
+{
+       struct service_auth *auth;
+       struct btd_device *device;
+       struct agent *agent;
+       char address[18];
+       const gchar *dev_path;
+       int err;
+
+       ba2str(dst, address);
+       device = adapter_find_device(adapter, address);
+       if (!device)
+               return -EPERM;
+
+       /* Device connected? */
+       if (!g_slist_find(adapter->connections, device))
+               return -ENOTCONN;
+
+       if (adapter->auth_idle_id)
+               return -EBUSY;
+
+       auth = g_try_new0(struct service_auth, 1);
+       if (!auth)
+               return -ENOMEM;
+
+       auth->cb = cb;
+       auth->user_data = user_data;
+       auth->device = device;
+       auth->adapter = adapter;
+
+       if (device_is_trusted(device) == TRUE) {
+               adapter->auth_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
+                                                       auth_idle_cb, auth,
+                                                       g_free);
+               return 0;
+       }
+
+       agent = device_get_agent(device);
+       if (!agent) {
+               g_free(auth);
+               return -EPERM;
+       }
+
+       dev_path = device_get_path(device);
+
+       err = agent_authorize(agent, dev_path, uuid, agent_auth_cb, auth, g_free);
+       if (err < 0)
+               g_free(auth);
+       else
+               device_set_authorizing(device, TRUE);
+
+       return err;
+}
+
+int btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst,
+                                       const char *uuid, service_auth_cb cb,
+                                       void *user_data)
+{
+       struct btd_adapter *adapter;
+       GSList *l;
+
+       if (bacmp(src, BDADDR_ANY) != 0) {
+               adapter = manager_find_adapter(src);
+               if (!adapter)
+                       return -EPERM;
+
+               return adapter_authorize(adapter, dst, uuid, cb, user_data);
+       }
+
+       for (l = manager_get_adapters(); l != NULL; l = g_slist_next(l)) {
+               int err;
+
+               adapter = l->data;
+
+               err = adapter_authorize(adapter, dst, uuid, cb, user_data);
+               if (err == 0)
+                       return 0;
+       }
+
+       return -EPERM;
+}
+
+int btd_cancel_authorization(const bdaddr_t *src, const bdaddr_t *dst)
+{
+       struct btd_adapter *adapter = manager_find_adapter(src);
+       struct btd_device *device;
+       struct agent *agent;
+       char address[18];
+       int err;
+
+       if (!adapter)
+               return -EPERM;
+
+       ba2str(dst, address);
+       device = adapter_find_device(adapter, address);
+       if (!device)
+               return -EPERM;
+
+       if (adapter->auth_idle_id) {
+               g_source_remove(adapter->auth_idle_id);
+               adapter->auth_idle_id = 0;
+               return 0;
+       }
+
+       /*
+        * FIXME: Cancel fails if authorization is requested to adapter's
+        * agent and in the meanwhile CreatePairedDevice is called.
+        */
+
+       agent = device_get_agent(device);
+       if (!agent)
+               return -EPERM;
+
+       err = agent_cancel(agent);
+
+       if (err == 0)
+               device_set_authorizing(device, FALSE);
+
+       return err;
+}
+
+static gchar *adapter_any_path = NULL;
+static int adapter_any_refcount = 0;
+
+const char *adapter_any_get_path(void)
+{
+       return adapter_any_path;
+}
+
+const char *btd_adapter_any_request_path(void)
+{
+       if (adapter_any_refcount++ > 0)
+               return adapter_any_path;
+
+       adapter_any_path = g_strdup_printf("%s/any", manager_get_base_path());
+
+       return adapter_any_path;
+}
+
+void btd_adapter_any_release_path(void)
+{
+       adapter_any_refcount--;
+
+       if (adapter_any_refcount > 0)
+               return;
+
+       g_free(adapter_any_path);
+       adapter_any_path = NULL;
+}
+
+gboolean adapter_is_pairable(struct btd_adapter *adapter)
+{
+       return adapter->pairable;
+}
+
+gboolean adapter_powering_down(struct btd_adapter *adapter)
+{
+       return adapter->off_requested;
+}
+
+int btd_adapter_restore_powered(struct btd_adapter *adapter)
+{
+       char mode[14], address[18];
+       gboolean discoverable;
+
+       if (!adapter_ops)
+               return -EINVAL;
+
+       if (!main_opts.remember_powered)
+               return -EINVAL;
+
+       if (adapter->up)
+               return 0;
+
+       ba2str(&adapter->bdaddr, address);
+       if (read_device_mode(address, mode, sizeof(mode)) == 0 &&
+                                               g_str_equal(mode, "off"))
+               return 0;
+
+       discoverable = get_mode(&adapter->bdaddr, mode) == MODE_DISCOVERABLE;
+
+       return adapter_ops->set_powered(adapter->dev_id, TRUE);
+}
+
+int btd_adapter_switch_online(struct btd_adapter *adapter)
+{
+       if (!adapter_ops)
+               return -EINVAL;
+
+       if (adapter->up)
+               return 0;
+
+       return adapter_ops->set_powered(adapter->dev_id, TRUE);
+}
+
+int btd_adapter_switch_offline(struct btd_adapter *adapter)
+{
+       if (!adapter_ops)
+               return -EINVAL;
+
+       if (!adapter->up)
+               return 0;
+
+       return adapter_ops->set_powered(adapter->dev_id, FALSE);
+}
+
+int btd_register_adapter_ops(struct btd_adapter_ops *ops, gboolean priority)
+{
+       if (ops->setup == NULL)
+               return -EINVAL;
+
+       if (priority)
+               ops_candidates = g_slist_prepend(ops_candidates, ops);
+       else
+               ops_candidates = g_slist_append(ops_candidates, ops);
+
+       return 0;
+}
+
+void btd_adapter_cleanup_ops(struct btd_adapter_ops *ops)
+{
+       ops_candidates = g_slist_remove(ops_candidates, ops);
+       ops->cleanup();
+
+       if (adapter_ops == ops)
+               adapter_ops = NULL;
+}
+
+int adapter_ops_setup(void)
+{
+       GSList *l;
+       int ret;
+
+       if (!ops_candidates)
+               return -EINVAL;
+
+       for (l = ops_candidates; l != NULL; l = g_slist_next(l)) {
+               struct btd_adapter_ops *ops = l->data;
+
+               ret = ops->setup();
+               if (ret < 0)
+                       continue;
+
+               adapter_ops = ops;
+               break;
+       }
+
+       return ret;
+}
+
+void btd_adapter_register_powered_callback(struct btd_adapter *adapter,
+                                               btd_adapter_powered_cb cb)
+{
+       adapter->powered_callbacks =
+                       g_slist_append(adapter->powered_callbacks, cb);
+}
+
+void btd_adapter_unregister_powered_callback(struct btd_adapter *adapter,
+                                               btd_adapter_powered_cb cb)
+{
+       adapter->powered_callbacks =
+                       g_slist_remove(adapter->powered_callbacks, cb);
+}
+
+int btd_adapter_set_fast_connectable(struct btd_adapter *adapter,
+                                                       gboolean enable)
+{
+       if (!adapter_ops)
+               return -EINVAL;
+
+       if (!adapter->up)
+               return -EINVAL;
+
+       return adapter_ops->set_fast_connectable(adapter->dev_id, enable);
+}
+
+int btd_adapter_read_clock(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                               int which, int timeout, uint32_t *clock,
+                               uint16_t *accuracy)
+{
+       if (!adapter_ops)
+               return -EINVAL;
+
+       if (!adapter->up)
+               return -EINVAL;
+
+       return adapter_ops->read_clock(adapter->dev_id, bdaddr, which,
+                                               timeout, clock, accuracy);
+}
+
+int btd_adapter_disconnect_device(struct btd_adapter *adapter, bdaddr_t *bdaddr)
+{
+       return adapter_ops->disconnect(adapter->dev_id, bdaddr);
+}
+
+int btd_adapter_remove_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr)
+{
+       return adapter_ops->remove_bonding(adapter->dev_id, bdaddr);
+}
+
+int btd_adapter_pincode_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                                       const char *pin)
+{
+       return adapter_ops->pincode_reply(adapter->dev_id, bdaddr, pin);
+}
+
+int btd_adapter_confirm_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                                       gboolean success)
+{
+       return adapter_ops->confirm_reply(adapter->dev_id, bdaddr, success);
+}
+
+int btd_adapter_passkey_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                                       uint32_t passkey)
+{
+       return adapter_ops->passkey_reply(adapter->dev_id, bdaddr, passkey);
+}
+
+void btd_adapter_update_local_ext_features(struct btd_adapter *adapter,
+                                               const uint8_t *features)
+{
+       struct hci_dev *dev = &adapter->dev;
+
+       memcpy(dev->extfeatures, features, 8);
+}
+
+int btd_adapter_encrypt_link(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                       bt_hci_result_t cb, gpointer user_data)
+{
+       return adapter_ops->encrypt_link(adapter->dev_id, bdaddr, cb, user_data);
+}
+
+int btd_adapter_set_did(struct btd_adapter *adapter, uint16_t vendor,
+                                       uint16_t product, uint16_t version)
+{
+       return adapter_ops->set_did(adapter->dev_id, vendor, product, version);
+}
+
+int adapter_create_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                                               uint8_t io_cap)
+{
+       return adapter_ops->create_bonding(adapter->dev_id, bdaddr, io_cap);
+}
+
+int adapter_cancel_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr)
+{
+       return adapter_ops->cancel_bonding(adapter->dev_id, bdaddr);
+}
diff --git a/src/adapter.h b/src/adapter.h
new file mode 100644 (file)
index 0000000..ba8c52a
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#define ADAPTER_INTERFACE      "org.bluez.Adapter"
+
+#define MODE_OFF               0x00
+#define MODE_CONNECTABLE       0x01
+#define MODE_DISCOVERABLE      0x02
+#define MODE_UNKNOWN           0xff
+
+/* Discover states */
+#define STATE_IDLE             0x00
+#define STATE_LE_SCAN          0x01
+#define STATE_STDINQ           0x02
+#define STATE_PINQ             0x04
+#define STATE_RESOLVNAME       0x08
+#define STATE_SUSPENDED                0x10
+
+/* Supported host/controller discover type */
+#define DISC_LE                        0x01
+#define DISC_STDINQ            0x02
+#define DISC_INTERLEAVE                0x04
+#define DISC_PINQ              0x08
+#define DISC_RESOLVNAME                0x10
+
+#define MAX_NAME_LENGTH                248
+
+/* Invalid SSP passkey value used to indicate negative replies */
+#define INVALID_PASSKEY                0xffffffff
+
+typedef enum {
+       NAME_ANY,
+       NAME_NOT_REQUIRED, /* used by get remote name without name resolving */
+       NAME_REQUIRED,      /* remote name needs be resolved       */
+       NAME_REQUESTED,    /* HCI remote name request was sent    */
+} name_status_t;
+
+struct btd_adapter;
+
+struct link_key_info {
+       bdaddr_t bdaddr;
+       unsigned char key[16];
+       uint8_t type;
+       uint8_t pin_len;
+};
+
+struct remote_dev_info {
+       bdaddr_t bdaddr;
+       int8_t rssi;
+       uint32_t class;
+       char *name;
+       char *alias;
+       dbus_bool_t legacy;
+       name_status_t name_status;
+       gboolean le;
+       char **uuids;
+       size_t uuid_count;
+       GSList *services;
+       uint8_t evt_type;
+       uint8_t bdaddr_type;
+       uint8_t flags;
+};
+
+struct hci_dev {
+       uint8_t  features[8];
+       uint8_t  extfeatures[8];
+       uint8_t  lmp_ver;
+       uint16_t lmp_subver;
+       uint16_t hci_rev;
+       uint16_t manufacturer;
+
+       uint8_t  ssp_mode;
+       char     name[MAX_NAME_LENGTH + 1];
+};
+
+#ifdef __TIZEN_PATCH__
+#ifndef MAX_REMOTE_SERVICES
+       #define MAX_REMOTE_SERVICES 0x14
+#endif
+typedef struct
+{
+       DBusMessage *msg;
+       DBusConnection* conn;
+#if 0 //Service search
+#else
+       DBusMessageIter* array_iter;
+#endif
+}service_dbus_ctxt_t;
+
+#endif
+void btd_adapter_start(struct btd_adapter *adapter);
+
+int btd_adapter_stop(struct btd_adapter *adapter);
+
+void btd_adapter_get_mode(struct btd_adapter *adapter, uint8_t *mode,
+                                       uint8_t *on_mode, gboolean *pairable);
+
+int adapter_update_ssp_mode(struct btd_adapter *adapter, uint8_t mode);
+
+struct btd_device *adapter_get_device(DBusConnection *conn,
+                               struct btd_adapter *adapter, const char *address);
+
+struct btd_device *adapter_find_device(struct btd_adapter *adapter, const char *dest);
+
+void adapter_remove_device(DBusConnection *conn, struct btd_adapter *adapter,
+                                               struct btd_device *device,
+                                               gboolean remove_storage);
+
+int adapter_resolve_names(struct btd_adapter *adapter);
+
+struct btd_adapter *adapter_create(DBusConnection *conn, int id);
+gboolean adapter_init(struct btd_adapter *adapter);
+void adapter_remove(struct btd_adapter *adapter);
+uint16_t adapter_get_dev_id(struct btd_adapter *adapter);
+const gchar *adapter_get_path(struct btd_adapter *adapter);
+void adapter_get_address(struct btd_adapter *adapter, bdaddr_t *bdaddr);
+void adapter_set_state(struct btd_adapter *adapter, int state);
+int adapter_get_state(struct btd_adapter *adapter);
+int adapter_get_discover_type(struct btd_adapter *adapter);
+struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter,
+                                               struct remote_dev_info *match);
+void adapter_update_device_from_info(struct btd_adapter *adapter,
+                                       bdaddr_t bdaddr, int8_t rssi,
+                                       uint8_t evt_type, const char *name,
+                                       GSList *services, int flags);
+void adapter_update_found_devices(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                               int8_t rssi, uint32_t class, const char *name,
+                               const char *alias, gboolean legacy,
+                               GSList *services, name_status_t name_status);
+int adapter_remove_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr);
+void adapter_emit_device_found(struct btd_adapter *adapter,
+                                               struct remote_dev_info *dev);
+void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode);
+void adapter_update_local_name(struct btd_adapter *adapter, const char *name);
+void adapter_service_insert(struct btd_adapter *adapter, void *rec);
+void adapter_service_remove(struct btd_adapter *adapter, void *rec);
+void btd_adapter_class_changed(struct btd_adapter *adapter,
+                                                       uint32_t new_class);
+void btd_adapter_pairable_changed(struct btd_adapter *adapter,
+                                                       gboolean pairable);
+
+struct agent *adapter_get_agent(struct btd_adapter *adapter);
+void adapter_add_connection(struct btd_adapter *adapter,
+                                               struct btd_device *device);
+void adapter_remove_connection(struct btd_adapter *adapter,
+                                               struct btd_device *device);
+gboolean adapter_has_discov_sessions(struct btd_adapter *adapter);
+void adapter_suspend_discovery(struct btd_adapter *adapter);
+void adapter_resume_discovery(struct btd_adapter *adapter);
+
+struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter);
+void btd_adapter_unref(struct btd_adapter *adapter);
+
+int btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major,
+                                                       uint8_t minor);
+
+struct btd_adapter_driver {
+       const char *name;
+       int (*probe) (struct btd_adapter *adapter);
+       void (*remove) (struct btd_adapter *adapter);
+};
+
+typedef void (*service_auth_cb) (DBusError *derr, void *user_data);
+
+int btd_register_adapter_driver(struct btd_adapter_driver *driver);
+void btd_unregister_adapter_driver(struct btd_adapter_driver *driver);
+int btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst,
+               const char *uuid, service_auth_cb cb, void *user_data);
+int btd_cancel_authorization(const bdaddr_t *src, const bdaddr_t *dst);
+
+const char *adapter_any_get_path(void);
+
+const char *btd_adapter_any_request_path(void);
+void btd_adapter_any_release_path(void);
+gboolean adapter_is_pairable(struct btd_adapter *adapter);
+gboolean adapter_powering_down(struct btd_adapter *adapter);
+
+int btd_adapter_restore_powered(struct btd_adapter *adapter);
+int btd_adapter_switch_online(struct btd_adapter *adapter);
+int btd_adapter_switch_offline(struct btd_adapter *adapter);
+
+typedef void (*bt_hci_result_t) (uint8_t status, gpointer user_data);
+
+struct btd_adapter_ops {
+       int (*setup) (void);
+       void (*cleanup) (void);
+       int (*set_powered) (int index, gboolean powered);
+       int (*set_discoverable) (int index, gboolean discoverable);
+       int (*set_pairable) (int index, gboolean pairable);
+       int (*set_limited_discoverable) (int index, gboolean limited);
+       int (*start_inquiry) (int index, uint8_t length, gboolean periodic);
+       int (*stop_inquiry) (int index);
+       int (*start_scanning) (int index);
+       int (*stop_scanning) (int index);
+
+       int (*resolve_name) (int index, bdaddr_t *bdaddr);
+       int (*cancel_resolve_name) (int index, bdaddr_t *bdaddr);
+       int (*set_name) (int index, const char *name);
+       int (*set_dev_class) (int index, uint8_t major, uint8_t minor);
+       int (*set_fast_connectable) (int index, gboolean enable);
+       int (*read_clock) (int index, bdaddr_t *bdaddr, int which, int timeout,
+                                       uint32_t *clock, uint16_t *accuracy);
+       int (*read_bdaddr) (int index, bdaddr_t *bdaddr);
+       int (*block_device) (int index, bdaddr_t *bdaddr);
+       int (*unblock_device) (int index, bdaddr_t *bdaddr);
+       int (*get_conn_list) (int index, GSList **conns);
+       int (*read_local_version) (int index, struct hci_version *ver);
+       int (*read_local_features) (int index, uint8_t *features);
+       int (*disconnect) (int index, bdaddr_t *bdaddr);
+       int (*remove_bonding) (int index, bdaddr_t *bdaddr);
+       int (*pincode_reply) (int index, bdaddr_t *bdaddr, const char *pin);
+       int (*confirm_reply) (int index, bdaddr_t *bdaddr, gboolean success);
+       int (*passkey_reply) (int index, bdaddr_t *bdaddr, uint32_t passkey);
+       int (*enable_le) (int index);
+       int (*encrypt_link) (int index, bdaddr_t *bdaddr, bt_hci_result_t cb,
+                                                       gpointer user_data);
+       int (*set_did) (int index, uint16_t vendor, uint16_t product,
+                                                       uint16_t version);
+       int (*add_uuid) (int index, uuid_t *uuid, uint8_t svc_hint);
+       int (*remove_uuid) (int index, uuid_t *uuid);
+       int (*disable_cod_cache) (int index);
+       int (*restore_powered) (int index);
+       int (*load_keys) (int index, GSList *keys, gboolean debug_keys);
+       int (*set_io_capability) (int index, uint8_t io_capability);
+       int (*create_bonding) (int index, bdaddr_t *bdaddr, uint8_t io_cap);
+       int (*cancel_bonding) (int index, bdaddr_t *bdaddr);
+};
+
+int btd_register_adapter_ops(struct btd_adapter_ops *ops, gboolean priority);
+void btd_adapter_cleanup_ops(struct btd_adapter_ops *btd_adapter_ops);
+int adapter_ops_setup(void);
+
+typedef void (*btd_adapter_powered_cb) (struct btd_adapter *adapter,
+                                               gboolean powered);
+void btd_adapter_register_powered_callback(struct btd_adapter *adapter,
+                                               btd_adapter_powered_cb cb);
+void btd_adapter_unregister_powered_callback(struct btd_adapter *adapter,
+                                               btd_adapter_powered_cb cb);
+
+/* If TRUE, enables fast connectabe, i.e. reduces page scan interval and changes
+ * type. If FALSE, disables fast connectable, i.e. sets page scan interval and
+ * type to default values. Valid for both connectable and discoverable modes. */
+int btd_adapter_set_fast_connectable(struct btd_adapter *adapter,
+                                                       gboolean enable);
+
+int btd_adapter_read_clock(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                               int which, int timeout, uint32_t *clock,
+                               uint16_t *accuracy);
+
+int btd_adapter_block_address(struct btd_adapter *adapter, bdaddr_t *bdaddr);
+int btd_adapter_unblock_address(struct btd_adapter *adapter, bdaddr_t *bdaddr);
+
+int btd_adapter_disconnect_device(struct btd_adapter *adapter,
+                                                       bdaddr_t *bdaddr);
+
+int btd_adapter_remove_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr);
+
+int btd_adapter_pincode_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                                       const char *pin);
+int btd_adapter_confirm_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                                       gboolean success);
+int btd_adapter_passkey_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                                       uint32_t passkey);
+
+void btd_adapter_update_local_ext_features(struct btd_adapter *adapter,
+                                               const uint8_t *features);
+
+int btd_adapter_encrypt_link(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                               bt_hci_result_t cb, gpointer user_data);
+
+int btd_adapter_set_did(struct btd_adapter *adapter, uint16_t vendor,
+                                       uint16_t product, uint16_t version);
+
+int adapter_create_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr,
+                                                       uint8_t io_cap);
+
+int adapter_cancel_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr);
+
+#ifdef __TIZEN_PATCH__
+sdp_list_t *adapter_get_services(struct btd_adapter *adapter);
+#endif
diff --git a/src/agent.c b/src/agent.c
new file mode 100644 (file)
index 0000000..377abe7
--- /dev/null
@@ -0,0 +1,815 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+
+#include "hcid.h"
+#include "adapter.h"
+#include "device.h"
+#include "agent.h"
+
+#define REQUEST_TIMEOUT (60 * 1000)            /* 60 seconds */
+
+typedef enum {
+       AGENT_REQUEST_PASSKEY,
+       AGENT_REQUEST_CONFIRMATION,
+       AGENT_REQUEST_PINCODE,
+       AGENT_REQUEST_AUTHORIZE,
+       AGENT_REQUEST_CONFIRM_MODE
+} agent_request_type_t;
+
+struct agent {
+       struct btd_adapter *adapter;
+       char *name;
+       char *path;
+       uint8_t capability;
+       struct agent_request *request;
+       int exited;
+       agent_remove_cb remove_cb;
+       void *remove_cb_data;
+       guint listener_id;
+};
+
+struct agent_request {
+       agent_request_type_t type;
+       struct agent *agent;
+       DBusMessage *msg;
+       DBusPendingCall *call;
+       void *cb;
+       void *user_data;
+       GDestroyNotify destroy;
+};
+
+static DBusConnection *connection = NULL;
+
+static int request_fallback(struct agent_request *req,
+                               DBusPendingCallNotifyFunction function);
+
+static void agent_release(struct agent *agent)
+{
+       DBusMessage *message;
+
+       DBG("Releasing agent %s, %s", agent->name, agent->path);
+
+       if (agent->request)
+               agent_cancel(agent);
+
+       message = dbus_message_new_method_call(agent->name, agent->path,
+                       "org.bluez.Agent", "Release");
+       if (message == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return;
+       }
+
+       g_dbus_send_message(connection, message);
+}
+
+static int send_cancel_request(struct agent_request *req)
+{
+       DBusMessage *message;
+
+       DBG("Sending Cancel request to %s, %s", req->agent->name,
+                                                       req->agent->path);
+
+       message = dbus_message_new_method_call(req->agent->name, req->agent->path,
+                                               "org.bluez.Agent", "Cancel");
+       if (message == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return -ENOMEM;
+       }
+
+       g_dbus_send_message(connection, message);
+
+       return 0;
+}
+
+static void agent_request_free(struct agent_request *req, gboolean destroy)
+{
+       if (req->msg)
+               dbus_message_unref(req->msg);
+       if (req->call)
+               dbus_pending_call_unref(req->call);
+       if (req->agent && req->agent->request)
+               req->agent->request = NULL;
+       if (destroy && req->destroy)
+               req->destroy(req->user_data);
+       g_free(req);
+}
+
+static void agent_exited(DBusConnection *conn, void *user_data)
+{
+       struct agent *agent = user_data;
+
+       DBG("Agent exited without calling Unregister");
+
+       agent->exited = TRUE;
+
+       agent_free(agent);
+}
+
+void agent_free(struct agent *agent)
+{
+       if (!agent)
+               return;
+
+       if (agent->remove_cb)
+               agent->remove_cb(agent, agent->remove_cb_data);
+
+       if (agent->request) {
+               DBusError err;
+               agent_pincode_cb pincode_cb;
+               agent_cb cb;
+
+               dbus_error_init(&err);
+               dbus_set_error_const(&err, "org.bluez.Error.Failed", "Canceled");
+
+               switch (agent->request->type) {
+               case AGENT_REQUEST_PINCODE:
+                       pincode_cb = agent->request->cb;
+                       pincode_cb(agent, &err, NULL, agent->request->user_data);
+                       break;
+               default:
+                       cb = agent->request->cb;
+                       cb(agent, &err, agent->request->user_data);
+               }
+
+               dbus_error_free(&err);
+
+               agent_cancel(agent);
+       }
+
+       if (!agent->exited) {
+               g_dbus_remove_watch(connection, agent->listener_id);
+               agent_release(agent);
+       }
+
+       g_free(agent->name);
+       g_free(agent->path);
+
+       g_free(agent);
+}
+
+struct agent *agent_create(struct btd_adapter *adapter, const char *name,
+                               const char *path, uint8_t capability,
+                               agent_remove_cb cb, void *remove_cb_data)
+{
+       struct agent *agent;
+
+       agent = g_new0(struct agent, 1);
+
+       agent->adapter = adapter;
+       agent->name = g_strdup(name);
+       agent->path = g_strdup(path);
+       agent->capability = capability;
+       agent->remove_cb = cb;
+       agent->remove_cb_data = remove_cb_data;
+
+       agent->listener_id = g_dbus_add_disconnect_watch(connection, name,
+                                                       agent_exited, agent,
+                                                       NULL);
+
+       return agent;
+}
+
+static struct agent_request *agent_request_new(struct agent *agent,
+                                               agent_request_type_t type,
+                                               void *cb,
+                                               void *user_data,
+                                               GDestroyNotify destroy)
+{
+       struct agent_request *req;
+
+       req = g_new0(struct agent_request, 1);
+
+       req->agent = agent;
+       req->type = type;
+       req->cb = cb;
+       req->user_data = user_data;
+       req->destroy = destroy;
+
+       return req;
+}
+
+int agent_cancel(struct agent *agent)
+{
+       if (!agent->request)
+               return -EINVAL;
+
+       if (agent->request->call)
+               dbus_pending_call_cancel(agent->request->call);
+
+       if (!agent->exited)
+               send_cancel_request(agent->request);
+
+       agent_request_free(agent->request, TRUE);
+       agent->request = NULL;
+
+       return 0;
+}
+
+static void simple_agent_reply(DBusPendingCall *call, void *user_data)
+{
+       struct agent_request *req = user_data;
+       struct agent *agent = req->agent;
+       DBusMessage *message;
+       DBusError err;
+       agent_cb cb = req->cb;
+
+       /* steal_reply will always return non-NULL since the callback
+        * is only called after a reply has been received */
+       message = dbus_pending_call_steal_reply(call);
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, message)) {
+               if ((g_str_equal(DBUS_ERROR_UNKNOWN_METHOD, err.name) ||
+                               g_str_equal(DBUS_ERROR_NO_REPLY, err.name)) &&
+                               request_fallback(req, simple_agent_reply) == 0) {
+                       dbus_error_free(&err);
+                       return;
+               }
+
+               error("Agent replied with an error: %s, %s",
+                               err.name, err.message);
+
+#ifdef __TIZEN_PATCH__
+               if (strcmp(err.message, "CanceledbyUser") == 0)
+               {
+                       set_cancel_from_authentication_req(req->user_data);
+               }
+#endif
+               cb(agent, &err, req->user_data);
+
+               if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) {
+                       agent_cancel(agent);
+                       dbus_message_unref(message);
+                       dbus_error_free(&err);
+                       return;
+               }
+
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       dbus_error_init(&err);
+       if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) {
+               error("Wrong reply signature: %s", err.message);
+               cb(agent, &err, req->user_data);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       cb(agent, NULL, req->user_data);
+done:
+       dbus_message_unref(message);
+
+       agent->request = NULL;
+       agent_request_free(req, TRUE);
+}
+
+static int agent_call_authorize(struct agent_request *req,
+                               const char *device_path,
+                               const char *uuid)
+{
+       struct agent *agent = req->agent;
+
+       req->msg = dbus_message_new_method_call(agent->name, agent->path,
+                               "org.bluez.Agent", "Authorize");
+       if (!req->msg) {
+               error("Couldn't allocate D-Bus message");
+               return -ENOMEM;
+       }
+
+       dbus_message_append_args(req->msg,
+                               DBUS_TYPE_OBJECT_PATH, &device_path,
+                               DBUS_TYPE_STRING, &uuid,
+                               DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(connection, req->msg,
+                                       &req->call, REQUEST_TIMEOUT) == FALSE) {
+               error("D-Bus send failed");
+               return -EIO;
+       }
+
+       dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);
+       return 0;
+}
+
+int agent_authorize(struct agent *agent,
+                       const char *path,
+                       const char *uuid,
+                       agent_cb cb,
+                       void *user_data,
+                       GDestroyNotify destroy)
+{
+       struct agent_request *req;
+       int err;
+
+       info("agent_authorize");
+       if (agent->request)
+               return -EBUSY;
+
+       req = agent_request_new(agent, AGENT_REQUEST_AUTHORIZE, cb,
+                                                       user_data, destroy);
+
+       err = agent_call_authorize(req, path, uuid);
+       if (err < 0) {
+               agent_request_free(req, FALSE);
+               return -ENOMEM;
+       }
+
+       agent->request = req;
+
+       DBG("authorize request was sent for %s", path);
+
+       return 0;
+}
+
+static void pincode_reply(DBusPendingCall *call, void *user_data)
+{
+       struct agent_request *req = user_data;
+       struct agent *agent = req->agent;
+       struct btd_adapter *adapter = agent->adapter;
+       agent_pincode_cb cb = req->cb;
+       DBusMessage *message;
+       DBusError err;
+       bdaddr_t sba;
+       size_t len;
+       char *pin;
+
+       adapter_get_address(adapter, &sba);
+
+       /* steal_reply will always return non-NULL since the callback
+        * is only called after a reply has been received */
+       message = dbus_pending_call_steal_reply(call);
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, message)) {
+               if ((g_str_equal(DBUS_ERROR_UNKNOWN_METHOD, err.name) ||
+                               g_str_equal(DBUS_ERROR_NO_REPLY, err.name)) &&
+                               request_fallback(req, pincode_reply) == 0) {
+                       dbus_error_free(&err);
+                       return;
+               }
+
+               error("Agent replied with an error: %s, %s",
+                               err.name, err.message);
+
+#ifdef __TIZEN_PATCH__
+               if (strcmp(err.message, "CanceledbyUser") == 0)
+               {
+                       set_cancel_from_authentication_req(req->user_data);
+               }
+#endif
+
+               cb(agent, &err, NULL, req->user_data);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       dbus_error_init(&err);
+       if (!dbus_message_get_args(message, &err,
+                               DBUS_TYPE_STRING, &pin,
+                               DBUS_TYPE_INVALID)) {
+               error("Wrong passkey reply signature: %s", err.message);
+               cb(agent, &err, NULL, req->user_data);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       len = strlen(pin);
+
+       dbus_error_init(&err);
+       if (len > 16 || len < 1) {
+               error("Invalid PIN length (%zu) from agent", len);
+               dbus_set_error_const(&err, "org.bluez.Error.InvalidArgs",
+                                       "Invalid passkey length");
+               cb(agent, &err, NULL, req->user_data);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       cb(agent, NULL, pin, req->user_data);
+
+done:
+       if (message)
+               dbus_message_unref(message);
+
+       dbus_pending_call_cancel(req->call);
+       agent->request = NULL;
+       agent_request_free(req, TRUE);
+}
+
+static int pincode_request_new(struct agent_request *req, const char *device_path,
+                               dbus_bool_t numeric)
+{
+       struct agent *agent = req->agent;
+
+       req->msg = dbus_message_new_method_call(agent->name, agent->path,
+                                       "org.bluez.Agent", "RequestPinCode");
+       if (req->msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return -ENOMEM;
+       }
+
+       dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path,
+                                       DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(connection, req->msg,
+                                       &req->call, REQUEST_TIMEOUT) == FALSE) {
+               error("D-Bus send failed");
+               return -EIO;
+       }
+
+       dbus_pending_call_set_notify(req->call, pincode_reply, req, NULL);
+       return 0;
+}
+
+int agent_request_pincode(struct agent *agent, struct btd_device *device,
+                               agent_pincode_cb cb, void *user_data,
+                               GDestroyNotify destroy)
+{
+       struct agent_request *req;
+       const gchar *dev_path = device_get_path(device);
+       int err;
+
+       if (agent->request)
+               return -EBUSY;
+
+       req = agent_request_new(agent, AGENT_REQUEST_PINCODE, cb,
+                                                       user_data, destroy);
+
+       err = pincode_request_new(req, dev_path, FALSE);
+       if (err < 0)
+               goto failed;
+
+       agent->request = req;
+
+       return 0;
+
+failed:
+       g_free(req);
+       return err;
+}
+
+static int confirm_mode_change_request_new(struct agent_request *req,
+                                               const char *mode)
+{
+       struct agent *agent = req->agent;
+
+       req->msg = dbus_message_new_method_call(agent->name, agent->path,
+                               "org.bluez.Agent", "ConfirmModeChange");
+       if (req->msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return -ENOMEM;
+       }
+
+       dbus_message_append_args(req->msg,
+                               DBUS_TYPE_STRING, &mode,
+                               DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(connection, req->msg,
+                                       &req->call, REQUEST_TIMEOUT) == FALSE) {
+               error("D-Bus send failed");
+               return -EIO;
+       }
+
+       dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);
+       return 0;
+}
+
+int agent_confirm_mode_change(struct agent *agent, const char *new_mode,
+                               agent_cb cb, void *user_data,
+                               GDestroyNotify destroy)
+{
+       struct agent_request *req;
+       int err;
+
+       if (agent->request)
+               return -EBUSY;
+
+       DBG("Calling Agent.ConfirmModeChange: name=%s, path=%s, mode=%s",
+                       agent->name, agent->path, new_mode);
+
+       req = agent_request_new(agent, AGENT_REQUEST_CONFIRM_MODE,
+                               cb, user_data, destroy);
+
+       err = confirm_mode_change_request_new(req, new_mode);
+       if (err < 0)
+               goto failed;
+
+       agent->request = req;
+
+       return 0;
+
+failed:
+       agent_request_free(req, FALSE);
+       return err;
+}
+
+static void passkey_reply(DBusPendingCall *call, void *user_data)
+{
+       struct agent_request *req = user_data;
+       struct agent *agent = req->agent;
+       agent_passkey_cb cb = req->cb;
+       DBusMessage *message;
+       DBusError err;
+       uint32_t passkey;
+
+       /* steal_reply will always return non-NULL since the callback
+        * is only called after a reply has been received */
+       message = dbus_pending_call_steal_reply(call);
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, message)) {
+               if ((g_str_equal(DBUS_ERROR_UNKNOWN_METHOD, err.name) ||
+                               g_str_equal(DBUS_ERROR_NO_REPLY, err.name)) &&
+                               request_fallback(req, passkey_reply) == 0) {
+                       dbus_error_free(&err);
+                       return;
+               }
+
+               error("Agent replied with an error: %s, %s",
+                               err.name, err.message);
+#ifdef __TIZEN_PATCH__
+               if (strcmp(err.message, "CanceledbyUser") == 0)
+               {
+                       set_cancel_from_authentication_req(req->user_data);
+               }
+#endif
+
+               cb(agent, &err, 0, req->user_data);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       dbus_error_init(&err);
+       if (!dbus_message_get_args(message, &err,
+                               DBUS_TYPE_UINT32, &passkey,
+                               DBUS_TYPE_INVALID)) {
+               error("Wrong passkey reply signature: %s", err.message);
+               cb(agent, &err, 0, req->user_data);
+               dbus_error_free(&err);
+               goto done;
+       }
+
+       cb(agent, NULL, passkey, req->user_data);
+
+done:
+       if (message)
+               dbus_message_unref(message);
+
+       dbus_pending_call_cancel(req->call);
+       agent->request = NULL;
+       agent_request_free(req, TRUE);
+}
+
+static int passkey_request_new(struct agent_request *req,
+                               const char *device_path)
+{
+       struct agent *agent = req->agent;
+
+       req->msg = dbus_message_new_method_call(agent->name, agent->path,
+                                       "org.bluez.Agent", "RequestPasskey");
+       if (req->msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return -ENOMEM;
+       }
+
+       dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path,
+                                       DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(connection, req->msg,
+                                       &req->call, REQUEST_TIMEOUT) == FALSE) {
+               error("D-Bus send failed");
+               return -EIO;
+       }
+
+       dbus_pending_call_set_notify(req->call, passkey_reply, req, NULL);
+       return 0;
+}
+
+int agent_request_passkey(struct agent *agent, struct btd_device *device,
+                               agent_passkey_cb cb, void *user_data,
+                               GDestroyNotify destroy)
+{
+       struct agent_request *req;
+       const gchar *dev_path = device_get_path(device);
+       int err;
+
+       if (agent->request)
+               return -EBUSY;
+
+       DBG("Calling Agent.RequestPasskey: name=%s, path=%s",
+                       agent->name, agent->path);
+
+       req = agent_request_new(agent, AGENT_REQUEST_PASSKEY, cb,
+                                                       user_data, destroy);
+
+       err = passkey_request_new(req, dev_path);
+       if (err < 0)
+               goto failed;
+
+       agent->request = req;
+
+       return 0;
+
+failed:
+       agent_request_free(req, FALSE);
+       return err;
+}
+
+static int confirmation_request_new(struct agent_request *req,
+                                       const char *device_path,
+                                       uint32_t passkey)
+{
+       struct agent *agent = req->agent;
+
+       req->msg = dbus_message_new_method_call(agent->name, agent->path,
+                               "org.bluez.Agent", "RequestConfirmation");
+       if (req->msg == NULL) {
+               error("Couldn't allocate D-Bus message");
+               return -ENOMEM;
+       }
+
+       dbus_message_append_args(req->msg,
+                               DBUS_TYPE_OBJECT_PATH, &device_path,
+                               DBUS_TYPE_UINT32, &passkey,
+                               DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(connection, req->msg,
+                               &req->call, REQUEST_TIMEOUT) == FALSE) {
+               error("D-Bus send failed");
+               return -EIO;
+       }
+
+       dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);
+
+       return 0;
+}
+
+int agent_request_confirmation(struct agent *agent, struct btd_device *device,
+                               uint32_t passkey, agent_cb cb,
+                               void *user_data, GDestroyNotify destroy)
+{
+       struct agent_request *req;
+       const gchar *dev_path = device_get_path(device);
+       int err;
+
+       if (agent->request)
+               return -EBUSY;
+
+       DBG("Calling Agent.RequestConfirmation: name=%s, path=%s, passkey=%06u",
+                       agent->name, agent->path, passkey);
+
+       req = agent_request_new(agent, AGENT_REQUEST_CONFIRMATION, cb,
+                               user_data, destroy);
+
+       err = confirmation_request_new(req, dev_path, passkey);
+       if (err < 0)
+               goto failed;
+
+       agent->request = req;
+
+       return 0;
+
+failed:
+       agent_request_free(req, FALSE);
+       return err;
+}
+
+static int request_fallback(struct agent_request *req,
+                               DBusPendingCallNotifyFunction function)
+{
+       struct btd_adapter *adapter = req->agent->adapter;
+       struct agent *adapter_agent = adapter_get_agent(adapter);
+       DBusMessage *msg;
+
+       if (req->agent == adapter_agent || adapter_agent == NULL)
+               return -EINVAL;
+
+       dbus_pending_call_cancel(req->call);
+       dbus_pending_call_unref(req->call);
+
+       msg = dbus_message_copy(req->msg);
+
+       dbus_message_set_destination(msg, adapter_agent->name);
+       dbus_message_set_path(msg, adapter_agent->path);
+
+       if (dbus_connection_send_with_reply(connection, msg,
+                                       &req->call, REQUEST_TIMEOUT) == FALSE) {
+               error("D-Bus send failed");
+               dbus_message_unref(msg);
+               return -EIO;
+       }
+
+       req->agent->request = NULL;
+       req->agent = adapter_agent;
+       req->agent->request = req;
+
+       dbus_message_unref(req->msg);
+       req->msg = msg;
+
+       dbus_pending_call_set_notify(req->call, function, req, NULL);
+
+       return 0;
+}
+
+int agent_display_passkey(struct agent *agent, struct btd_device *device,
+                               uint32_t passkey)
+{
+       DBusMessage *message;
+       const gchar *dev_path = device_get_path(device);
+
+       message = dbus_message_new_method_call(agent->name, agent->path,
+                               "org.bluez.Agent", "DisplayPasskey");
+       if (!message) {
+               error("Couldn't allocate D-Bus message");
+               return -1;
+       }
+
+       dbus_message_append_args(message,
+                               DBUS_TYPE_OBJECT_PATH, &dev_path,
+                               DBUS_TYPE_UINT32, &passkey,
+                               DBUS_TYPE_INVALID);
+
+       if (!g_dbus_send_message(connection, message)) {
+               error("D-Bus send failed");
+               dbus_message_unref(message);
+               return -1;
+       }
+
+       return 0;
+}
+
+uint8_t agent_get_io_capability(struct agent *agent)
+{
+       return agent->capability;
+}
+
+gboolean agent_matches(struct agent *agent, const char *name, const char *path)
+{
+       if (g_str_equal(agent->name, name) && g_str_equal(agent->path, path))
+               return TRUE;
+
+       return FALSE;
+}
+
+gboolean agent_is_busy(struct agent *agent, void *user_data)
+{
+       if (!agent->request)
+               return FALSE;
+
+       if (user_data && user_data != agent->request->user_data)
+               return FALSE;
+
+       return TRUE;
+}
+
+void agent_exit(void)
+{
+       dbus_connection_unref(connection);
+       connection = NULL;
+}
+
+void agent_init(void)
+{
+       connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+}
diff --git a/src/agent.h b/src/agent.h
new file mode 100644 (file)
index 0000000..e184250
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 agent;
+
+typedef void (*agent_cb) (struct agent *agent, DBusError *err,
+                               void *user_data);
+
+typedef void (*agent_pincode_cb) (struct agent *agent, DBusError *err,
+                                       const char *pincode, void *user_data);
+
+typedef void (*agent_passkey_cb) (struct agent *agent, DBusError *err,
+                                       uint32_t passkey, void *user_data);
+
+typedef void (*agent_remove_cb) (struct agent *agent, void *user_data);
+
+struct agent *agent_create(struct btd_adapter *adapter, const char *name,
+                               const char *path, uint8_t capability,
+                               agent_remove_cb cb, void *remove_cb_data);
+
+void agent_free(struct agent *agent);
+
+int agent_authorize(struct agent *agent, const char *path,
+                       const char *uuid, agent_cb cb, void *user_data,
+                       GDestroyNotify destroy);
+
+int agent_request_pincode(struct agent *agent, struct btd_device *device,
+                               agent_pincode_cb cb, void *user_data,
+                               GDestroyNotify destroy);
+
+int agent_confirm_mode_change(struct agent *agent, const char *new_mode,
+                               agent_cb cb, void *user_data,
+                               GDestroyNotify destroy);
+
+int agent_request_passkey(struct agent *agent, struct btd_device *device,
+                               agent_passkey_cb cb, void *user_data,
+                               GDestroyNotify destroy);
+
+int agent_request_confirmation(struct agent *agent, struct btd_device *device,
+                               uint32_t passkey, agent_cb cb,
+                               void *user_data, GDestroyNotify destroy);
+
+int agent_display_passkey(struct agent *agent, struct btd_device *device,
+                               uint32_t passkey);
+
+int agent_cancel(struct agent *agent);
+
+gboolean agent_is_busy(struct agent *agent, void *user_data);
+
+uint8_t agent_get_io_capability(struct agent *agent);
+
+gboolean agent_matches(struct agent *agent, const char *name, const char *path);
+
+void agent_init(void);
+void agent_exit(void);
+
diff --git a/src/attrib-server.c b/src/attrib-server.c
new file mode 100644 (file)
index 0000000..9e37aee
--- /dev/null
@@ -0,0 +1,1296 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "log.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "sdpd.h"
+#include "hcid.h"
+#include "att.h"
+#include "gattrib.h"
+
+#include "attrib-server.h"
+
+#define GATT_PSM 0x1f
+#define GATT_CID 4
+
+static GSList *database = NULL;
+
+struct gatt_channel {
+       bdaddr_t src;
+       bdaddr_t dst;
+       GSList *configs;
+       GSList *notify;
+       GSList *indicate;
+       GAttrib *attrib;
+       guint mtu;
+       gboolean le;
+       guint id;
+       gboolean encrypted;
+};
+
+struct group_elem {
+       uint16_t handle;
+       uint16_t end;
+       uint8_t *data;
+       uint16_t len;
+};
+
+static GIOChannel *l2cap_io = NULL;
+static GIOChannel *le_io = NULL;
+static GSList *clients = NULL;
+static uint32_t gatt_sdp_handle = 0;
+static uint32_t gap_sdp_handle = 0;
+
+/* GAP attribute handles */
+static uint16_t name_handle = 0x0000;
+static uint16_t appearance_handle = 0x0000;
+
+static bt_uuid_t prim_uuid = {
+                       .type = BT_UUID16,
+                       .value.u16 = GATT_PRIM_SVC_UUID
+};
+static bt_uuid_t snd_uuid = {
+                       .type = BT_UUID16,
+                       .value.u16 = GATT_SND_SVC_UUID
+};
+
+static sdp_record_t *server_record_new(uuid_t *uuid, uint16_t start, uint16_t end)
+{
+       sdp_list_t *svclass_id, *apseq, *proto[2], *root, *aproto;
+       uuid_t root_uuid, proto_uuid, l2cap;
+       sdp_record_t *record;
+       sdp_data_t *psm, *sh, *eh;
+       uint16_t lp = GATT_PSM;
+
+       if (uuid == NULL)
+               return NULL;
+
+       if (start > end)
+               return NULL;
+
+       record = sdp_record_alloc();
+       if (record == NULL)
+               return NULL;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(record, root);
+       sdp_list_free(root, NULL);
+
+       svclass_id = sdp_list_append(NULL, uuid);
+       sdp_set_service_classes(record, svclass_id);
+       sdp_list_free(svclass_id, NULL);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap);
+       psm = sdp_data_alloc(SDP_UINT16, &lp);
+       proto[0] = sdp_list_append(proto[0], psm);
+       apseq = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&proto_uuid, ATT_UUID);
+       proto[1] = sdp_list_append(NULL, &proto_uuid);
+       sh = sdp_data_alloc(SDP_UINT16, &start);
+       proto[1] = sdp_list_append(proto[1], sh);
+       eh = sdp_data_alloc(SDP_UINT16, &end);
+       proto[1] = sdp_list_append(proto[1], eh);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(NULL, apseq);
+       sdp_set_access_protos(record, aproto);
+
+       sdp_data_free(psm);
+       sdp_data_free(sh);
+       sdp_data_free(eh);
+       sdp_list_free(proto[0], NULL);
+       sdp_list_free(proto[1], NULL);
+       sdp_list_free(apseq, NULL);
+       sdp_list_free(aproto, NULL);
+
+       return record;
+}
+
+static int handle_cmp(gconstpointer a, gconstpointer b)
+{
+       const struct attribute *attrib = a;
+       uint16_t handle = GPOINTER_TO_UINT(b);
+
+       return attrib->handle - handle;
+}
+
+static int attribute_cmp(gconstpointer a1, gconstpointer a2)
+{
+       const struct attribute *attrib1 = a1;
+       const struct attribute *attrib2 = a2;
+
+       return attrib1->handle - attrib2->handle;
+}
+
+static uint8_t att_check_reqs(struct gatt_channel *channel, uint8_t opcode,
+                                                               int reqs)
+{
+       /* FIXME: currently, it is assumed an encrypted link is enough for
+        * authentication. This will allow to enable the SMP negotiation once
+        * it is on upstream kernel. */
+       if (!channel->encrypted)
+               channel->encrypted = g_attrib_is_encrypted(channel->attrib);
+       if (reqs == ATT_AUTHENTICATION && !channel->encrypted)
+               return ATT_ECODE_INSUFF_ENC;
+
+       switch (opcode) {
+       case ATT_OP_READ_BY_GROUP_REQ:
+       case ATT_OP_READ_BY_TYPE_REQ:
+       case ATT_OP_READ_REQ:
+       case ATT_OP_READ_BLOB_REQ:
+       case ATT_OP_READ_MULTI_REQ:
+               if (reqs == ATT_NOT_PERMITTED)
+                       return ATT_ECODE_READ_NOT_PERM;
+               break;
+       case ATT_OP_PREP_WRITE_REQ:
+       case ATT_OP_WRITE_REQ:
+       case ATT_OP_WRITE_CMD:
+               if (reqs == ATT_NOT_PERMITTED)
+                       return ATT_ECODE_WRITE_NOT_PERM;
+               break;
+       }
+
+       return 0;
+}
+
+static uint8_t client_set_notifications(struct attribute *attr,
+                                                       gpointer user_data)
+{
+       struct gatt_channel *channel = user_data;
+       struct attribute *last_chr_val = NULL;
+       uint16_t cfg_val;
+       uint8_t props;
+       bt_uuid_t uuid;
+       GSList *l;
+
+       cfg_val = att_get_u16(attr->data);
+
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       for (l = database, props = 0; l != NULL; l = l->next) {
+               struct attribute *a = l->data;
+               static uint16_t handle = 0;
+
+               if (a->handle >= attr->handle)
+                       break;
+
+               if (bt_uuid_cmp(&a->uuid, &uuid) == 0) {
+                       props = att_get_u8(&a->data[0]);
+                       handle = att_get_u16(&a->data[1]);
+                       continue;
+               }
+
+               if (handle && a->handle == handle)
+                       last_chr_val = a;
+       }
+
+       if (last_chr_val == NULL)
+               return 0;
+
+       if ((cfg_val & 0x0001) && !(props & ATT_CHAR_PROPER_NOTIFY))
+               return ATT_ECODE_WRITE_NOT_PERM;
+
+       if ((cfg_val & 0x0002) && !(props & ATT_CHAR_PROPER_INDICATE))
+               return ATT_ECODE_WRITE_NOT_PERM;
+
+       if (cfg_val & 0x0001)
+               channel->notify = g_slist_append(channel->notify, last_chr_val);
+       else
+               channel->notify = g_slist_remove(channel->notify, last_chr_val);
+
+       if (cfg_val & 0x0002)
+               channel->indicate = g_slist_append(channel->indicate,
+                                                               last_chr_val);
+       else
+               channel->indicate = g_slist_remove(channel->indicate,
+                                                               last_chr_val);
+
+       return 0;
+}
+
+static struct attribute *client_cfg_attribute(struct gatt_channel *channel,
+                                               struct attribute *orig_attr,
+                                               const uint8_t *value, int vlen)
+{
+       guint handle = orig_attr->handle;
+       bt_uuid_t uuid;
+       GSList *l;
+
+       bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
+       if (bt_uuid_cmp(&orig_attr->uuid, &uuid) != 0)
+               return NULL;
+
+       /* Value is unchanged, not need to create a private copy yet */
+       if (vlen == orig_attr->len && memcmp(orig_attr->data, value, vlen) == 0)
+               return orig_attr;
+
+       l = g_slist_find_custom(channel->configs, GUINT_TO_POINTER(handle),
+                                                               handle_cmp);
+       if (!l) {
+               struct attribute *a;
+
+               /* Create a private copy of the Client Characteristic
+                * Configuration attribute */
+               a = g_malloc0(sizeof(*a) + vlen);
+               memcpy(a, orig_attr, sizeof(*a));
+               memcpy(a->data, value, vlen);
+               a->write_cb = client_set_notifications;
+               a->cb_user_data = channel;
+
+               channel->configs = g_slist_insert_sorted(channel->configs, a,
+                                                               attribute_cmp);
+
+               return a;
+       }
+
+       return l->data;
+}
+
+static uint16_t read_by_group(struct gatt_channel *channel, uint16_t start,
+                                               uint16_t end, bt_uuid_t *uuid,
+                                               uint8_t *pdu, int len)
+{
+       struct att_data_list *adl;
+       struct attribute *a;
+       struct group_elem *cur, *old = NULL;
+       GSList *l, *groups;
+       uint16_t length, last_handle, last_size = 0;
+       uint8_t status;
+       int i;
+
+       if (start > end || start == 0x0000)
+               return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start,
+                                       ATT_ECODE_INVALID_HANDLE, pdu, len);
+
+       /*
+        * Only <<Primary Service>> and <<Secondary Service>> grouping
+        * types may be used in the Read By Group Type Request.
+        */
+
+       if (bt_uuid_cmp(uuid, &prim_uuid) != 0 &&
+               bt_uuid_cmp(uuid, &snd_uuid) != 0)
+               return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, 0x0000,
+                                       ATT_ECODE_UNSUPP_GRP_TYPE, pdu, len);
+
+       last_handle = end;
+       for (l = database, groups = NULL; l; l = l->next) {
+               struct attribute *client_attr;
+
+               a = l->data;
+
+               if (a->handle < start)
+                       continue;
+
+               if (a->handle >= end)
+                       break;
+
+               /* The old group ends when a new one starts */
+               if (old && (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 ||
+                               bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)) {
+                       old->end = last_handle;
+                       old = NULL;
+               }
+
+               if (bt_uuid_cmp(&a->uuid, uuid) != 0) {
+                       /* Still inside a service, update its last handle */
+                       if (old)
+                               last_handle = a->handle;
+                       continue;
+               }
+
+               if (last_size && (last_size != a->len))
+                       break;
+
+               status = att_check_reqs(channel, ATT_OP_READ_BY_GROUP_REQ,
+                                                               a->read_reqs);
+
+               client_attr = client_cfg_attribute(channel, a, a->data, a->len);
+               if (client_attr)
+                       a = client_attr;
+
+               if (status == 0x00 && a->read_cb)
+                       status = a->read_cb(a, a->cb_user_data);
+
+               if (status) {
+                       g_slist_foreach(groups, (GFunc) g_free, NULL);
+                       g_slist_free(groups);
+                       return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ,
+                                               a->handle, status, pdu, len);
+               }
+
+               cur = g_new0(struct group_elem, 1);
+               cur->handle = a->handle;
+               cur->data = a->data;
+               cur->len = a->len;
+
+               /* Attribute Grouping Type found */
+               groups = g_slist_append(groups, cur);
+
+               last_size = a->len;
+               old = cur;
+               last_handle = cur->handle;
+       }
+
+       if (groups == NULL)
+               return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start,
+                                       ATT_ECODE_ATTR_NOT_FOUND, pdu, len);
+
+       if (l == NULL)
+               cur->end = a->handle;
+       else
+               cur->end = last_handle;
+
+       length = g_slist_length(groups);
+
+       adl = att_data_list_alloc(length, last_size + 4);
+
+       for (i = 0, l = groups; l; l = l->next, i++) {
+               uint8_t *value;
+
+               cur = l->data;
+
+               value = (void *) adl->data[i];
+
+               att_put_u16(cur->handle, value);
+               att_put_u16(cur->end, &value[2]);
+               /* Attribute Value */
+               memcpy(&value[4], cur->data, cur->len);
+       }
+
+       length = enc_read_by_grp_resp(adl, pdu, len);
+
+       att_data_list_free(adl);
+       g_slist_foreach(groups, (GFunc) g_free, NULL);
+       g_slist_free(groups);
+
+       return length;
+}
+
+static uint16_t read_by_type(struct gatt_channel *channel, uint16_t start,
+                                               uint16_t end, bt_uuid_t *uuid,
+                                               uint8_t *pdu, int len)
+{
+       struct att_data_list *adl;
+       GSList *l, *types;
+       struct attribute *a;
+       uint16_t num, length;
+       uint8_t status;
+       int i;
+
+       if (start > end || start == 0x0000)
+               return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start,
+                                       ATT_ECODE_INVALID_HANDLE, pdu, len);
+
+       for (l = database, length = 0, types = NULL; l; l = l->next) {
+               struct attribute *client_attr;
+
+               a = l->data;
+
+               if (a->handle < start)
+                       continue;
+
+               if (a->handle >= end)
+                       break;
+
+               if (bt_uuid_cmp(&a->uuid, uuid)  != 0)
+                       continue;
+
+               status = att_check_reqs(channel, ATT_OP_READ_BY_TYPE_REQ,
+                                                               a->read_reqs);
+
+               client_attr = client_cfg_attribute(channel, a, a->data, a->len);
+               if (client_attr)
+                       a = client_attr;
+
+               if (status == 0x00 && a->read_cb)
+                       status = a->read_cb(a, a->cb_user_data);
+
+               if (status) {
+                       g_slist_free(types);
+                       return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ,
+                                               a->handle, status, pdu, len);
+               }
+
+               /* All elements must have the same length */
+               if (length == 0)
+                       length = a->len;
+               else if (a->len != length)
+                       break;
+
+               types = g_slist_append(types, a);
+       }
+
+       if (types == NULL)
+               return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start,
+                                       ATT_ECODE_ATTR_NOT_FOUND, pdu, len);
+
+       num = g_slist_length(types);
+
+       /* Handle length plus attribute value length */
+       length += 2;
+
+       adl = att_data_list_alloc(num, length);
+
+       for (i = 0, l = types; l; i++, l = l->next) {
+               uint8_t *value;
+
+               a = l->data;
+
+               value = (void *) adl->data[i];
+
+               att_put_u16(a->handle, value);
+
+               /* Attribute Value */
+               memcpy(&value[2], a->data, a->len);
+       }
+
+       length = enc_read_by_type_resp(adl, pdu, len);
+
+       att_data_list_free(adl);
+       g_slist_free(types);
+
+       return length;
+}
+
+static int find_info(uint16_t start, uint16_t end, uint8_t *pdu, int len)
+{
+       struct attribute *a;
+       struct att_data_list *adl;
+       GSList *l, *info;
+       uint8_t format, last_type = BT_UUID_UNSPEC;
+       uint16_t length, num;
+       int i;
+
+       if (start > end || start == 0x0000)
+               return enc_error_resp(ATT_OP_FIND_INFO_REQ, start,
+                                       ATT_ECODE_INVALID_HANDLE, pdu, len);
+
+       for (l = database, info = NULL, num = 0; l; l = l->next) {
+               a = l->data;
+
+               if (a->handle < start)
+                       continue;
+
+               if (a->handle > end)
+                       break;
+
+               if (last_type == BT_UUID_UNSPEC)
+                       last_type = a->uuid.type;
+
+               if (a->uuid.type != last_type)
+                       break;
+
+               info = g_slist_append(info, a);
+               num++;
+
+               last_type = a->uuid.type;
+       }
+
+       if (info == NULL)
+               return enc_error_resp(ATT_OP_FIND_INFO_REQ, start,
+                                       ATT_ECODE_ATTR_NOT_FOUND, pdu, len);
+
+       if (last_type == BT_UUID16) {
+               length = 2;
+               format = 0x01;
+       } else if (last_type == BT_UUID128) {
+               length = 16;
+               format = 0x02;
+       }
+
+       adl = att_data_list_alloc(num, length + 2);
+
+       for (i = 0, l = info; l; i++, l = l->next) {
+               uint8_t *value;
+
+               a = l->data;
+
+               value = (void *) adl->data[i];
+
+               att_put_u16(a->handle, value);
+
+               /* Attribute Value */
+               att_put_uuid(a->uuid, &value[2]);
+       }
+
+       length = enc_find_info_resp(format, adl, pdu, len);
+
+       att_data_list_free(adl);
+       g_slist_free(info);
+
+       return length;
+}
+
+static int find_by_type(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+                       const uint8_t *value, int vlen, uint8_t *opdu, int mtu)
+{
+       struct attribute *a;
+       struct att_range *range;
+       GSList *l, *matches;
+       int len;
+
+       if (start > end || start == 0x0000)
+               return enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start,
+                                       ATT_ECODE_INVALID_HANDLE, opdu, mtu);
+
+       /* Searching first requested handle number */
+       for (l = database, matches = NULL, range = NULL; l; l = l->next) {
+               a = l->data;
+
+               if (a->handle < start)
+                       continue;
+
+               if (a->handle > end)
+                       break;
+
+               /* Primary service? Attribute value matches? */
+               if ((bt_uuid_cmp(&a->uuid, uuid) == 0) && (a->len == vlen) &&
+                                       (memcmp(a->data, value, vlen) == 0)) {
+
+                       range = g_new0(struct att_range, 1);
+                       range->start = a->handle;
+                       /* It is allowed to have end group handle the same as
+                        * start handle, for groups with only one attribute. */
+                       range->end = a->handle;
+
+                       matches = g_slist_append(matches, range);
+               } else if (range) {
+                       /* Update the last found handle or reset the pointer
+                        * to track that a new group started: Primary or
+                        * Secondary service. */
+                       if (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 ||
+                                       bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)
+                               range = NULL;
+                       else
+                               range->end = a->handle;
+               }
+       }
+
+       if (matches == NULL)
+               return enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start,
+                               ATT_ECODE_ATTR_NOT_FOUND, opdu, mtu);
+
+       len = enc_find_by_type_resp(matches, opdu, mtu);
+
+       g_slist_foreach(matches, (GFunc) g_free, NULL);
+       g_slist_free(matches);
+
+       return len;
+}
+
+static struct attribute *find_primary_range(uint16_t start, uint16_t *end)
+{
+       struct attribute *attrib;
+       guint h = start;
+       GSList *l;
+
+       if (end == NULL)
+               return NULL;
+
+       l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp);
+       if (!l)
+               return NULL;
+
+       attrib = l->data;
+
+       if (bt_uuid_cmp(&attrib->uuid, &prim_uuid) != 0)
+               return NULL;
+
+       *end = start;
+
+       for (l = l->next; l; l = l->next) {
+               struct attribute *a = l->data;
+
+               if (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 ||
+                               bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)
+                       break;
+
+               *end = a->handle;
+       }
+
+       return attrib;
+}
+
+static uint16_t read_value(struct gatt_channel *channel, uint16_t handle,
+                                                       uint8_t *pdu, int len)
+{
+       struct attribute *a, *client_attr;
+       uint8_t status;
+       GSList *l;
+       guint h = handle;
+
+       l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp);
+       if (!l)
+               return enc_error_resp(ATT_OP_READ_REQ, handle,
+                                       ATT_ECODE_INVALID_HANDLE, pdu, len);
+
+       a = l->data;
+
+       status = att_check_reqs(channel, ATT_OP_READ_REQ, a->read_reqs);
+
+       client_attr = client_cfg_attribute(channel, a, a->data, a->len);
+       if (client_attr)
+               a = client_attr;
+
+       if (status == 0x00 && a->read_cb)
+               status = a->read_cb(a, a->cb_user_data);
+
+       if (status)
+               return enc_error_resp(ATT_OP_READ_REQ, handle, status, pdu,
+                                                                       len);
+
+       return enc_read_resp(a->data, a->len, pdu, len);
+}
+
+static uint16_t read_blob(struct gatt_channel *channel, uint16_t handle,
+                                       uint16_t offset, uint8_t *pdu, int len)
+{
+       struct attribute *a, *client_attr;
+       uint8_t status;
+       GSList *l;
+       guint h = handle;
+
+       l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp);
+       if (!l)
+               return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle,
+                                       ATT_ECODE_INVALID_HANDLE, pdu, len);
+
+       a = l->data;
+
+       if (a->len <= offset)
+               return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle,
+                                       ATT_ECODE_INVALID_OFFSET, pdu, len);
+
+       status = att_check_reqs(channel, ATT_OP_READ_BLOB_REQ, a->read_reqs);
+
+       client_attr = client_cfg_attribute(channel, a, a->data, a->len);
+       if (client_attr)
+               a = client_attr;
+
+       if (status == 0x00 && a->read_cb)
+               status = a->read_cb(a, a->cb_user_data);
+
+       if (status)
+               return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle, status,
+                                                               pdu, len);
+
+       return enc_read_blob_resp(a->data, a->len, offset, pdu, len);
+}
+
+static uint16_t write_value(struct gatt_channel *channel, uint16_t handle,
+                                               const uint8_t *value, int vlen,
+                                               uint8_t *pdu, int len)
+{
+       struct attribute *a, *client_attr;
+       uint8_t status;
+       GSList *l;
+       guint h = handle;
+
+       l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp);
+       if (!l)
+               return enc_error_resp(ATT_OP_WRITE_REQ, handle,
+                               ATT_ECODE_INVALID_HANDLE, pdu, len);
+
+       a = l->data;
+
+       status = att_check_reqs(channel, ATT_OP_WRITE_REQ, a->write_reqs);
+       if (status)
+               return enc_error_resp(ATT_OP_WRITE_REQ, handle, status, pdu,
+                                                                       len);
+
+       client_attr = client_cfg_attribute(channel, a, value, vlen);
+       if (client_attr)
+               a = client_attr;
+       else
+               attrib_db_update(a->handle, &a->uuid, value, vlen);
+
+       if (a->write_cb) {
+               status = a->write_cb(a, a->cb_user_data);
+               if (status)
+                       return enc_error_resp(ATT_OP_WRITE_REQ, handle, status,
+                                                               pdu, len);
+       }
+
+       DBG("Notifications: %d, indications: %d",
+                                       g_slist_length(channel->notify),
+                                       g_slist_length(channel->indicate));
+
+       return enc_write_resp(pdu, len);
+}
+
+static uint16_t mtu_exchange(struct gatt_channel *channel, uint16_t mtu,
+               uint8_t *pdu, int len)
+{
+       guint old_mtu = channel->mtu;
+
+       if (mtu < ATT_DEFAULT_LE_MTU)
+               channel->mtu = ATT_DEFAULT_LE_MTU;
+       else
+               channel->mtu = MIN(mtu, channel->mtu);
+
+       bt_io_set(le_io, BT_IO_L2CAP, NULL,
+                       BT_IO_OPT_OMTU, channel->mtu,
+                       BT_IO_OPT_INVALID);
+
+       return enc_mtu_resp(old_mtu, pdu, len);
+}
+
+static void channel_disconnect(void *user_data)
+{
+       struct gatt_channel *channel = user_data;
+
+       g_attrib_unref(channel->attrib);
+       clients = g_slist_remove(clients, channel);
+
+       g_slist_free(channel->notify);
+       g_slist_free(channel->indicate);
+       g_slist_foreach(channel->configs, (GFunc) g_free, NULL);
+       g_slist_free(channel->configs);
+
+       g_free(channel);
+}
+
+static void channel_handler(const uint8_t *ipdu, uint16_t len,
+                                                       gpointer user_data)
+{
+       struct gatt_channel *channel = user_data;
+       uint8_t opdu[ATT_MAX_MTU], value[ATT_MAX_MTU];
+       uint16_t length, start, end, mtu, offset;
+       bt_uuid_t uuid;
+       uint8_t status = 0;
+       int vlen;
+
+       DBG("op 0x%02x", ipdu[0]);
+
+       switch (ipdu[0]) {
+       case ATT_OP_READ_BY_GROUP_REQ:
+               length = dec_read_by_grp_req(ipdu, len, &start, &end, &uuid);
+               if (length == 0) {
+                       status = ATT_ECODE_INVALID_PDU;
+                       goto done;
+               }
+
+               length = read_by_group(channel, start, end, &uuid, opdu,
+                                                               channel->mtu);
+               break;
+       case ATT_OP_READ_BY_TYPE_REQ:
+               length = dec_read_by_type_req(ipdu, len, &start, &end, &uuid);
+               if (length == 0) {
+                       status = ATT_ECODE_INVALID_PDU;
+                       goto done;
+               }
+
+               length = read_by_type(channel, start, end, &uuid, opdu,
+                                                               channel->mtu);
+               break;
+       case ATT_OP_READ_REQ:
+               length = dec_read_req(ipdu, len, &start);
+               if (length == 0) {
+                       status = ATT_ECODE_INVALID_PDU;
+                       goto done;
+               }
+
+               length = read_value(channel, start, opdu, channel->mtu);
+               break;
+       case ATT_OP_READ_BLOB_REQ:
+               length = dec_read_blob_req(ipdu, len, &start, &offset);
+               if (length == 0) {
+                       status = ATT_ECODE_INVALID_PDU;
+                       goto done;
+               }
+
+               length = read_blob(channel, start, offset, opdu, channel->mtu);
+               break;
+       case ATT_OP_MTU_REQ:
+               if (!channel->le) {
+                       status = ATT_ECODE_REQ_NOT_SUPP;
+                       goto done;
+               }
+
+               length = dec_mtu_req(ipdu, len, &mtu);
+               if (length == 0) {
+                       status = ATT_ECODE_INVALID_PDU;
+                       goto done;
+               }
+
+               length = mtu_exchange(channel, mtu, opdu, channel->mtu);
+               break;
+       case ATT_OP_FIND_INFO_REQ:
+               length = dec_find_info_req(ipdu, len, &start, &end);
+               if (length == 0) {
+                       status = ATT_ECODE_INVALID_PDU;
+                       goto done;
+               }
+
+               length = find_info(start, end, opdu, channel->mtu);
+               break;
+       case ATT_OP_WRITE_REQ:
+               length = dec_write_req(ipdu, len, &start, value, &vlen);
+               if (length == 0) {
+                       status = ATT_ECODE_INVALID_PDU;
+                       goto done;
+               }
+
+               length = write_value(channel, start, value, vlen, opdu,
+                                                               channel->mtu);
+               break;
+       case ATT_OP_WRITE_CMD:
+               length = dec_write_cmd(ipdu, len, &start, value, &vlen);
+               if (length > 0)
+                       write_value(channel, start, value, vlen, opdu,
+                                                               channel->mtu);
+               return;
+       case ATT_OP_FIND_BY_TYPE_REQ:
+               length = dec_find_by_type_req(ipdu, len, &start, &end,
+                                                       &uuid, value, &vlen);
+               if (length == 0) {
+                       status = ATT_ECODE_INVALID_PDU;
+                       goto done;
+               }
+
+               length = find_by_type(start, end, &uuid, value, vlen,
+                                                       opdu, channel->mtu);
+               break;
+       case ATT_OP_HANDLE_CNF:
+               return;
+       case ATT_OP_READ_MULTI_REQ:
+       case ATT_OP_PREP_WRITE_REQ:
+       case ATT_OP_EXEC_WRITE_REQ:
+       default:
+               DBG("Unsupported request 0x%02x", ipdu[0]);
+               status = ATT_ECODE_REQ_NOT_SUPP;
+               goto done;
+       }
+
+       if (length == 0)
+               status = ATT_ECODE_IO;
+
+done:
+       if (status)
+               length = enc_error_resp(ipdu[0], 0x0000, status, opdu,
+                                                               channel->mtu);
+
+       g_attrib_send(channel->attrib, 0, opdu[0], opdu, length,
+                                                       NULL, NULL, NULL);
+}
+
+static void connect_event(GIOChannel *io, GError *err, void *user_data)
+{
+       struct gatt_channel *channel;
+       uint16_t cid;
+       GError *gerr = NULL;
+
+       if (err) {
+               error("%s", err->message);
+               return;
+       }
+
+       channel = g_new0(struct gatt_channel, 1);
+
+       bt_io_get(io, BT_IO_L2CAP, &gerr,
+                       BT_IO_OPT_SOURCE_BDADDR, &channel->src,
+                       BT_IO_OPT_DEST_BDADDR, &channel->dst,
+                       BT_IO_OPT_CID, &cid,
+                       BT_IO_OPT_OMTU, &channel->mtu,
+                       BT_IO_OPT_INVALID);
+       if (gerr) {
+               error("bt_io_get: %s", gerr->message);
+               g_error_free(gerr);
+               g_free(channel);
+               g_io_channel_shutdown(io, TRUE, NULL);
+               return;
+       }
+
+       if (channel->mtu > ATT_MAX_MTU)
+               channel->mtu = ATT_MAX_MTU;
+
+       if (cid != GATT_CID)
+               channel->le = FALSE;
+       else
+               channel->le = TRUE;
+
+       channel->attrib = g_attrib_new(io);
+       g_io_channel_unref(io);
+
+       channel->id = g_attrib_register(channel->attrib, GATTRIB_ALL_EVENTS,
+                               channel_handler, channel, NULL);
+
+       g_attrib_set_disconnect_function(channel->attrib, channel_disconnect,
+                                                               channel);
+
+       clients = g_slist_append(clients, channel);
+}
+
+static void confirm_event(GIOChannel *io, void *user_data)
+{
+       GError *gerr = NULL;
+
+       if (bt_io_accept(io, connect_event, user_data, NULL, &gerr) == FALSE) {
+               error("bt_io_accept: %s", gerr->message);
+               g_error_free(gerr);
+               g_io_channel_unref(io);
+       }
+
+       return;
+}
+
+static void attrib_notify_clients(struct attribute *attr)
+{
+       guint handle = attr->handle;
+       GSList *l;
+
+       for (l = clients; l; l = l->next) {
+               struct gatt_channel *channel = l->data;
+
+               /* Notification */
+               if (g_slist_find_custom(channel->notify,
+                                       GUINT_TO_POINTER(handle), handle_cmp)) {
+                       uint8_t pdu[ATT_MAX_MTU];
+                       uint16_t len;
+
+                       len = enc_notification(attr, pdu, channel->mtu);
+                       if (len == 0)
+                               continue;
+
+                       g_attrib_send(channel->attrib, 0, pdu[0], pdu, len,
+                                                       NULL, NULL, NULL);
+               }
+
+               /* Indication */
+               if (g_slist_find_custom(channel->indicate,
+                                       GUINT_TO_POINTER(handle), handle_cmp)) {
+                       uint8_t pdu[ATT_MAX_MTU];
+                       uint16_t len;
+
+                       len = enc_indication(attr, pdu, channel->mtu);
+                       if (len == 0)
+                               return;
+
+                       g_attrib_send(channel->attrib, 0, pdu[0], pdu, len,
+                                                       NULL, NULL, NULL);
+               }
+       }
+}
+
+static gboolean register_core_services(void)
+{
+       uint8_t atval[256];
+       bt_uuid_t uuid;
+       uint16_t appearance = 0x0000;
+
+       /* GAP service: primary service definition */
+       bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+       att_put_u16(GENERIC_ACCESS_PROFILE_ID, &atval[0]);
+       attrib_db_add(0x0001, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+       /* GAP service: device name characteristic */
+       name_handle = 0x0006;
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(name_handle, &atval[1]);
+       att_put_u16(GATT_CHARAC_DEVICE_NAME, &atval[3]);
+       attrib_db_add(0x0004, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* GAP service: device name attribute */
+       bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
+       attrib_db_add(name_handle, &uuid, ATT_NONE, ATT_NOT_PERMITTED,
+                                                               NULL, 0);
+
+       /* GAP service: device appearance characteristic */
+       appearance_handle = 0x0008;
+       bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+       atval[0] = ATT_CHAR_PROPER_READ;
+       att_put_u16(appearance_handle, &atval[1]);
+       att_put_u16(GATT_CHARAC_APPEARANCE, &atval[3]);
+       attrib_db_add(0x0007, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+       /* GAP service: device appearance attribute */
+       bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
+       att_put_u16(appearance, &atval[0]);
+       attrib_db_add(appearance_handle, &uuid, ATT_NONE, ATT_NOT_PERMITTED,
+                                                               atval, 2);
+       gap_sdp_handle = attrib_create_sdp(0x0001, "Generic Access Profile");
+       if (gap_sdp_handle == 0) {
+               error("Failed to register GAP service record");
+               return FALSE;
+       }
+
+       /* GATT service: primary service definition */
+       bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+       att_put_u16(GENERIC_ATTRIB_PROFILE_ID, &atval[0]);
+       attrib_db_add(0x0010, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+       gatt_sdp_handle = attrib_create_sdp(0x0010,
+                                               "Generic Attribute Profile");
+       if (gatt_sdp_handle == 0) {
+               error("Failed to register GATT service record");
+               goto failed;
+       }
+
+       return TRUE;
+
+failed:
+       remove_record_from_server(gap_sdp_handle);
+
+       return FALSE;
+}
+
+int attrib_server_init(void)
+{
+       GError *gerr = NULL;
+
+       /* BR/EDR socket */
+       l2cap_io = bt_io_listen(BT_IO_L2CAP, NULL, confirm_event,
+                                       NULL, NULL, &gerr,
+                                       BT_IO_OPT_SOURCE_BDADDR, BDADDR_ANY,
+                                       BT_IO_OPT_PSM, GATT_PSM,
+                                       BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+                                       BT_IO_OPT_INVALID);
+       if (l2cap_io == NULL) {
+               error("%s", gerr->message);
+               g_error_free(gerr);
+               return -1;
+       }
+
+       if (!register_core_services())
+               goto failed;
+
+       if (!main_opts.le)
+               return 0;
+
+       /* LE socket */
+       le_io = bt_io_listen(BT_IO_L2CAP, NULL, confirm_event,
+                                       &le_io, NULL, &gerr,
+                                       BT_IO_OPT_SOURCE_BDADDR, BDADDR_ANY,
+                                       BT_IO_OPT_CID, GATT_CID,
+                                       BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+                                       BT_IO_OPT_INVALID);
+       if (le_io == NULL) {
+               error("%s", gerr->message);
+               g_error_free(gerr);
+               /* Doesn't have LE support, continue */
+       }
+
+       return 0;
+
+failed:
+       g_io_channel_unref(l2cap_io);
+       l2cap_io = NULL;
+
+       if (le_io) {
+               g_io_channel_unref(le_io);
+               le_io = NULL;
+       }
+
+       return -1;
+}
+
+void attrib_server_exit(void)
+{
+       GSList *l;
+
+       g_slist_foreach(database, (GFunc) g_free, NULL);
+       g_slist_free(database);
+
+       if (l2cap_io) {
+               g_io_channel_unref(l2cap_io);
+               g_io_channel_shutdown(l2cap_io, FALSE, NULL);
+       }
+
+       if (le_io) {
+               g_io_channel_unref(le_io);
+               g_io_channel_shutdown(le_io, FALSE, NULL);
+       }
+
+       for (l = clients; l; l = l->next) {
+               struct gatt_channel *channel = l->data;
+
+               g_slist_free(channel->notify);
+               g_slist_free(channel->indicate);
+               g_slist_foreach(channel->configs, (GFunc) g_free, NULL);
+               g_slist_free(channel->configs);
+
+               g_attrib_unref(channel->attrib);
+               g_free(channel);
+       }
+
+       g_slist_free(clients);
+
+       if (gatt_sdp_handle)
+               remove_record_from_server(gatt_sdp_handle);
+
+       if (gap_sdp_handle)
+               remove_record_from_server(gap_sdp_handle);
+}
+
+uint32_t attrib_create_sdp(uint16_t handle, const char *name)
+{
+       sdp_record_t *record;
+       struct attribute *a;
+       uint16_t end = 0;
+       uuid_t svc, gap_uuid;
+
+       a = find_primary_range(handle, &end);
+
+       if (a == NULL)
+               return 0;
+
+       if (a->len == 2)
+               sdp_uuid16_create(&svc, att_get_u16(a->data));
+       else if (a->len == 16)
+               sdp_uuid128_create(&svc, a->data);
+       else
+               return 0;
+
+       record = server_record_new(&svc, handle, end);
+       if (record == NULL)
+               return 0;
+
+       if (name)
+               sdp_set_info_attr(record, name, "BlueZ", NULL);
+
+       sdp_uuid16_create(&gap_uuid, GENERIC_ACCESS_PROFILE_ID);
+       if (sdp_uuid_cmp(&svc, &gap_uuid) == 0) {
+               sdp_set_url_attr(record, "http://www.bluez.org/",
+                               "http://www.bluez.org/",
+                               "http://www.bluez.org/");
+       }
+
+       if (add_record_to_server(BDADDR_ANY, record) < 0)
+               sdp_record_free(record);
+       else
+               return record->handle;
+
+       return 0;
+}
+
+void attrib_free_sdp(uint32_t sdp_handle)
+{
+       remove_record_from_server(sdp_handle);
+}
+
+struct attribute *attrib_db_add(uint16_t handle, bt_uuid_t *uuid, int read_reqs,
+                               int write_reqs, const uint8_t *value, int len)
+{
+       struct attribute *a;
+
+       /* FIXME: handle conflicts */
+
+       a = g_malloc0(sizeof(struct attribute) + len);
+       a->handle = handle;
+       memcpy(&a->uuid, uuid, sizeof(bt_uuid_t));
+       a->read_reqs = read_reqs;
+       a->write_reqs = write_reqs;
+       a->len = len;
+       memcpy(a->data, value, len);
+
+       database = g_slist_insert_sorted(database, a, attribute_cmp);
+
+       return a;
+}
+
+int attrib_db_update(uint16_t handle, bt_uuid_t *uuid, const uint8_t *value,
+                                                               int len)
+{
+       struct attribute *a;
+       GSList *l;
+       guint h = handle;
+
+       l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp);
+       if (!l)
+               return -ENOENT;
+
+       a = g_try_realloc(l->data, sizeof(struct attribute) + len);
+       if (a == NULL)
+               return -ENOMEM;
+
+       l->data = a;
+       a->handle = handle;
+       if (uuid != &a->uuid)
+               memcpy(&a->uuid, uuid, sizeof(bt_uuid_t));
+       a->len = len;
+       memcpy(a->data, value, len);
+
+       attrib_notify_clients(a);
+
+       return 0;
+}
+
+int attrib_db_del(uint16_t handle)
+{
+       struct attribute *a;
+       GSList *l;
+       guint h = handle;
+
+       l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp);
+       if (!l)
+               return -ENOENT;
+
+       a = l->data;
+       database = g_slist_remove(database, a);
+       g_free(a);
+
+       return 0;
+}
+
+int attrib_gap_set(uint16_t uuid, const uint8_t *value, int len)
+{
+       bt_uuid_t u16;
+       uint16_t handle;
+
+       /* FIXME: Missing Privacy and Reconnection Address */
+
+       bt_uuid16_create(&u16, uuid);
+
+       switch (uuid) {
+       case GATT_CHARAC_DEVICE_NAME:
+               handle = name_handle;
+               break;
+       case GATT_CHARAC_APPEARANCE:
+               handle = appearance_handle;
+               break;
+       default:
+               return -ENOSYS;
+       }
+
+       return attrib_db_update(handle, &u16, value, len);
+}
diff --git a/src/attrib-server.h b/src/attrib-server.h
new file mode 100644 (file)
index 0000000..c03d3c5
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 attrib_server_init(void);
+void attrib_server_exit(void);
+
+struct attribute *attrib_db_add(uint16_t handle, bt_uuid_t *uuid, int read_reqs,
+                               int write_reqs, const uint8_t *value, int len);
+int attrib_db_update(uint16_t handle, bt_uuid_t *uuid, const uint8_t *value,
+                                                               int len);
+int attrib_db_del(uint16_t handle);
+int attrib_gap_set(uint16_t uuid, const uint8_t *value, int len);
+uint32_t attrib_create_sdp(uint16_t handle, const char *name);
+void attrib_free_sdp(uint32_t sdp_handle);
diff --git a/src/bluetooth.conf b/src/bluetooth.conf
new file mode 100644 (file)
index 0000000..0aafe54
--- /dev/null
@@ -0,0 +1,103 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+       <policy user="root">
+               <allow own="org.projectx.bluetooth"/>
+               <allow send_interface="org.projectx.bluetooth"/>
+               <allow send_destination="org.projectx.bluetooth"/>
+               <allow own="org.bluez.frwk_agent"/>
+               <allow send_interface="org.bluez.frwk_agent"/>
+               <allow send_destination="org.bluez.frwk_agent"/>
+               <allow own="org.bluez.Agent"/>
+               <allow send_interface="org.bluez.Agent"/>
+               <allow send_destination="org.bluez.Agent"/>
+               <allow own="org.bluez.Adapter"/>
+               <allow send_interface="org.bluez.Adapter"/>
+               <allow send_destination="org.bluez.Adapter"/>
+               <allow own="org.bluez.Manager"/>
+               <allow send_interface="org.bluez.Manager"/>
+               <allow send_destination="org.bluez.Manager"/>
+               <allow own="org.bluez.Device"/>
+               <allow send_interface="org.bluez.Device"/>
+               <allow send_destination="org.bluez.Device"/>
+               <allow own="org.bluez.Serial"/>
+               <allow send_interface="org.bluez.Serial"/>
+               <allow send_destination="org.bluez.Serial"/>
+               <allow own="org.bluez.SerialProxyManager"/>
+               <allow send_interface="org.bluez.SerialProxyManager"/>
+               <allow send_destination="org.bluez.SerialProxyManager"/>
+               <allow own="org.bluez.SerialProxy"/>
+               <allow send_interface="org.bluez.SerialProxy"/>
+               <allow send_destination="org.bluez.SerialProxy"/>
+               <allow own="org.bluez.NetworkServer"/>
+               <allow send_interface="org.bluez.NetworkServer"/>
+               <allow send_destination="org.bluez.NetworkServer"/>
+               <allow own="org.bluez.Network"/>
+               <allow send_interface="org.bluez.Network"/>
+               <allow send_destination="org.bluez.Network"/>
+               <allow own="org.bluez.AudioSink"/>
+               <allow send_interface="org.bluez.AudioSink"/>
+               <allow send_destination="org.bluez.AudioSink"/>
+               <allow own="org.bluez.Input"/>
+               <allow send_interface="org.bluez.Input"/>
+               <allow send_destination="org.bluez.Input"/>
+       </policy>
+       <policy group="bt_use">
+               <allow send_interface="org.projectx.bluetooth"/>
+               <allow send_destination="org.projectx.bluetooth"/>
+               <allow send_interface="org.bluez.frwk_agent"/>
+               <allow send_destination="org.bluez.frwk_agent"/>
+               <allow send_interface="org.bluez.Agent"/>
+               <allow send_destination="org.bluez.Agent"/>
+               <allow send_interface="org.bluez.Adapter"/>
+               <allow send_destination="org.bluez.Adapter"/>
+               <allow send_interface="org.bluez.Manager"/>
+               <allow send_destination="org.bluez.Manager"/>
+               <allow send_interface="org.bluez.Device"/>
+               <allow send_destination="org.bluez.Device"/>
+               <allow send_interface="org.bluez.Serial"/>
+               <allow send_destination="org.bluez.Serial"/>
+               <allow send_interface="org.bluez.SerialProxyManager"/>
+               <allow send_destination="org.bluez.SerialProxyManager"/>
+               <allow send_interface="org.bluez.SerialProxy"/>
+               <allow send_destination="org.bluez.SerialProxy"/>
+               <allow send_interface="org.bluez.NetworkServer"/>
+               <allow send_destination="org.bluez.NetworkServer"/>
+               <allow send_interface="org.bluez.Network"/>
+               <allow send_destination="org.bluez.Network"/>
+               <allow send_interface="org.bluez.AudioSink"/>
+               <allow send_destination="org.bluez.AudioSink"/>
+               <allow send_interface="org.bluez.Input"/>
+               <allow send_destination="org.bluez.Input"/>
+       </policy>
+       <policy context="default">
+               <deny send_interface="org.projectx.bluetooth"/>
+               <deny send_destination="org.projectx.bluetooth"/>
+               <deny send_interface="org.bluez.frwk_agent"/>
+               <deny send_destination="org.bluez.frwk_agent"/>
+               <deny send_interface="org.bluez.Agent"/>
+               <deny send_destination="org.bluez.Agent"/>
+               <deny send_interface="org.bluez.Adapter"/>
+               <deny send_destination="org.bluez.Adapter"/>
+               <deny send_interface="org.bluez.Manager"/>
+               <deny send_destination="org.bluez.Manager"/>
+               <deny send_interface="org.bluez.Device"/>
+               <deny send_destination="org.bluez.Device"/>
+               <deny send_interface="org.bluez.Serial"/>
+               <deny send_destination="org.bluez.Serial"/>
+               <deny send_interface="org.bluez.SerialProxyManager"/>
+               <deny send_destination="org.bluez.SerialProxyManager"/>
+               <deny send_interface="org.bluez.SerialProxy"/>
+               <deny send_destination="org.bluez.SerialProxy"/>
+               <deny send_interface="org.bluez.NetworkServer"/>
+               <deny send_destination="org.bluez.NetworkServer"/>
+               <deny send_interface="org.bluez.Network"/>
+               <deny send_destination="org.bluez.Network"/>
+               <deny send_interface="org.bluez.AudioSink"/>
+               <deny send_destination="org.bluez.AudioSink"/>
+               <deny send_interface="org.bluez.Input"/>
+               <deny send_destination="org.bluez.Input"/>
+       </policy>
+</busconfig>
diff --git a/src/bluetooth.ver b/src/bluetooth.ver
new file mode 100644 (file)
index 0000000..b71c70d
--- /dev/null
@@ -0,0 +1,10 @@
+{
+       global:
+               btd_*;
+               g_dbus_*;
+               info;
+               error;
+               debug;
+       local:
+               *;
+};
diff --git a/src/bluetoothd.8.in b/src/bluetoothd.8.in
new file mode 100644 (file)
index 0000000..a7ae77b
--- /dev/null
@@ -0,0 +1,91 @@
+.\"
+.TH "BLUETOOTHD" "8" "March 2004" "Bluetooth daemon" "System management commands"
+.SH "NAME"
+bluetoothd \- Bluetooth daemon
+
+.SH "SYNOPSIS"
+.B bluetoothd
+[
+.B \-n
+]
+
+.SH "DESCRIPTION"
+This manual page documents briefly the
+.B bluetoothd
+daemon, which manages all the Bluetooth devices.
+.B bluetoothd
+itself does not accept many command\-line options, as most of its
+configuration is done in the
+.B @CONFIGDIR@/main.conf
+file, which has its own man page.
+.B bluetoothd
+can also provide a number of services via the D-Bus message bus
+system.
+.SH "OPTIONS"
+.TP
+.BI \-n
+Don't run as daemon in background.
+.TP
+.BI \-d
+Enable debug information output.
+.TP
+.BI \-m\ mtu\-size
+Use specific MTU size for SDP server.
+
+.SH "FILES"
+.TP
+.I @CONFIGDIR@/main.conf
+Default location of the global configuration file.
+
+.TP
+.I @STORAGEDIR@/nn:nn:nn:nn:nn:nn/linkkeys
+Default location for link keys of paired devices. The directory
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP
+is the address of the local device. The file is line separated, with
+the following columns separated by whitespace:
+
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address.
+
+\fInnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn\fP Link key.
+
+\fIn\fP Link type integer.
+
+.TP
+.I @STORAGEDIR@/nn:nn:nn:nn:nn:nn/names
+Default location for the device name cache. The directory
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP
+is the address of the local device. The file is line separated, with
+the following columns separated by whitespace:
+
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address.
+
+\fIname\fP Remote device name, terminated with newline.
+
+.TP
+.I @STORAGEDIR@/nn:nn:nn:nn:nn:nn/features
+Default location for the features cache. The directory
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP
+is the address of the local device. The file is line separated, with
+the following columns separated by whitespace:
+
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address.
+
+\fInnnnnnnnnnnnnnnn\fP Remote device LMP features coded as an 8 byte bitfield.
+
+.TP
+.I @STORAGEDIR@/nn:nn:nn:nn:nn:nn/manufacturers
+Default location for the manufacturers cache. The directory
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP
+is the address of the local device. The file is line separated, with
+the following columns separated by whitespace:
+
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address.
+
+\fIn\fP Remote device manufacturer integer.
+
+\fIn\fP Remote device LMP version integer.
+
+\fIn\fP Remote device LMP sub-version integer.
+
+.SH "AUTHOR"
+This manual page was written by Marcel Holtmann, Philipp Matthias Hahn and Fredrik Noring.
diff --git a/src/dbus-common.c b/src/dbus-common.c
new file mode 100644 (file)
index 0000000..8436100
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2005-2007  Johan Hedberg <johan.hedberg@nokia.com>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+
+#include "adapter.h"
+#include "manager.h"
+#include "event.h"
+#include "dbus-common.h"
+
+static DBusConnection *connection = NULL;
+
+static void append_variant(DBusMessageIter *iter, int type, void *val)
+{
+       DBusMessageIter value;
+       char sig[2] = { type, '\0' };
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value);
+
+       dbus_message_iter_append_basic(&value, type, val);
+
+       dbus_message_iter_close_container(iter, &value);
+}
+
+static void append_array_variant(DBusMessageIter *iter, int type, void *val,
+                                                       int n_elements)
+{
+       DBusMessageIter variant, array;
+       char type_sig[2] = { type, '\0' };
+       char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' };
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
+                                               array_sig, &variant);
+
+       dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
+                                               type_sig, &array);
+
+       if (dbus_type_is_fixed(type) == TRUE) {
+               dbus_message_iter_append_fixed_array(&array, type, val,
+                                                       n_elements);
+       } else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) {
+               const char ***str_array = val;
+               int i;
+
+               for (i = 0; i < n_elements; i++)
+                       dbus_message_iter_append_basic(&array, type,
+                                                       &((*str_array)[i]));
+       }
+
+       dbus_message_iter_close_container(&variant, &array);
+
+       dbus_message_iter_close_container(iter, &variant);
+}
+
+void dict_append_entry(DBusMessageIter *dict,
+                       const char *key, int type, void *val)
+{
+       DBusMessageIter entry;
+
+       if (type == DBUS_TYPE_STRING) {
+               const char *str = *((const char **) val);
+               if (str == NULL)
+                       return;
+       }
+
+       dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+                                                       NULL, &entry);
+
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+       append_variant(&entry, type, val);
+
+       dbus_message_iter_close_container(dict, &entry);
+}
+
+void dict_append_array(DBusMessageIter *dict, const char *key, int type,
+                       void *val, int n_elements)
+{
+       DBusMessageIter entry;
+
+       dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+                                               NULL, &entry);
+
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+       append_array_variant(&entry, type, val, n_elements);
+
+       dbus_message_iter_close_container(dict, &entry);
+}
+
+dbus_bool_t emit_property_changed(DBusConnection *conn,
+                                       const char *path,
+                                       const char *interface,
+                                       const char *name,
+                                       int type, void *value)
+{
+       DBusMessage *signal;
+       DBusMessageIter iter;
+
+       signal = dbus_message_new_signal(path, interface, "PropertyChanged");
+
+       if (!signal) {
+               error("Unable to allocate new %s.PropertyChanged signal",
+                               interface);
+               return FALSE;
+       }
+
+       dbus_message_iter_init_append(signal, &iter);
+
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name);
+
+       append_variant(&iter, type, value);
+
+       return g_dbus_send_message(conn, signal);
+}
+
+dbus_bool_t emit_array_property_changed(DBusConnection *conn,
+                                       const char *path,
+                                       const char *interface,
+                                       const char *name,
+                                       int type, void *value, int num)
+{
+       DBusMessage *signal;
+       DBusMessageIter iter;
+
+       signal = dbus_message_new_signal(path, interface, "PropertyChanged");
+
+       if (!signal) {
+               error("Unable to allocate new %s.PropertyChanged signal",
+                               interface);
+               return FALSE;
+       }
+
+       dbus_message_iter_init_append(signal, &iter);
+
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name);
+
+       append_array_variant(&iter, type, value, num);
+
+       return g_dbus_send_message(conn, signal);
+}
+
+void set_dbus_connection(DBusConnection *conn)
+{
+       connection = conn;
+}
+
+DBusConnection *get_dbus_connection(void)
+{
+       return connection;
+}
+
+const char *class_to_icon(uint32_t class)
+{
+       switch ((class & 0x1f00) >> 8) {
+       case 0x01:
+               return "computer";
+       case 0x02:
+               switch ((class & 0xfc) >> 2) {
+               case 0x01:
+               case 0x02:
+               case 0x03:
+               case 0x05:
+                       return "phone";
+               case 0x04:
+                       return "modem";
+               }
+               break;
+       case 0x03:
+               return "network-wireless";
+       case 0x04:
+               switch ((class & 0xfc) >> 2) {
+               case 0x01:
+               case 0x02:
+                       return "audio-card";    /* Headset */
+               case 0x06:
+                       return "audio-card";    /* Headphone */
+               case 0x0b: /* VCR */
+               case 0x0c: /* Video Camera */
+               case 0x0d: /* Camcorder */
+                       return "camera-video";
+               default:
+                       return "audio-card";    /* Other audio device */
+               }
+               break;
+       case 0x05:
+               switch ((class & 0xc0) >> 6) {
+               case 0x00:
+                       switch ((class & 0x1e) >> 2) {
+                       case 0x01:
+                       case 0x02:
+                               return "input-gaming";
+                       }
+                       break;
+               case 0x01:
+                       return "input-keyboard";
+               case 0x02:
+                       switch ((class & 0x1e) >> 2) {
+                       case 0x05:
+                               return "input-tablet";
+                       default:
+                               return "input-mouse";
+                       }
+               }
+               break;
+       case 0x06:
+               if (class & 0x80)
+                       return "printer";
+               if (class & 0x20)
+                       return "camera-photo";
+               break;
+       }
+
+       return NULL;
+}
diff --git a/src/dbus-common.h b/src/dbus-common.h
new file mode 100644 (file)
index 0000000..b196a1b
--- /dev/null
@@ -0,0 +1,47 @@
+/* *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 MAX_PATH_LENGTH 64
+
+void dict_append_entry(DBusMessageIter *dict,
+                       const char *key, int type, void *val);
+
+void dict_append_array(DBusMessageIter *dict, const char *key, int type,
+                       void *val, int n_elements);
+
+dbus_bool_t emit_property_changed(DBusConnection *conn,
+                                       const char *path,
+                                       const char *interface,
+                                       const char *name,
+                                       int type, void *value);
+
+dbus_bool_t emit_array_property_changed(DBusConnection *conn,
+                                       const char *path,
+                                       const char *interface,
+                                       const char *name,
+                                       int type, void *value, int num);
+
+void set_dbus_connection(DBusConnection *conn);
+DBusConnection *get_dbus_connection(void);
+
+const char *class_to_icon(uint32_t class);
diff --git a/src/device.c b/src/device.c
new file mode 100644 (file)
index 0000000..c9c47c6
--- /dev/null
@@ -0,0 +1,2843 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "textfile.h"
+
+#include "att.h"
+#include "hcid.h"
+#include "adapter.h"
+#include "device.h"
+#include "dbus-common.h"
+#include "event.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "agent.h"
+#include "sdp-xml.h"
+#include "storage.h"
+#include "btio.h"
+
+#define DISCONNECT_TIMER       2
+#define DISCOVERY_TIMER                2
+
+/* When all services should trust a remote device */
+#define GLOBAL_TRUST "[all]"
+
+struct btd_driver_data {
+       guint id;
+       struct btd_device_driver *driver;
+       void *priv;
+};
+
+struct btd_disconnect_data {
+       guint id;
+       disconnect_watch watch;
+       void *user_data;
+       GDestroyNotify destroy;
+};
+
+struct bonding_req {
+       DBusConnection *conn;
+       DBusMessage *msg;
+       GIOChannel *io;
+       guint listener_id;
+#ifdef __TIZEN_PATCH__
+       guint cancel_by_user;
+#endif
+       struct btd_device *device;
+};
+
+struct authentication_req {
+       auth_type_t type;
+       void *cb;
+       struct agent *agent;
+       struct btd_device *device;
+};
+
+struct browse_req {
+       DBusConnection *conn;
+       DBusMessage *msg;
+       GAttrib *attrib;
+       GIOChannel *io;
+       struct btd_device *device;
+       GSList *match_uuids;
+       GSList *profiles_added;
+       GSList *profiles_removed;
+       sdp_list_t *records;
+       int search_uuid;
+       int reconnect_attempt;
+       guint listener_id;
+};
+
+struct btd_device {
+       bdaddr_t        bdaddr;
+       device_type_t   type;
+       gchar           *path;
+       char            name[MAX_NAME_LENGTH + 1];
+       char            *alias;
+       struct btd_adapter      *adapter;
+       GSList          *uuids;
+       GSList          *services;              /* Primary services path */
+       GSList          *primaries;             /* List of primary services */
+       GSList          *drivers;               /* List of driver_data */
+       GSList          *watches;               /* List of disconnect_data */
+       gboolean        temporary;
+       struct agent    *agent;
+       guint           disconn_timer;
+       guint           discov_timer;
+       struct browse_req *browse;              /* service discover request */
+       struct bonding_req *bonding;
+       struct authentication_req *authr;       /* authentication request */
+       GSList          *disconnects;           /* disconnects message */
+
+       gboolean        connected;
+
+       /* Whether were creating a security mode 3 connection */
+       gboolean        secmode3;
+
+       sdp_list_t      *tmp_records;
+
+       gboolean        trusted;
+       gboolean        paired;
+       gboolean        blocked;
+
+       gboolean        authorizing;
+       gint            ref;
+};
+
+static uint16_t uuid_list[] = {
+       L2CAP_UUID,
+       PNP_INFO_SVCLASS_ID,
+       PUBLIC_BROWSE_GROUP,
+       0
+};
+
+static GSList *device_drivers = NULL;
+
+static void browse_request_free(struct browse_req *req)
+{
+       if (req->listener_id)
+               g_dbus_remove_watch(req->conn, req->listener_id);
+       if (req->msg)
+               dbus_message_unref(req->msg);
+       if (req->conn)
+               dbus_connection_unref(req->conn);
+       if (req->device)
+               btd_device_unref(req->device);
+       g_slist_foreach(req->profiles_added, (GFunc) g_free, NULL);
+       g_slist_free(req->profiles_added);
+       g_slist_free(req->profiles_removed);
+       if (req->records)
+               sdp_list_free(req->records, (sdp_free_func_t) sdp_record_free);
+
+       if (req->io) {
+               g_attrib_unref(req->attrib);
+               g_io_channel_unref(req->io);
+               g_io_channel_shutdown(req->io, FALSE, NULL);
+       }
+
+       g_free(req);
+}
+
+static void browse_request_cancel(struct browse_req *req)
+{
+       struct btd_device *device = req->device;
+       struct btd_adapter *adapter = device->adapter;
+       bdaddr_t src;
+
+       if (device_is_creating(device, NULL))
+               device_set_temporary(device, TRUE);
+
+       adapter_get_address(adapter, &src);
+
+       bt_cancel_discovery(&src, &device->bdaddr);
+
+       device->browse = NULL;
+       browse_request_free(req);
+}
+
+static void device_free(gpointer user_data)
+{
+       struct btd_device *device = user_data;
+       struct btd_adapter *adapter = device->adapter;
+       struct agent *agent = adapter_get_agent(adapter);
+
+       if (device->agent)
+               agent_free(device->agent);
+
+       if (agent && (agent_is_busy(agent, device) ||
+                               agent_is_busy(agent, device->authr)))
+               agent_cancel(agent);
+
+       g_slist_foreach(device->services, (GFunc) g_free, NULL);
+       g_slist_free(device->services);
+
+       g_slist_foreach(device->uuids, (GFunc) g_free, NULL);
+       g_slist_free(device->uuids);
+
+       g_slist_foreach(device->primaries, (GFunc) g_free, NULL);
+       g_slist_free(device->primaries);
+
+       if (device->tmp_records)
+               sdp_list_free(device->tmp_records,
+                                       (sdp_free_func_t) sdp_record_free);
+
+       if (device->disconn_timer)
+               g_source_remove(device->disconn_timer);
+
+       if (device->discov_timer)
+               g_source_remove(device->discov_timer);
+
+       DBG("%p", device);
+
+       g_free(device->authr);
+       g_free(device->path);
+       g_free(device->alias);
+       g_free(device);
+}
+
+gboolean device_is_paired(struct btd_device *device)
+{
+       return device->paired;
+}
+
+gboolean device_is_trusted(struct btd_device *device)
+{
+       return device->trusted;
+}
+
+#ifdef __TIZEN_PATCH__
+#include "oui.h"
+uint32_t device_pin_length(struct btd_device *device)
+{
+       struct btd_adapter *adapter = device->adapter;
+       bdaddr_t src;
+       uint8_t length;
+       int len;
+
+       adapter_get_address(adapter, &src);
+
+       len = read_pin_length(&src, &device->bdaddr);
+       if (len < 0)
+               len = 0;
+
+       length = (uint32_t)len;
+
+       return length;
+}
+
+int get_device_company(struct btd_device *device, char *company, size_t size)
+{
+       char oui[9], *tmp;
+       int err;
+
+       ba2oui(&device->bdaddr, oui);
+       tmp = (char*)ouitocomp(oui);
+
+       err = snprintf(company, size, "%s", tmp);
+
+       free(tmp);
+
+       return err;
+}
+
+int get_device_version_info(struct btd_device *device,
+                                       char *manufacturer, size_t manufacturer_size,
+                                       char *revision, size_t revision_size,
+                                       char *version, size_t version_size)
+{
+       struct btd_adapter *adapter = device->adapter;
+       int err;
+       bdaddr_t src;
+       char dstaddr[18], edr[7], *tmp;
+
+       uint16_t remote_manufacturer = 65534;
+       uint8_t remote_lmp_ver = 0;
+       uint16_t remote_lmp_subver = 0;
+       uint8_t features[8] = {0};
+
+       if (version_size < 14)
+               return -ENOBUFS;
+
+       adapter_get_address(adapter, &src);
+       ba2str(&device->bdaddr, dstaddr);
+
+       err = read_version_info(&src, dstaddr, &remote_manufacturer, &remote_lmp_ver, &remote_lmp_subver, features);
+       if (err < 0)
+               return err;
+
+       tmp = bt_compidtostr(remote_manufacturer);
+
+       err = snprintf(manufacturer, manufacturer_size, "%s", tmp);
+
+       snprintf(revision, revision_size, "HCI 0x%X", remote_lmp_subver);
+
+       if ((remote_lmp_ver == 0x03 || remote_lmp_ver == 0x04) &&
+                       (features[3] & (LMP_EDR_ACL_2M | LMP_EDR_ACL_3M)))
+               sprintf(edr, " + EDR");
+       else
+               edr[0] = '\0';
+
+       tmp = lmp_vertostr(remote_lmp_ver);
+
+       if (strlen(tmp) == 0)
+               err = snprintf(version, version_size, "not assigned");
+       else
+               err = snprintf(version, version_size, "Bluetooth %s%s", tmp, edr);
+
+       free(tmp);
+
+       return err;
+}
+#endif
+static DBusMessage *get_properties(DBusConnection *conn,
+                               DBusMessage *msg, void *user_data)
+{
+       struct btd_device *device = user_data;
+       struct btd_adapter *adapter = device->adapter;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       DBusMessageIter dict;
+       bdaddr_t src;
+       char name[MAX_NAME_LENGTH + 1], srcaddr[18], dstaddr[18];
+       char **str;
+       const char *ptr;
+       dbus_bool_t boolean;
+       uint32_t class;
+       int i;
+       GSList *l;
+
+#ifdef __TIZEN_PATCH__
+       uint32_t pin_length;
+#endif
+
+       ba2str(&device->bdaddr, dstaddr);
+
+       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);
+
+       /* Address */
+       ptr = dstaddr;
+       dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &ptr);
+
+       /* Name */
+       ptr = NULL;
+       memset(name, 0, sizeof(name));
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+
+       ptr = device->name;
+       dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &ptr);
+
+       /* Alias (fallback to name or address) */
+       if (device->alias != NULL)
+               ptr = device->alias;
+       else if (strlen(ptr) == 0) {
+               g_strdelimit(dstaddr, ":", '-');
+               ptr = dstaddr;
+       }
+
+       dict_append_entry(&dict, "Alias", DBUS_TYPE_STRING, &ptr);
+
+       /* Class */
+       if (read_remote_class(&src, &device->bdaddr, &class) == 0) {
+               const char *icon = class_to_icon(class);
+
+               dict_append_entry(&dict, "Class", DBUS_TYPE_UINT32, &class);
+
+               if (icon)
+                       dict_append_entry(&dict, "Icon",
+                                               DBUS_TYPE_STRING, &icon);
+       }
+
+       /* Paired */
+       boolean = device_is_paired(device);
+       dict_append_entry(&dict, "Paired", DBUS_TYPE_BOOLEAN, &boolean);
+
+#ifdef __TIZEN_PATCH__
+       /* PinLength */
+       pin_length = device_pin_length(device);
+       dict_append_entry(&dict, "PinLength", DBUS_TYPE_UINT32, &pin_length);
+#endif
+       /* Trusted */
+       boolean = device_is_trusted(device);
+       dict_append_entry(&dict, "Trusted", DBUS_TYPE_BOOLEAN, &boolean);
+
+       /* Blocked */
+       boolean = device->blocked;
+       dict_append_entry(&dict, "Blocked", DBUS_TYPE_BOOLEAN, &boolean);
+
+       /* Connected */
+       dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN,
+                                                       &device->connected);
+
+       /* UUIDs */
+       str = g_new0(char *, g_slist_length(device->uuids) + 1);
+       for (i = 0, l = device->uuids; l; l = l->next, i++)
+               str[i] = l->data;
+       dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, &str, i);
+       g_free(str);
+
+       /* Services */
+       str = g_new0(char *, g_slist_length(device->services) + 1);
+       for (i = 0, l = device->services; l; l = l->next, i++)
+               str[i] = l->data;
+       dict_append_array(&dict, "Services", DBUS_TYPE_OBJECT_PATH, &str, i);
+       g_free(str);
+
+       /* Adapter */
+       ptr = adapter_get_path(adapter);
+       dict_append_entry(&dict, "Adapter", DBUS_TYPE_OBJECT_PATH, &ptr);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+#ifdef __TIZEN_PATCH__
+static DBusMessage *get_version_properties(DBusConnection *conn,
+                               DBusMessage *msg, void *user_data)
+{
+       struct btd_device *device = user_data;
+       struct btd_adapter *adapter = device->adapter;
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       DBusMessageIter dict;
+       bdaddr_t src;
+       char name[MAX_NAME_LENGTH + 1], srcaddr[18], dstaddr[18];
+       const char *ptr;
+       uint32_t class;
+
+       char company[MAX_NAME_LENGTH + 1];
+       char manufacturer[MAX_NAME_LENGTH + 1];
+       char revision[MAX_NAME_LENGTH + 1];
+       char version[MAX_NAME_LENGTH + 1];
+
+       ba2str(&device->bdaddr, dstaddr);
+
+       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);
+
+       /* Address */
+       ptr = dstaddr;
+       dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &ptr);
+
+       /* Name */
+       ptr = NULL;
+       memset(name, 0, sizeof(name));
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+       if (g_str_equal("", device->name))
+       {
+               // use address itself instead name
+           ptr = dstaddr;
+       }
+       else
+       {
+               ptr = device->name;
+       }
+       dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &ptr);
+
+       /* Class */
+       if (read_remote_class(&src, &device->bdaddr, &class) == 0) {
+               dict_append_entry(&dict, "Class", DBUS_TYPE_UINT32, &class);
+       }
+
+       /* Company */
+       memset(company, 0, sizeof(company));
+       get_device_company(device, company, sizeof(company));
+       ptr = company;
+       dict_append_entry(&dict, "Company", DBUS_TYPE_STRING, &ptr);
+
+       /* Manufacturer */
+       memset(manufacturer, 0, sizeof(manufacturer));
+       memset(manufacturer, 0, sizeof(revision));
+       memset(manufacturer, 0, sizeof(version));
+       get_device_version_info(device, manufacturer, sizeof(manufacturer), revision, sizeof(revision), version, sizeof(version));
+
+       ptr = manufacturer;
+       dict_append_entry(&dict, "Manufacturer", DBUS_TYPE_STRING, &ptr);
+
+       ptr = revision;
+       dict_append_entry(&dict, "Revision", DBUS_TYPE_STRING, &ptr);
+
+       ptr = version;
+       dict_append_entry(&dict, "Version", DBUS_TYPE_STRING, &ptr);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+#endif
+
+static DBusMessage *set_alias(DBusConnection *conn, DBusMessage *msg,
+                                       const char *alias, void *data)
+{
+       struct btd_device *device = data;
+       struct btd_adapter *adapter = device->adapter;
+       char srcaddr[18], dstaddr[18];
+       bdaddr_t src;
+       int err;
+
+       /* No change */
+       if ((device->alias == NULL && g_str_equal(alias, "")) ||
+                       g_strcmp0(device->alias, alias) == 0)
+               return dbus_message_new_method_return(msg);
+
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+       ba2str(&device->bdaddr, dstaddr);
+
+       /* Remove alias if empty string */
+       err = write_device_alias(srcaddr, dstaddr,
+                       g_str_equal(alias, "") ? NULL : alias);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       g_free(device->alias);
+       device->alias = g_str_equal(alias, "") ? NULL : g_strdup(alias);
+
+       emit_property_changed(conn, dbus_message_get_path(msg),
+                               DEVICE_INTERFACE, "Alias",
+                               DBUS_TYPE_STRING, &alias);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_trust(DBusConnection *conn, DBusMessage *msg,
+                                       gboolean value, void *data)
+{
+       struct btd_device *device = data;
+       struct btd_adapter *adapter = device->adapter;
+       char srcaddr[18], dstaddr[18];
+       bdaddr_t src;
+       int err;
+
+       if (device->trusted == value)
+               return dbus_message_new_method_return(msg);
+
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+       ba2str(&device->bdaddr, dstaddr);
+
+       err = write_trust(srcaddr, dstaddr, GLOBAL_TRUST, value);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       device->trusted = value;
+
+       emit_property_changed(conn, dbus_message_get_path(msg),
+                               DEVICE_INTERFACE, "Trusted",
+                               DBUS_TYPE_BOOLEAN, &value);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static void driver_remove(struct btd_driver_data *driver_data,
+                                               struct btd_device *device)
+{
+       struct btd_device_driver *driver = driver_data->driver;
+
+       driver->remove(device);
+
+       device->drivers = g_slist_remove(device->drivers, driver_data);
+       g_free(driver_data);
+}
+
+static gboolean do_disconnect(gpointer user_data)
+{
+       struct btd_device *device = user_data;
+
+       device->disconn_timer = 0;
+
+       btd_adapter_disconnect_device(device->adapter, &device->bdaddr);
+
+       return FALSE;
+}
+
+static int device_block(DBusConnection *conn, struct btd_device *device)
+{
+       int err;
+       bdaddr_t src;
+
+       if (device->blocked)
+               return 0;
+
+       if (device->connected)
+               do_disconnect(device);
+
+       g_slist_foreach(device->drivers, (GFunc) driver_remove, device);
+
+       err = btd_adapter_block_address(device->adapter, &device->bdaddr);
+       if (err < 0)
+               return err;
+
+       device->blocked = TRUE;
+
+       adapter_get_address(device->adapter, &src);
+
+       err = write_blocked(&src, &device->bdaddr, TRUE);
+       if (err < 0)
+               error("write_blocked(): %s (%d)", strerror(-err), -err);
+
+       device_set_temporary(device, FALSE);
+
+       emit_property_changed(conn, device->path, DEVICE_INTERFACE, "Blocked",
+                                       DBUS_TYPE_BOOLEAN, &device->blocked);
+
+       return 0;
+}
+
+static int device_unblock(DBusConnection *conn, struct btd_device *device,
+                                                       gboolean silent)
+{
+       int err;
+       bdaddr_t src;
+
+       if (!device->blocked)
+               return 0;
+
+       err = btd_adapter_unblock_address(device->adapter, &device->bdaddr);
+       if (err < 0)
+               return err;
+
+       device->blocked = FALSE;
+
+       adapter_get_address(device->adapter, &src);
+
+       err = write_blocked(&src, &device->bdaddr, FALSE);
+       if (err < 0)
+               error("write_blocked(): %s (%d)", strerror(-err), -err);
+
+       if (!silent) {
+               emit_property_changed(conn, device->path,
+                                       DEVICE_INTERFACE, "Blocked",
+                                       DBUS_TYPE_BOOLEAN, &device->blocked);
+               device_probe_drivers(device, device->uuids);
+       }
+
+       return 0;
+}
+
+static DBusMessage *set_blocked(DBusConnection *conn, DBusMessage *msg,
+                                               gboolean value, void *data)
+{
+       struct btd_device *device = data;
+       int err;
+
+       if (value)
+               err = device_block(conn, device);
+       else
+               err = device_unblock(conn, device, FALSE);
+
+       switch (-err) {
+       case 0:
+               return dbus_message_new_method_return(msg);
+       case EINVAL:
+               return btd_error_failed(msg, "Kernel lacks blacklist support");
+       default:
+               return btd_error_failed(msg, strerror(-err));
+       }
+}
+
+static DBusMessage *set_property(DBusConnection *conn,
+                               DBusMessage *msg, void *data)
+{
+       DBusMessageIter iter;
+       DBusMessageIter sub;
+       const char *property;
+
+       if (!dbus_message_iter_init(msg, &iter))
+               return btd_error_invalid_args(msg);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+               return btd_error_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 btd_error_invalid_args(msg);
+       dbus_message_iter_recurse(&iter, &sub);
+
+       if (g_str_equal("Trusted", property)) {
+               dbus_bool_t value;
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+                       return btd_error_invalid_args(msg);
+               dbus_message_iter_get_basic(&sub, &value);
+
+               return set_trust(conn, msg, value, data);
+       } else if (g_str_equal("Alias", property)) {
+               const char *alias;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
+                       return btd_error_invalid_args(msg);
+               dbus_message_iter_get_basic(&sub, &alias);
+
+               return set_alias(conn, msg, alias, data);
+       } else if (g_str_equal("Blocked", property)) {
+               dbus_bool_t value;
+
+               if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+                       return btd_error_invalid_args(msg);
+
+               dbus_message_iter_get_basic(&sub, &value);
+
+               return set_blocked(conn, msg, value, data);
+       }
+
+       return btd_error_invalid_args(msg);
+}
+
+static void discover_services_req_exit(DBusConnection *conn, void *user_data)
+{
+       struct browse_req *req = user_data;
+
+       DBG("DiscoverServices requestor exited");
+
+       browse_request_cancel(req);
+}
+
+static DBusMessage *discover_services(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct btd_device *device = user_data;
+       const char *pattern;
+       int err;
+
+       if (device->browse)
+               return btd_error_in_progress(msg);
+
+       if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               return btd_error_invalid_args(msg);
+
+       if (strlen(pattern) == 0) {
+               err = device_browse_sdp(device, conn, msg, NULL, FALSE);
+               if (err < 0)
+                       goto fail;
+       } else {
+               uuid_t uuid;
+
+               if (bt_string2uuid(&uuid, pattern) < 0)
+                       return btd_error_invalid_args(msg);
+
+               sdp_uuid128_to_uuid(&uuid);
+
+               err = device_browse_sdp(device, conn, msg, &uuid, FALSE);
+               if (err < 0)
+                       goto fail;
+       }
+
+       return NULL;
+
+fail:
+       return btd_error_failed(msg, strerror(-err));
+}
+
+static const char *browse_request_get_requestor(struct browse_req *req)
+{
+       if (!req->msg)
+               return NULL;
+
+       return dbus_message_get_sender(req->msg);
+}
+
+static void iter_append_record(DBusMessageIter *dict, uint32_t handle,
+                                                       const char *record)
+{
+       DBusMessageIter entry;
+
+       dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+                                                       NULL, &entry);
+
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_UINT32, &handle);
+
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &record);
+
+       dbus_message_iter_close_container(dict, &entry);
+}
+
+static void discover_services_reply(struct browse_req *req, int err,
+                                                       sdp_list_t *recs)
+{
+       DBusMessage *reply;
+       DBusMessageIter iter, dict;
+       sdp_list_t *seq;
+
+       if (err) {
+               const char *err_if;
+
+               if (err == -EHOSTDOWN)
+                       err_if = ERROR_INTERFACE ".ConnectionAttemptFailed";
+               else
+                       err_if = ERROR_INTERFACE ".Failed";
+
+               reply = dbus_message_new_error(req->msg, err_if,
+                                                       strerror(-err));
+               g_dbus_send_message(req->conn, reply);
+               return;
+       }
+
+       reply = dbus_message_new_method_return(req->msg);
+       if (!reply)
+               return;
+
+       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_UINT32_AS_STRING DBUS_TYPE_STRING_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+       for (seq = recs; seq; seq = seq->next) {
+               sdp_record_t *rec = (sdp_record_t *) seq->data;
+               GString *result;
+
+               if (!rec)
+                       break;
+
+               result = g_string_new(NULL);
+
+               convert_sdp_record_to_xml(rec, result,
+                               (void *) g_string_append);
+
+               if (result->len)
+                       iter_append_record(&dict, rec->handle, result->str);
+
+               g_string_free(result, TRUE);
+       }
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       g_dbus_send_message(req->conn, reply);
+}
+
+static DBusMessage *cancel_discover(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct btd_device *device = user_data;
+       const char *sender = dbus_message_get_sender(msg);
+       const char *requestor;
+
+       if (!device->browse)
+               return btd_error_does_not_exist(msg);
+
+       if (!dbus_message_is_method_call(device->browse->msg, DEVICE_INTERFACE,
+                                       "DiscoverServices"))
+               return btd_error_not_authorized(msg);
+
+       requestor = browse_request_get_requestor(device->browse);
+
+       /* only the discover requestor can cancel the inquiry process */
+       if (!requestor || !g_str_equal(requestor, sender))
+               return btd_error_not_authorized(msg);
+
+       discover_services_reply(device->browse, -ECANCELED, NULL);
+
+       browse_request_cancel(device->browse);
+
+       return dbus_message_new_method_return(msg);
+}
+
+static void bonding_request_cancel(struct bonding_req *bonding)
+{
+       struct btd_device *device = bonding->device;
+       struct btd_adapter *adapter = device->adapter;
+
+       adapter_cancel_bonding(adapter, &device->bdaddr);
+}
+
+void device_request_disconnect(struct btd_device *device, DBusMessage *msg)
+{
+       DBusConnection *conn = get_dbus_connection();
+
+       if (device->bonding)
+               bonding_request_cancel(device->bonding);
+
+       if (device->browse) {
+               discover_services_reply(device->browse, -ECANCELED, NULL);
+               browse_request_cancel(device->browse);
+       }
+
+       if (msg)
+               device->disconnects = g_slist_append(device->disconnects,
+                                               dbus_message_ref(msg));
+
+       if (device->disconn_timer)
+               return;
+
+       while (device->watches) {
+               struct btd_disconnect_data *data = device->watches->data;
+
+               if (data->watch)
+                       /* temporary is set if device is going to be removed */
+                       data->watch(device, device->temporary,
+                                                       data->user_data);
+
+               /* Check if the watch has been removed by callback function */
+               if (!g_slist_find(device->watches, data))
+                       continue;
+
+               device->watches = g_slist_remove(device->watches, data);
+               g_free(data);
+       }
+
+       device->disconn_timer = g_timeout_add_seconds(DISCONNECT_TIMER,
+                                               do_disconnect, device);
+
+       g_dbus_emit_signal(conn, device->path,
+                       DEVICE_INTERFACE, "DisconnectRequested",
+                       DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *disconnect(DBusConnection *conn, DBusMessage *msg,
+                                                       void *user_data)
+{
+       struct btd_device *device = user_data;
+
+       if (!device->connected)
+               return btd_error_not_connected(msg);
+
+       device_request_disconnect(device, msg);
+
+       return NULL;
+}
+
+static GDBusMethodTable device_methods[] = {
+       { "GetProperties",      "",     "a{sv}",        get_properties  },
+#ifdef __TIZEN_PATCH__
+       { "GetVersionProperties",       "",     "a{sv}",        get_version_properties  },
+#endif
+       { "SetProperty",        "sv",   "",             set_property    },
+       { "DiscoverServices",   "s",    "a{us}",        discover_services,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { "CancelDiscovery",    "",     "",             cancel_discover },
+       { "Disconnect",         "",     "",             disconnect,
+                                               G_DBUS_METHOD_FLAG_ASYNC},
+       { }
+};
+
+static GDBusSignalTable device_signals[] = {
+       { "PropertyChanged",            "sv"    },
+       { "DisconnectRequested",        ""      },
+       { }
+};
+
+gboolean device_is_connected(struct btd_device *device)
+{
+       return device->connected;
+}
+
+void device_add_connection(struct btd_device *device, DBusConnection *conn)
+{
+       if (device->connected) {
+               char addr[18];
+               ba2str(&device->bdaddr, addr);
+               error("Device %s is already connected", addr);
+               return;
+       }
+
+       device->connected = TRUE;
+
+       emit_property_changed(conn, device->path,
+                                       DEVICE_INTERFACE, "Connected",
+                                       DBUS_TYPE_BOOLEAN, &device->connected);
+}
+
+void device_remove_connection(struct btd_device *device, DBusConnection *conn)
+{
+       if (!device->connected) {
+               char addr[18];
+               ba2str(&device->bdaddr, addr);
+               error("Device %s isn't connected", addr);
+               return;
+       }
+
+       device->connected = FALSE;
+
+       if (device->disconn_timer > 0) {
+               g_source_remove(device->disconn_timer);
+               device->disconn_timer = 0;
+       }
+
+       while (device->disconnects) {
+               DBusMessage *msg = device->disconnects->data;
+
+               g_dbus_send_reply(conn, msg, DBUS_TYPE_INVALID);
+               device->disconnects = g_slist_remove(device->disconnects, msg);
+       }
+
+       emit_property_changed(conn, device->path,
+                                       DEVICE_INTERFACE, "Connected",
+                                       DBUS_TYPE_BOOLEAN, &device->connected);
+}
+
+guint device_add_disconnect_watch(struct btd_device *device,
+                               disconnect_watch watch, void *user_data,
+                               GDestroyNotify destroy)
+{
+       struct btd_disconnect_data *data;
+       static guint id = 0;
+
+       data = g_new0(struct btd_disconnect_data, 1);
+       data->id = ++id;
+       data->watch = watch;
+       data->user_data = user_data;
+       data->destroy = destroy;
+
+       device->watches = g_slist_append(device->watches, data);
+
+       return data->id;
+}
+
+void device_remove_disconnect_watch(struct btd_device *device, guint id)
+{
+       GSList *l;
+
+       for (l = device->watches; l; l = l->next) {
+               struct btd_disconnect_data *data = l->data;
+
+               if (data->id == id) {
+                       device->watches = g_slist_remove(device->watches,
+                                                       data);
+                       if (data->destroy)
+                               data->destroy(data->user_data);
+                       g_free(data);
+                       return;
+               }
+       }
+}
+
+struct btd_device *device_create(DBusConnection *conn,
+                               struct btd_adapter *adapter,
+                               const gchar *address, device_type_t type)
+{
+       gchar *address_up;
+       struct btd_device *device;
+       const gchar *adapter_path = adapter_get_path(adapter);
+       bdaddr_t src;
+       char srcaddr[18], alias[MAX_NAME_LENGTH + 1];
+
+       device = g_try_malloc0(sizeof(struct btd_device));
+       if (device == NULL)
+               return NULL;
+
+       address_up = g_ascii_strup(address, -1);
+       device->path = g_strdup_printf("%s/dev_%s", adapter_path, address_up);
+       g_strdelimit(device->path, ":", '_');
+       g_free(address_up);
+
+       DBG("Creating device %s", device->path);
+
+       if (g_dbus_register_interface(conn, device->path, DEVICE_INTERFACE,
+                               device_methods, device_signals, NULL,
+                               device, device_free) == FALSE) {
+               device_free(device);
+               return NULL;
+       }
+
+       str2ba(address, &device->bdaddr);
+       device->adapter = adapter;
+       device->type = type;
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+       read_device_name(srcaddr, address, device->name);
+       if (read_device_alias(srcaddr, address, alias, sizeof(alias)) == 0)
+               device->alias = g_strdup(alias);
+       device->trusted = read_trust(&src, address, GLOBAL_TRUST);
+
+       if (read_blocked(&src, &device->bdaddr))
+               device_block(conn, device);
+
+       if (read_link_key(&src, &device->bdaddr, NULL, NULL) == 0)
+               device->paired = TRUE;
+
+       return btd_device_ref(device);
+}
+
+void device_set_name(struct btd_device *device, const char *name)
+{
+       DBusConnection *conn = get_dbus_connection();
+
+       if (strncmp(name, device->name, MAX_NAME_LENGTH) == 0)
+               return;
+
+       strncpy(device->name, name, MAX_NAME_LENGTH);
+
+       emit_property_changed(conn, device->path,
+                               DEVICE_INTERFACE, "Name",
+                               DBUS_TYPE_STRING, &name);
+
+       if (device->alias != NULL)
+               return;
+
+       emit_property_changed(conn, device->path,
+                               DEVICE_INTERFACE, "Alias",
+                               DBUS_TYPE_STRING, &name);
+}
+
+void device_get_name(struct btd_device *device, char *name, size_t len)
+{
+       strncpy(name, device->name, len);
+}
+
+device_type_t device_get_type(struct btd_device *device)
+{
+       return device->type;
+}
+
+void device_remove_bonding(struct btd_device *device)
+{
+       char filename[PATH_MAX + 1];
+       char srcaddr[18], dstaddr[18];
+       bdaddr_t bdaddr;
+
+       adapter_get_address(device->adapter, &bdaddr);
+       ba2str(&bdaddr, srcaddr);
+       ba2str(&device->bdaddr, dstaddr);
+
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr,
+                       "linkkeys");
+
+       /* Delete the link key from storage */
+       textfile_casedel(filename, dstaddr);
+
+       btd_adapter_remove_bonding(device->adapter, &device->bdaddr);
+}
+
+static void device_remove_stored(struct btd_device *device)
+{
+       bdaddr_t src;
+       char addr[18];
+       DBusConnection *conn = get_dbus_connection();
+
+       adapter_get_address(device->adapter, &src);
+       ba2str(&device->bdaddr, addr);
+
+       if (device->paired)
+               device_remove_bonding(device);
+       delete_entry(&src, "profiles", addr);
+       delete_entry(&src, "trusts", addr);
+       delete_entry(&src, "types", addr);
+       delete_entry(&src, "primary", addr);
+       delete_all_records(&src, &device->bdaddr);
+       delete_device_service(&src, &device->bdaddr);
+
+       if (device->blocked)
+               device_unblock(conn, device, TRUE);
+}
+
+void device_remove(struct btd_device *device, gboolean remove_stored)
+{
+
+       DBG("Removing device %s", device->path);
+
+       if (device->agent)
+               agent_free(device->agent);
+
+       if (device->bonding) {
+               uint8_t status;
+
+               if (device->connected)
+                       status = HCI_OE_USER_ENDED_CONNECTION;
+               else
+                       status = HCI_PAGE_TIMEOUT;
+
+               device_cancel_bonding(device, status);
+       }
+
+       if (device->browse) {
+               discover_services_reply(device->browse, -ECANCELED, NULL);
+               browse_request_cancel(device->browse);
+       }
+
+       if (device->connected)
+               do_disconnect(device);
+
+       if (remove_stored)
+               device_remove_stored(device);
+
+       g_slist_foreach(device->drivers, (GFunc) driver_remove, device);
+       g_slist_free(device->drivers);
+       device->drivers = NULL;
+
+       btd_device_unref(device);
+}
+
+gint device_address_cmp(struct btd_device *device, const gchar *address)
+{
+       char addr[18];
+
+       ba2str(&device->bdaddr, addr);
+       return strcasecmp(addr, address);
+}
+
+static gboolean record_has_uuid(const sdp_record_t *rec,
+                               const char *profile_uuid)
+{
+       sdp_list_t *pat;
+
+       for (pat = rec->pattern; pat != NULL; pat = pat->next) {
+               char *uuid;
+               int ret;
+
+               uuid = bt_uuid2string(pat->data);
+               if (!uuid)
+                       continue;
+
+               ret = strcasecmp(uuid, profile_uuid);
+
+               g_free(uuid);
+
+               if (ret == 0)
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static GSList *device_match_pattern(struct btd_device *device,
+                                       const char *match_uuid,
+                                       GSList *profiles)
+{
+       GSList *l, *uuids = NULL;
+
+       for (l = profiles; l; l = l->next) {
+               char *profile_uuid = l->data;
+               const sdp_record_t *rec;
+
+               rec = btd_device_get_record(device, profile_uuid);
+               if (!rec)
+                       continue;
+
+               if (record_has_uuid(rec, match_uuid))
+                       uuids = g_slist_append(uuids, profile_uuid);
+       }
+
+       return uuids;
+}
+
+static GSList *device_match_driver(struct btd_device *device,
+                                       struct btd_device_driver *driver,
+                                       GSList *profiles)
+{
+       const char **uuid;
+       GSList *uuids = NULL;
+
+       for (uuid = driver->uuids; *uuid; uuid++) {
+               GSList *match;
+
+               /* skip duplicated uuids */
+               if (g_slist_find_custom(uuids, *uuid,
+                               (GCompareFunc) strcasecmp))
+                       continue;
+
+               /* match profile driver */
+               match = g_slist_find_custom(profiles, *uuid,
+                                       (GCompareFunc) strcasecmp);
+               if (match) {
+                       uuids = g_slist_append(uuids, match->data);
+                       continue;
+               }
+
+               /* match pattern driver */
+               match = device_match_pattern(device, *uuid, profiles);
+               for (; match; match = match->next)
+                       uuids = g_slist_append(uuids, match->data);
+       }
+
+       return uuids;
+}
+
+void device_probe_drivers(struct btd_device *device, GSList *profiles)
+{
+       GSList *list;
+       char addr[18];
+       int err;
+
+       ba2str(&device->bdaddr, addr);
+
+       if (device->blocked) {
+               DBG("Skipping drivers for blocked device %s", addr);
+               goto add_uuids;
+       }
+
+       DBG("Probing drivers for %s", addr);
+
+       for (list = device_drivers; list; list = list->next) {
+               struct btd_device_driver *driver = list->data;
+               GSList *probe_uuids;
+               struct btd_driver_data *driver_data;
+
+               probe_uuids = device_match_driver(device, driver, profiles);
+
+               if (!probe_uuids)
+                       continue;
+
+               driver_data = g_new0(struct btd_driver_data, 1);
+
+               err = driver->probe(device, probe_uuids);
+               if (err < 0) {
+                       error("%s driver probe failed for device %s",
+                                                       driver->name, addr);
+                       g_free(driver_data);
+                       g_slist_free(probe_uuids);
+                       continue;
+               }
+
+               driver_data->driver = driver;
+               device->drivers = g_slist_append(device->drivers, driver_data);
+               g_slist_free(probe_uuids);
+       }
+
+add_uuids:
+       for (list = profiles; list; list = list->next) {
+               GSList *l = g_slist_find_custom(device->uuids, list->data,
+                                               (GCompareFunc) strcasecmp);
+               if (l)
+                       continue;
+
+               device->uuids = g_slist_insert_sorted(device->uuids,
+                                               g_strdup(list->data),
+                                               (GCompareFunc) strcasecmp);
+       }
+}
+
+static void device_remove_drivers(struct btd_device *device, GSList *uuids)
+{
+       struct btd_adapter *adapter = device_get_adapter(device);
+       GSList *list, *next;
+       char srcaddr[18], dstaddr[18];
+       bdaddr_t src;
+       sdp_list_t *records;
+
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+       ba2str(&device->bdaddr, dstaddr);
+
+       records = read_records(&src, &device->bdaddr);
+
+       DBG("Removing drivers for %s", dstaddr);
+
+       for (list = device->drivers; list; list = next) {
+               struct btd_driver_data *driver_data = list->data;
+               struct btd_device_driver *driver = driver_data->driver;
+               const char **uuid;
+
+               next = list->next;
+
+               for (uuid = driver->uuids; *uuid; uuid++) {
+                       if (!g_slist_find_custom(uuids, *uuid,
+                                               (GCompareFunc) strcasecmp))
+                               continue;
+
+                       DBG("UUID %s was removed from device %s",
+                                                       *uuid, dstaddr);
+
+                       driver->remove(device);
+                       device->drivers = g_slist_remove(device->drivers,
+                                                               driver_data);
+                       g_free(driver_data);
+
+                       break;
+               }
+       }
+
+       for (list = uuids; list; list = list->next) {
+               sdp_record_t *rec;
+
+               device->uuids = g_slist_remove(device->uuids, list->data);
+
+               rec = find_record_in_list(records, list->data);
+               if (!rec)
+                       continue;
+
+               delete_record(srcaddr, dstaddr, rec->handle);
+
+               records = sdp_list_remove(records, rec);
+               sdp_record_free(rec);
+
+       }
+
+       if (records)
+               sdp_list_free(records, (sdp_free_func_t) sdp_record_free);
+}
+
+static void services_changed(struct btd_device *device)
+{
+       DBusConnection *conn = get_dbus_connection();
+       char **uuids;
+       GSList *l;
+       int i;
+
+       uuids = g_new0(char *, g_slist_length(device->uuids) + 1);
+       for (i = 0, l = device->uuids; l; l = l->next, i++)
+               uuids[i] = l->data;
+
+       emit_array_property_changed(conn, device->path, DEVICE_INTERFACE,
+                                       "UUIDs", DBUS_TYPE_STRING, &uuids, i);
+
+       g_free(uuids);
+}
+
+static int rec_cmp(const void *a, const void *b)
+{
+       const sdp_record_t *r1 = a;
+       const sdp_record_t *r2 = b;
+
+       return r1->handle - r2->handle;
+}
+
+static void update_services(struct browse_req *req, sdp_list_t *recs)
+{
+       struct btd_device *device = req->device;
+       struct btd_adapter *adapter = device_get_adapter(device);
+       sdp_list_t *seq;
+       char srcaddr[18], dstaddr[18];
+       bdaddr_t src;
+
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+       ba2str(&device->bdaddr, dstaddr);
+
+       for (seq = recs; seq; seq = seq->next) {
+               sdp_record_t *rec = (sdp_record_t *) seq->data;
+               sdp_list_t *svcclass = NULL;
+               gchar *profile_uuid;
+               GSList *l;
+
+               if (!rec)
+                       break;
+
+               if (sdp_get_service_classes(rec, &svcclass) < 0)
+                       continue;
+
+               /* Check for empty service classes list */
+               if (svcclass == NULL) {
+                       DBG("Skipping record with no service classes");
+                       continue;
+               }
+
+               /* Extract the first element and skip the remainning */
+               profile_uuid = bt_uuid2string(svcclass->data);
+               if (!profile_uuid) {
+                       sdp_list_free(svcclass, free);
+                       continue;
+               }
+
+               if (!strcasecmp(profile_uuid, PNP_UUID)) {
+                       uint16_t source, vendor, product, version;
+                       sdp_data_t *pdlist;
+
+                       pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE);
+                       source = pdlist ? pdlist->val.uint16 : 0x0000;
+
+                       pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID);
+                       vendor = pdlist ? pdlist->val.uint16 : 0x0000;
+
+                       pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID);
+                       product = pdlist ? pdlist->val.uint16 : 0x0000;
+
+                       pdlist = sdp_data_get(rec, SDP_ATTR_VERSION);
+                       version = pdlist ? pdlist->val.uint16 : 0x0000;
+
+                       if (source || vendor || product || version)
+                               store_device_id(srcaddr, dstaddr, source,
+                                               vendor, product, version);
+               }
+
+               /* Check for duplicates */
+               if (sdp_list_find(req->records, rec, rec_cmp)) {
+                       g_free(profile_uuid);
+                       sdp_list_free(svcclass, free);
+                       continue;
+               }
+
+               store_record(srcaddr, dstaddr, rec);
+
+               /* Copy record */
+               req->records = sdp_list_append(req->records,
+                                                       sdp_copy_record(rec));
+
+               l = g_slist_find_custom(device->uuids, profile_uuid,
+                               (GCompareFunc) strcmp);
+               if (!l)
+                       req->profiles_added =
+                               g_slist_append(req->profiles_added,
+                                               profile_uuid);
+               else {
+                       req->profiles_removed =
+                               g_slist_remove(req->profiles_removed,
+                                               l->data);
+                       g_free(profile_uuid);
+               }
+
+               sdp_list_free(svcclass, free);
+       }
+}
+
+static void store_profiles(struct btd_device *device)
+{
+       struct btd_adapter *adapter = device->adapter;
+       bdaddr_t src;
+       char *str;
+
+       adapter_get_address(adapter, &src);
+
+       if (!device->uuids) {
+               write_device_profiles(&src, &device->bdaddr, "");
+               return;
+       }
+
+       str = bt_list2string(device->uuids);
+       write_device_profiles(&src, &device->bdaddr, str);
+       g_free(str);
+}
+
+static void create_device_reply(struct btd_device *device, struct browse_req *req)
+{
+       DBusMessage *reply;
+
+       reply = dbus_message_new_method_return(req->msg);
+       if (!reply)
+               return;
+
+       dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &device->path,
+                                       DBUS_TYPE_INVALID);
+
+       g_dbus_send_message(req->conn, reply);
+}
+
+static void search_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+       struct browse_req *req = user_data;
+       struct btd_device *device = req->device;
+       char addr[18];
+
+       ba2str(&device->bdaddr, addr);
+
+       if (err < 0) {
+               error("%s: error updating services: %s (%d)",
+                               addr, strerror(-err), -err);
+               goto send_reply;
+       }
+
+       update_services(req, recs);
+
+       if (device->tmp_records)
+               sdp_list_free(device->tmp_records,
+                                       (sdp_free_func_t) sdp_record_free);
+
+       device->tmp_records = req->records;
+       req->records = NULL;
+
+       if (!req->profiles_added && !req->profiles_removed) {
+               DBG("%s: No service update", addr);
+               goto send_reply;
+       }
+
+       /* Probe matching drivers for services added */
+       if (req->profiles_added)
+               device_probe_drivers(device, req->profiles_added);
+
+       /* Remove drivers for services removed */
+       if (req->profiles_removed)
+               device_remove_drivers(device, req->profiles_removed);
+
+       /* Propagate services changes */
+       services_changed(req->device);
+
+send_reply:
+       if (!req->msg)
+               goto cleanup;
+
+       if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE,
+                                       "DiscoverServices"))
+               discover_services_reply(req, err, device->tmp_records);
+       else if (dbus_message_is_method_call(req->msg, ADAPTER_INTERFACE,
+                                               "CreatePairedDevice"))
+               create_device_reply(device, req);
+       else if (dbus_message_is_method_call(req->msg, ADAPTER_INTERFACE,
+                                               "CreateDevice")) {
+               if (err < 0) {
+                       DBusMessage *reply;
+                       reply = btd_error_failed(req->msg, strerror(-err));
+                       g_dbus_send_message(req->conn, reply);
+                       goto cleanup;
+               }
+
+               create_device_reply(device, req);
+               device_set_temporary(device, FALSE);
+       }
+
+cleanup:
+       if (!device->temporary) {
+               bdaddr_t sba, dba;
+
+               adapter_get_address(device->adapter, &sba);
+               device_get_address(device, &dba);
+
+               store_profiles(device);
+               write_device_type(&sba, &dba, device->type);
+       }
+
+       device->browse = NULL;
+       browse_request_free(req);
+}
+
+static void browse_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+       struct browse_req *req = user_data;
+       struct btd_device *device = req->device;
+       struct btd_adapter *adapter = device->adapter;
+       bdaddr_t src;
+       uuid_t uuid;
+
+       /* If we have a valid response and req->search_uuid == 2, then L2CAP
+        * UUID & PNP searching was successful -- we are done */
+       if (err < 0 || (req->search_uuid == 2 && req->records)) {
+               if (err == -ECONNRESET && req->reconnect_attempt < 1) {
+                       req->search_uuid--;
+                       req->reconnect_attempt++;
+               } else
+                       goto done;
+       }
+
+       update_services(req, recs);
+
+       adapter_get_address(adapter, &src);
+
+       /* Search for mandatory uuids */
+       if (uuid_list[req->search_uuid]) {
+               sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]);
+               bt_search_service(&src, &device->bdaddr, &uuid,
+                                               browse_cb, user_data, NULL);
+               return;
+       }
+
+done:
+       search_cb(recs, err, user_data);
+}
+
+static void init_browse(struct browse_req *req, gboolean reverse)
+{
+       GSList *l;
+
+       /* If we are doing reverse-SDP don't try to detect removed profiles
+        * since some devices hide their service records while they are
+        * connected
+        */
+       if (reverse)
+               return;
+
+       for (l = req->device->uuids; l; l = l->next)
+               req->profiles_removed = g_slist_append(req->profiles_removed,
+                                               l->data);
+}
+
+static char *primary_list_to_string(GSList *primary_list)
+{
+       GString *services;
+       GSList *l;
+
+       services = g_string_new(NULL);
+
+       for (l = primary_list; l; l = l->next) {
+               struct att_primary *primary = l->data;
+               char service[64];
+
+               memset(service, 0, sizeof(service));
+
+               snprintf(service, sizeof(service), "%04X#%04X#%s ",
+                               primary->start, primary->end, primary->uuid);
+
+               services = g_string_append(services, service);
+       }
+
+       return g_string_free(services, FALSE);
+}
+
+static void primary_cb(GSList *services, guint8 status, gpointer user_data)
+{
+       struct browse_req *req = user_data;
+       struct btd_device *device = req->device;
+       struct btd_adapter *adapter = device->adapter;
+       GSList *l, *uuids = NULL;
+       bdaddr_t dba, sba;
+       char *str;
+
+       if (status) {
+               DBusMessage *reply;
+               reply = btd_error_failed(req->msg, att_ecode2str(status));
+               g_dbus_send_message(req->conn, reply);
+               goto done;
+       }
+
+       services_changed(device);
+       device_set_temporary(device, FALSE);
+
+       for (l = services; l; l = l->next) {
+               struct att_primary *prim = l->data;
+               uuids = g_slist_append(uuids, prim->uuid);
+               device_add_primary(device, prim);
+       }
+
+       device_probe_drivers(device, uuids);
+       g_slist_free(uuids);
+
+       create_device_reply(device, req);
+
+       str = primary_list_to_string(services);
+
+       adapter_get_address(adapter, &sba);
+       device_get_address(device, &dba);
+
+       write_device_type(&sba, &dba, device->type);
+       write_device_services(&sba, &dba, str);
+       g_free(str);
+
+done:
+       device->browse = NULL;
+       browse_request_free(req);
+}
+
+static void gatt_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
+{
+       struct browse_req *req = user_data;
+       struct btd_device *device = req->device;
+
+       if (gerr) {
+               DBusMessage *reply;
+
+               DBG("%s", gerr->message);
+
+               reply = btd_error_failed(req->msg, gerr->message);
+               g_dbus_send_message(req->conn, reply);
+
+               device->browse = NULL;
+               browse_request_free(req);
+
+               return;
+       }
+
+       req->attrib = g_attrib_new(io);
+       gatt_discover_primary(req->attrib, NULL, primary_cb, req);
+}
+
+int device_browse_primary(struct btd_device *device, DBusConnection *conn,
+                               DBusMessage *msg, gboolean secure)
+{
+       struct btd_adapter *adapter = device->adapter;
+       struct browse_req *req;
+       BtIOSecLevel sec_level;
+       bdaddr_t src;
+
+       if (device->browse)
+               return -EBUSY;
+
+       req = g_new0(struct browse_req, 1);
+       req->device = btd_device_ref(device);
+
+       adapter_get_address(adapter, &src);
+
+       sec_level = secure ? BT_IO_SEC_HIGH : BT_IO_SEC_LOW;
+
+       req->io = bt_io_connect(BT_IO_L2CAP, gatt_connect_cb, req, NULL, NULL,
+                               BT_IO_OPT_SOURCE_BDADDR, &src,
+                               BT_IO_OPT_DEST_BDADDR, &device->bdaddr,
+                               BT_IO_OPT_CID, GATT_CID,
+                               BT_IO_OPT_SEC_LEVEL, sec_level,
+                               BT_IO_OPT_INVALID);
+
+       if (req->io == NULL ) {
+               browse_request_free(req);
+               return -EIO;
+       }
+
+       if (conn == NULL)
+               conn = get_dbus_connection();
+
+       req->conn = dbus_connection_ref(conn);
+       device->browse = req;
+
+       if (msg) {
+               const char *sender = dbus_message_get_sender(msg);
+
+               req->msg = dbus_message_ref(msg);
+               /* Track the request owner to cancel it
+                * automatically if the owner exits */
+               req->listener_id = g_dbus_add_disconnect_watch(conn,
+                                               sender,
+                                               discover_services_req_exit,
+                                               req, NULL);
+       }
+
+       return 0;
+}
+
+int device_browse_sdp(struct btd_device *device, DBusConnection *conn,
+                       DBusMessage *msg, uuid_t *search, gboolean reverse)
+{
+       struct btd_adapter *adapter = device->adapter;
+       struct browse_req *req;
+       bt_callback_t cb;
+       bdaddr_t src;
+       uuid_t uuid;
+       int err;
+
+       if (device->browse)
+               return -EBUSY;
+
+       adapter_get_address(adapter, &src);
+
+       req = g_new0(struct browse_req, 1);
+       req->device = btd_device_ref(device);
+       if (search) {
+               memcpy(&uuid, search, sizeof(uuid_t));
+               cb = search_cb;
+       } else {
+               sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]);
+               init_browse(req, reverse);
+               cb = browse_cb;
+       }
+
+       err = bt_search_service(&src, &device->bdaddr, &uuid, cb, req, NULL);
+       if (err < 0) {
+               browse_request_free(req);
+               return err;
+       }
+
+       if (conn == NULL)
+               conn = get_dbus_connection();
+
+       req->conn = dbus_connection_ref(conn);
+       device->browse = req;
+
+       if (msg) {
+               const char *sender = dbus_message_get_sender(msg);
+
+               req->msg = dbus_message_ref(msg);
+               /* Track the request owner to cancel it
+                * automatically if the owner exits */
+               req->listener_id = g_dbus_add_disconnect_watch(conn,
+                                               sender,
+                                               discover_services_req_exit,
+                                               req, NULL);
+       }
+
+       return err;
+}
+#ifdef __TIZEN_PATCH__
+struct bonding_req *device_get_bonding(struct btd_device *device)
+{
+       if (!device)
+               return NULL;
+
+       return device->bonding;
+}
+#endif
+struct btd_adapter *device_get_adapter(struct btd_device *device)
+{
+       if (!device)
+               return NULL;
+
+       return device->adapter;
+}
+
+void device_get_address(struct btd_device *device, bdaddr_t *bdaddr)
+{
+       bacpy(bdaddr, &device->bdaddr);
+}
+
+const gchar *device_get_path(struct btd_device *device)
+{
+       if (!device)
+               return NULL;
+
+       return device->path;
+}
+
+struct agent *device_get_agent(struct btd_device *device)
+{
+       if (!device)
+               return NULL;
+
+       if (device->agent)
+               return device->agent;
+
+       return adapter_get_agent(device->adapter);
+}
+
+gboolean device_is_busy(struct btd_device *device)
+{
+       return device->browse ? TRUE : FALSE;
+}
+
+gboolean device_is_temporary(struct btd_device *device)
+{
+       return device->temporary;
+}
+
+void device_set_temporary(struct btd_device *device, gboolean temporary)
+{
+       if (!device)
+               return;
+
+       DBG("temporary %d", temporary);
+
+       device->temporary = temporary;
+}
+
+void device_set_type(struct btd_device *device, device_type_t type)
+{
+       if (!device)
+               return;
+
+       device->type = type;
+}
+
+static gboolean start_discovery(gpointer user_data)
+{
+       struct btd_device *device = user_data;
+
+       device_browse_sdp(device, NULL, NULL, NULL, TRUE);
+
+       device->discov_timer = 0;
+
+       return FALSE;
+}
+
+static DBusMessage *new_authentication_return(DBusMessage *msg, int status)
+{
+       switch (status) {
+       case 0x00: /* success */
+               return dbus_message_new_method_return(msg);
+
+       case 0x04: /* page timeout */
+               return dbus_message_new_error(msg,
+                               ERROR_INTERFACE ".ConnectionAttemptFailed",
+                               "Page Timeout");
+       case 0x08: /* connection timeout */
+               return dbus_message_new_error(msg,
+                               ERROR_INTERFACE ".ConnectionAttemptFailed",
+                               "Connection Timeout");
+       case 0x10: /* connection accept timeout */
+       case 0x22: /* LMP response timeout */
+       case 0x28: /* instant passed - is this a timeout? */
+               return dbus_message_new_error(msg,
+                                       ERROR_INTERFACE ".AuthenticationTimeout",
+                                       "Authentication Timeout");
+       case 0x17: /* too frequent pairing attempts */
+               return dbus_message_new_error(msg,
+                                       ERROR_INTERFACE ".RepeatedAttempts",
+                                       "Repeated Attempts");
+
+       case 0x06:
+       case 0x18: /* pairing not allowed (e.g. gw rejected attempt) */
+               return dbus_message_new_error(msg,
+                                       ERROR_INTERFACE ".AuthenticationRejected",
+                                       "Authentication Rejected");
+
+       case 0x07: /* memory capacity */
+       case 0x09: /* connection limit */
+       case 0x0a: /* synchronous connection limit */
+       case 0x0d: /* limited resources */
+       case 0x13: /* user ended the connection */
+       case 0x14: /* terminated due to low resources */
+       case 0x16: /* connection terminated */
+               return dbus_message_new_error(msg,
+                                       ERROR_INTERFACE ".AuthenticationCanceled",
+                                       "Authentication Canceled");
+
+#ifdef __TIZEN_PATCH__
+       case 0x2a: /* Cancel by agent */
+               return dbus_message_new_error(msg,
+                                       ERROR_INTERFACE ".CanceledbyUser",
+                                       "CanceledbyUser");
+#endif
+       case 0x05: /* authentication failure */
+       case 0x0E: /* rejected due to security reasons - is this auth failure? */
+       case 0x25: /* encryption mode not acceptable - is this auth failure? */
+       case 0x26: /* link key cannot be changed - is this auth failure? */
+       case 0x29: /* pairing with unit key unsupported - is this auth failure? */
+       case 0x2f: /* insufficient security - is this auth failure? */
+       default:
+               return dbus_message_new_error(msg,
+                                       ERROR_INTERFACE ".AuthenticationFailed",
+                                       "Authentication Failed");
+       }
+}
+
+static void bonding_request_free(struct bonding_req *bonding)
+{
+       struct btd_device *device;
+
+       if (!bonding)
+               return;
+
+       if (bonding->listener_id)
+               g_dbus_remove_watch(bonding->conn, bonding->listener_id);
+
+       if (bonding->msg)
+               dbus_message_unref(bonding->msg);
+
+       if (bonding->conn)
+               dbus_connection_unref(bonding->conn);
+
+       if (bonding->io)
+               g_io_channel_unref(bonding->io);
+
+       device = bonding->device;
+       g_free(bonding);
+
+       if (!device)
+               return;
+
+       device->bonding = NULL;
+
+       adapter_resume_discovery(device->adapter);
+
+       if (!device->agent)
+               return;
+
+       agent_cancel(device->agent);
+       agent_free(device->agent);
+       device->agent = NULL;
+}
+#ifdef __TIZEN_PATCH__
+void device_send_reply(struct btd_device *device)
+{
+        info("device_send_reply 1\n");
+       DBusMessage *reply;
+
+       device_set_temporary(device, FALSE);
+
+       if(device->bonding !=NULL)
+       {
+               reply = dbus_message_new_method_return(device->bonding->msg);
+               dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &device->path, DBUS_TYPE_INVALID);
+               g_dbus_send_message(device->bonding->conn, reply);
+                info("device_send_reply 2\n");
+               bonding_request_free(device->bonding);
+
+       }
+        info("device_send_reply 3\n");
+
+}
+#endif
+void device_set_paired(struct btd_device *device, gboolean value)
+{
+       info("device_set_paired +\n");
+       DBusConnection *conn = get_dbus_connection();
+
+       if (device->paired == value)
+               return;
+
+       device->paired = value;
+
+       emit_property_changed(conn, device->path, DEVICE_INTERFACE, "Paired",
+                               DBUS_TYPE_BOOLEAN, &value);
+}
+
+static void device_agent_removed(struct agent *agent, void *user_data)
+{
+       struct btd_device *device = user_data;
+
+       device->agent = NULL;
+#ifdef __TIZEN_PATCH__
+       if (device->authr && (device->authr->agent == agent)) {
+               DBG("device->agent is the same with authr->agent");
+               device->authr->agent = NULL;
+       }
+#else
+       if (device->authr)
+               device->authr->agent = NULL;
+#endif
+}
+
+static struct bonding_req *bonding_request_new(DBusConnection *conn,
+                                               DBusMessage *msg,
+                                               struct btd_device *device,
+                                               const char *agent_path,
+                                               uint8_t capability)
+{
+       struct bonding_req *bonding;
+       const char *name = dbus_message_get_sender(msg);
+       struct agent *agent;
+       char addr[18];
+
+       ba2str(&device->bdaddr, addr);
+       DBG("Requesting bonding for %s", addr);
+
+       if (!agent_path)
+               goto proceed;
+
+       agent = agent_create(device->adapter, name, agent_path,
+                                       capability,
+                                       device_agent_removed,
+                                       device);
+       if (!agent) {
+               error("Unable to create a new agent");
+               return NULL;
+       }
+
+       device->agent = agent;
+
+       DBG("Temporary agent registered for %s at %s:%s",
+                       addr, name, agent_path);
+
+proceed:
+       bonding = g_new0(struct bonding_req, 1);
+
+       bonding->conn = dbus_connection_ref(conn);
+       bonding->msg = dbus_message_ref(msg);
+
+       adapter_suspend_discovery(device->adapter);
+
+       return bonding;
+}
+
+static int device_authentication_requested(struct btd_device *device,
+                                               int handle)
+{
+       struct hci_request rq;
+       auth_requested_cp cp;
+       evt_cmd_status rp;
+       int dd;
+
+       dd = hci_open_dev(adapter_get_dev_id(device->adapter));
+       if (dd < 0) {
+               int err = -errno;
+               error("Unable to open adapter: %s(%d)", strerror(-err), -err);
+               return err;
+       }
+
+       memset(&rp, 0, sizeof(rp));
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = htobs(handle);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_AUTH_REQUESTED;
+       rq.cparam = &cp;
+       rq.clen   = AUTH_REQUESTED_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_CMD_STATUS_SIZE;
+       rq.event  = EVT_CMD_STATUS;
+
+       if (hci_send_req(dd, &rq, HCI_REQ_TIMEOUT) < 0) {
+               int err = -errno;
+               error("Unable to send HCI request: %s (%d)",
+                                       strerror(-err), -err);
+               hci_close_dev(dd);
+               return err;
+       }
+
+       if (rp.status) {
+               error("HCI_Authentication_Requested failed with status 0x%02x",
+                               rp.status);
+               hci_close_dev(dd);
+               return rp.status;
+       }
+
+       info("Authentication requested");
+
+       hci_close_dev(dd);
+       return 0;
+}
+
+static void bonding_connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+       struct btd_device *device = user_data;
+       uint16_t handle;
+       int status;
+
+       if (!device->bonding) {
+               if (!err)
+                       g_io_channel_shutdown(io, TRUE, NULL);
+               return;
+       }
+
+       if (err)
+               /* Wait proper error to be propagated by bonding complete */
+               return;
+
+       if (!bt_io_get(io, BT_IO_L2RAW, &err,
+                       BT_IO_OPT_HANDLE, &handle,
+                       BT_IO_OPT_INVALID)) {
+               error("Unable to get connection handle: %s", err->message);
+               g_error_free(err);
+               status = -errno;
+               goto failed;
+       }
+
+       status = device_authentication_requested(device, handle);
+       if (status != 0)
+               goto failed;
+
+       return;
+
+failed:
+       g_io_channel_shutdown(io, TRUE, NULL);
+       device_cancel_bonding(device, status);
+}
+static void create_bond_req_exit(DBusConnection *conn, void *user_data)
+{
+       struct btd_device *device = user_data;
+       char addr[18];
+
+       ba2str(&device->bdaddr, addr);
+       DBG("%s: requestor exited before bonding was completed", addr);
+
+       if (device->authr)
+               device_cancel_authentication(device, FALSE);
+
+       if (device->bonding) {
+               device->bonding->listener_id = 0;
+               device_request_disconnect(device, NULL);
+       }
+}
+#ifdef __TIZEN_PATCH__
+void set_cancel_from_authentication_req(void *user_data)
+{
+       struct authentication_req *auth = (struct authentication_req *)user_data;
+
+       if (auth && auth->device && auth->device->bonding)
+       {
+               auth->device->bonding->cancel_by_user = 1;
+       }
+}
+#endif
+#ifdef __TIZEN_PATCH__
+DBusMessage *device_jsr82_authenticate_link(struct btd_device *device,
+                                       DBusConnection *conn,
+                                       DBusMessage *msg,
+                                       const char *agent_path,
+                                       uint8_t capability)
+
+{
+       char filename[PATH_MAX + 1];
+       char *str, srcaddr[18], dstaddr[18];
+       struct btd_adapter *adapter = device->adapter;
+       struct bonding_req *bonding;
+       bdaddr_t src;
+       GError *err = NULL;
+       GIOChannel *io;
+
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+       ba2str(&device->bdaddr, dstaddr);
+
+       if (device->bonding)
+               return btd_error_in_progress(msg);
+
+       /* check if a link key already exists */
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr,
+                       "linkkeys");
+
+       str = textfile_caseget(filename, dstaddr);
+/*     if (str) {
+               free(str);
+               return g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".AlreadyExists",
+                               "Bonding already exists");
+       }*/
+
+
+       io = bt_io_connect(BT_IO_L2RAW, bonding_connect_cb, device,
+                               NULL, &err,
+                               BT_IO_OPT_SOURCE_BDADDR, &src,
+                               BT_IO_OPT_DEST_BDADDR, &device->bdaddr,
+                               BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH,
+                               BT_IO_OPT_INVALID);
+       if (io == NULL) {
+               DBusMessage *reply;
+               reply = g_dbus_create_error(msg,
+                               ERROR_INTERFACE ".ConnectionAttemptFailed",
+                               err->message);
+               error("bt_io_connect: %s", err->message);
+               g_error_free(err);
+               return reply;
+       }
+
+       bonding = bonding_request_new(conn, msg, device, agent_path,
+                                       capability);
+       if (!bonding) {
+               g_io_channel_shutdown(io, TRUE, NULL);
+               return NULL;
+       }
+
+       bonding->io = io;
+
+       bonding->listener_id = g_dbus_add_disconnect_watch(conn,
+                                               dbus_message_get_sender(msg),
+                                               create_bond_req_exit, device,
+                                               NULL);
+
+       device->bonding = bonding;
+       bonding->device = device;
+
+       return NULL;
+
+}
+#endif
+
+DBusMessage *device_create_bonding(struct btd_device *device,
+                                       DBusConnection *conn,
+                                       DBusMessage *msg,
+                                       const char *agent_path,
+                                       uint8_t capability)
+{
+       char filename[PATH_MAX + 1];
+       char *str, srcaddr[18], dstaddr[18];
+       struct btd_adapter *adapter = device->adapter;
+       struct bonding_req *bonding;
+       bdaddr_t src;
+       int err;
+
+       adapter_get_address(adapter, &src);
+       ba2str(&src, srcaddr);
+       ba2str(&device->bdaddr, dstaddr);
+
+       if (device->bonding)
+               return btd_error_in_progress(msg);
+
+       /* check if a link key already exists */
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr,
+                       "linkkeys");
+
+       str = textfile_caseget(filename, dstaddr);
+       if (str) {
+               free(str);
+               return btd_error_already_exists(msg);
+       }
+
+       err = adapter_create_bonding(adapter, &device->bdaddr, capability);
+       if (err < 0)
+               return btd_error_failed(msg, strerror(-err));
+
+       bonding = bonding_request_new(conn, msg, device, agent_path,
+                                       capability);
+       if (!bonding) {
+               adapter_cancel_bonding(adapter, &device->bdaddr);
+               return NULL;
+       }
+
+       bonding->listener_id = g_dbus_add_disconnect_watch(conn,
+                                               dbus_message_get_sender(msg),
+                                               create_bond_req_exit, device,
+                                               NULL);
+
+       device->bonding = bonding;
+       bonding->device = device;
+
+       return NULL;
+}
+
+void device_simple_pairing_complete(struct btd_device *device, uint8_t status)
+{
+       struct authentication_req *auth = device->authr;
+
+       if (auth && auth->type == AUTH_TYPE_NOTIFY && auth->agent)
+               agent_cancel(auth->agent);
+}
+
+static void device_auth_req_free(struct btd_device *device)
+{
+       g_free(device->authr);
+       device->authr = NULL;
+}
+
+void device_bonding_complete(struct btd_device *device, uint8_t status)
+{
+       info("device_bonding_complete() +");
+       struct bonding_req *bonding = device->bonding;
+       struct authentication_req *auth = device->authr;
+
+       DBG("bonding %p status 0x%02x", bonding, status);
+
+       if (auth && auth->type == AUTH_TYPE_NOTIFY && auth->agent)
+               agent_cancel(auth->agent);
+
+       if (status) {
+               device_cancel_authentication(device, TRUE);
+               device_cancel_bonding(device, status);
+               return;
+       }
+
+       device_auth_req_free(device);
+
+#ifdef __TIZEN_PATCH__
+       if (bonding) {
+               if (dbus_message_is_method_call(bonding->msg, ADAPTER_INTERFACE,
+                                                               "AuthenticateLink") ) {
+                       DBusMessage *reply;
+                       reply = dbus_message_new_method_return(bonding->msg);
+                       if (!reply) {
+                               bonding_request_free(bonding);
+                               return;
+                       }
+
+                       const char *path = device_get_path(device);
+
+                       dbus_message_append_args(reply,
+                                       DBUS_TYPE_OBJECT_PATH, &path,
+                                       DBUS_TYPE_INVALID);
+
+                       g_dbus_send_message(bonding->conn, reply);
+                       bonding_request_free(bonding);
+                       return;
+               }
+       }
+#endif
+
+
+       /* If we're already paired nothing more is needed */
+       if (device->paired)
+               return;
+
+       device_set_paired(device, TRUE);
+
+       /* If we were initiators start service discovery immediately.
+        * However if the other end was the initator wait a few seconds
+        * before SDP. This is due to potential IOP issues if the other
+        * end starts doing SDP at the same time as us */
+       if (bonding) {
+               DBG("Proceeding with service discovery");
+               /* If we are initiators remove any discovery timer and just
+                * start discovering services directly */
+               if (device->discov_timer) {
+                       g_source_remove(device->discov_timer);
+                       device->discov_timer = 0;
+               }
+
+               device_browse_sdp(device, bonding->conn, bonding->msg,
+                               NULL, FALSE);
+
+               bonding_request_free(bonding);
+       } else {
+               if (!device->browse && !device->discov_timer &&
+                               main_opts.reverse_sdp) {
+                       /* If we are not initiators and there is no currently
+                        * active discovery or discovery timer, set discovery
+                        * timer */
+                       DBG("setting timer for reverse service discovery");
+                       device->discov_timer = g_timeout_add_seconds(
+                                                       DISCOVERY_TIMER,
+                                                       start_discovery,
+                                                       device);
+               }
+       }
+       info("device_bonding_complete() -");
+}
+
+gboolean device_is_creating(struct btd_device *device, const char *sender)
+{
+       DBusMessage *msg;
+
+       if (device->bonding && device->bonding->msg)
+               msg = device->bonding->msg;
+       else if (device->browse && device->browse->msg)
+               msg = device->browse->msg;
+       else
+               return FALSE;
+
+       if (!dbus_message_is_method_call(msg, ADAPTER_INTERFACE,
+                                               "CreatePairedDevice") &&
+                       !dbus_message_is_method_call(msg, ADAPTER_INTERFACE,
+                                                       "CreateDevice"))
+               return FALSE;
+
+       if (sender == NULL)
+               return TRUE;
+
+       return g_str_equal(sender, dbus_message_get_sender(msg));
+}
+
+gboolean device_is_bonding(struct btd_device *device, const char *sender)
+{
+       struct bonding_req *bonding = device->bonding;
+
+       if (!device->bonding)
+               return FALSE;
+
+       if (!sender)
+               return TRUE;
+
+       return g_str_equal(sender, dbus_message_get_sender(bonding->msg));
+}
+
+void device_cancel_bonding(struct btd_device *device, uint8_t status)
+{
+       struct bonding_req *bonding = device->bonding;
+       DBusMessage *reply;
+       char addr[18];
+
+       if (!bonding)
+               return;
+
+       ba2str(&device->bdaddr, addr);
+       DBG("Canceling bonding request for %s", addr);
+
+       if (device->authr)
+               device_cancel_authentication(device, FALSE);
+
+#ifdef __TIZEN_PATCH__
+       if (bonding->cancel_by_user)
+       {
+               info("Bonding Cancel by user");
+               reply = new_authentication_return(bonding->msg, 0x2a);
+       }
+       else
+#endif
+       reply = new_authentication_return(bonding->msg, status);
+       g_dbus_send_message(bonding->conn, reply);
+
+       bonding_request_cancel(bonding);
+       bonding_request_free(bonding);
+}
+
+static void pincode_cb(struct agent *agent, DBusError *err,
+                                       const char *pincode, void *data)
+{
+       struct authentication_req *auth = data;
+       struct btd_device *device = auth->device;
+
+       /* No need to reply anything if the authentication already failed */
+       if (auth->cb == NULL)
+               return;
+
+       ((agent_pincode_cb) auth->cb)(agent, err, pincode, device);
+
+       device->authr->cb = NULL;
+       device->authr->agent = NULL;
+}
+
+static void confirm_cb(struct agent *agent, DBusError *err, void *data)
+{
+       struct authentication_req *auth = data;
+       struct btd_device *device = auth->device;
+
+       /* No need to reply anything if the authentication already failed */
+       if (auth->cb == NULL)
+               return;
+
+#ifdef __TIZEN_PATCH__
+       if (!device)
+               return;
+#endif
+
+       ((agent_cb) auth->cb)(agent, err, device);
+
+#ifdef __TIZEN_PATCH__
+       if (!device->authr)
+               return;
+#endif
+
+       device->authr->cb = NULL;
+       device->authr->agent = NULL;
+}
+
+static void passkey_cb(struct agent *agent, DBusError *err,
+                                               uint32_t passkey, void *data)
+{
+       struct authentication_req *auth = data;
+       struct btd_device *device = auth->device;
+
+       /* No need to reply anything if the authentication already failed */
+       if (auth->cb == NULL)
+               return;
+
+       ((agent_passkey_cb) auth->cb)(agent, err, passkey, device);
+
+       device->authr->cb = NULL;
+       device->authr->agent = NULL;
+}
+
+int device_request_authentication(struct btd_device *device, auth_type_t type,
+                                               uint32_t passkey, void *cb)
+{
+       struct authentication_req *auth;
+       struct agent *agent;
+       char addr[18];
+       int err;
+
+       ba2str(&device->bdaddr, addr);
+       DBG("Requesting agent authentication for %s", addr);
+
+       if (device->authr) {
+               error("Authentication already requested for %s", addr);
+               return -EALREADY;
+       }
+
+       agent = device_get_agent(device);
+       if (!agent) {
+               error("No agent available for request type %d", type);
+               return -EPERM;
+       }
+
+       auth = g_new0(struct authentication_req, 1);
+       auth->agent = agent;
+       auth->device = device;
+       auth->cb = cb;
+       auth->type = type;
+       device->authr = auth;
+
+       switch (type) {
+       case AUTH_TYPE_PINCODE:
+               err = agent_request_pincode(agent, device, pincode_cb,
+                                                               auth, NULL);
+               break;
+       case AUTH_TYPE_PASSKEY:
+               err = agent_request_passkey(agent, device, passkey_cb,
+                                                               auth, NULL);
+               break;
+       case AUTH_TYPE_CONFIRM:
+               err = agent_request_confirmation(agent, device, passkey,
+                                               confirm_cb, auth, NULL);
+               break;
+       case AUTH_TYPE_NOTIFY:
+               err = agent_display_passkey(agent, device, passkey);
+               break;
+       default:
+               err = -EINVAL;
+       }
+
+       if (err < 0) {
+               error("Failed requesting authentication");
+               device_auth_req_free(device);
+       }
+
+       return err;
+}
+
+static void cancel_authentication(struct authentication_req *auth)
+{
+       struct btd_device *device;
+       struct agent *agent;
+       DBusError err;
+
+       if (!auth || !auth->cb)
+               return;
+
+       device = auth->device;
+       agent = auth->agent;
+
+       dbus_error_init(&err);
+       dbus_set_error_const(&err, "org.bluez.Error.Canceled", NULL);
+
+       switch (auth->type) {
+       case AUTH_TYPE_PINCODE:
+               ((agent_pincode_cb) auth->cb)(agent, &err, NULL, device);
+               break;
+       case AUTH_TYPE_CONFIRM:
+               ((agent_cb) auth->cb)(agent, &err, device);
+               break;
+       case AUTH_TYPE_PASSKEY:
+               ((agent_passkey_cb) auth->cb)(agent, &err, 0, device);
+               break;
+       case AUTH_TYPE_NOTIFY:
+               /* User Notify doesn't require any reply */
+               break;
+       }
+
+       dbus_error_free(&err);
+       auth->cb = NULL;
+}
+
+void device_cancel_authentication(struct btd_device *device, gboolean aborted)
+{
+       struct authentication_req *auth = device->authr;
+       char addr[18];
+
+       if (!auth)
+               return;
+
+       ba2str(&device->bdaddr, addr);
+       DBG("Canceling authentication request for %s", addr);
+
+       if (auth->agent)
+               agent_cancel(auth->agent);
+
+       if (!aborted)
+               cancel_authentication(auth);
+
+       device_auth_req_free(device);
+}
+
+gboolean device_is_authenticating(struct btd_device *device)
+{
+       return (device->authr != NULL);
+}
+
+gboolean device_is_authorizing(struct btd_device *device)
+{
+       return device->authorizing;
+}
+
+void device_set_authorizing(struct btd_device *device, gboolean auth)
+{
+       device->authorizing = auth;
+}
+
+void btd_device_add_service(struct btd_device *device, const char *path)
+{
+       if (g_slist_find_custom(device->services, path, (GCompareFunc) strcmp))
+               return;
+
+       device->services = g_slist_append(device->services, g_strdup(path));
+}
+
+void device_add_primary(struct btd_device *device, struct att_primary *prim)
+{
+       device->primaries = g_slist_append(device->primaries, prim);
+}
+
+GSList *btd_device_get_primaries(struct btd_device *device)
+{
+       return device->primaries;
+}
+
+void btd_device_add_uuid(struct btd_device *device, const char *uuid)
+{
+       GSList *uuid_list;
+       char *new_uuid;
+
+       if (g_slist_find_custom(device->uuids, uuid,
+                               (GCompareFunc) strcasecmp))
+               return;
+
+       new_uuid = g_strdup(uuid);
+       uuid_list = g_slist_append(NULL, new_uuid);
+
+       device_probe_drivers(device, uuid_list);
+
+       g_free(new_uuid);
+       g_slist_free(uuid_list);
+
+       store_profiles(device);
+       services_changed(device);
+}
+
+const sdp_record_t *btd_device_get_record(struct btd_device *device,
+                                                       const char *uuid)
+{
+       bdaddr_t src;
+
+       if (device->tmp_records) {
+               const sdp_record_t *record;
+
+               record = find_record_in_list(device->tmp_records, uuid);
+               if (record != NULL)
+                       return record;
+       }
+
+       adapter_get_address(device->adapter, &src);
+
+       device->tmp_records = read_records(&src, &device->bdaddr);
+       if (!device->tmp_records)
+               return NULL;
+
+       return find_record_in_list(device->tmp_records, uuid);
+}
+
+int btd_register_device_driver(struct btd_device_driver *driver)
+{
+       device_drivers = g_slist_append(device_drivers, driver);
+
+       return 0;
+}
+
+void btd_unregister_device_driver(struct btd_device_driver *driver)
+{
+       device_drivers = g_slist_remove(device_drivers, driver);
+}
+
+struct btd_device *btd_device_ref(struct btd_device *device)
+{
+       device->ref++;
+
+       DBG("%p: ref=%d", device, device->ref);
+
+       return device;
+}
+
+void btd_device_unref(struct btd_device *device)
+{
+       DBusConnection *conn = get_dbus_connection();
+       gchar *path;
+
+       device->ref--;
+
+       DBG("%p: ref=%d", device, device->ref);
+
+       if (device->ref > 0)
+               return;
+
+       path = g_strdup(device->path);
+
+       g_dbus_unregister_interface(conn, path, DEVICE_INTERFACE);
+
+       g_free(path);
+}
diff --git a/src/device.h b/src/device.h
new file mode 100644 (file)
index 0000000..7b0b708
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 DEVICE_INTERFACE       "org.bluez.Device"
+
+struct btd_device;
+struct att_primary;
+
+typedef enum {
+       AUTH_TYPE_PINCODE,
+       AUTH_TYPE_PASSKEY,
+       AUTH_TYPE_CONFIRM,
+       AUTH_TYPE_NOTIFY,
+} auth_type_t;
+
+typedef enum {
+       DEVICE_TYPE_UNKNOWN,
+       DEVICE_TYPE_BREDR,
+       DEVICE_TYPE_LE,
+       DEVICE_TYPE_DUALMODE
+} device_type_t;
+
+struct btd_device *device_create(DBusConnection *conn,
+                               struct btd_adapter *adapter,
+                               const gchar *address, device_type_t type);
+void device_set_name(struct btd_device *device, const char *name);
+void device_get_name(struct btd_device *device, char *name, size_t len);
+device_type_t device_get_type(struct btd_device *device);
+void device_remove(struct btd_device *device, gboolean remove_stored);
+gint device_address_cmp(struct btd_device *device, const gchar *address);
+int device_browse_primary(struct btd_device *device, DBusConnection *conn,
+                               DBusMessage *msg, gboolean secure);
+int device_browse_sdp(struct btd_device *device, DBusConnection *conn,
+                       DBusMessage *msg, uuid_t *search, gboolean reverse);
+void device_probe_drivers(struct btd_device *device, GSList *profiles);
+const sdp_record_t *btd_device_get_record(struct btd_device *device,
+                                               const char *uuid);
+GSList *btd_device_get_primaries(struct btd_device *device);
+void btd_device_add_service(struct btd_device *device, const char *path);
+void device_add_primary(struct btd_device *device, struct att_primary *prim);
+void btd_device_add_uuid(struct btd_device *device, const char *uuid);
+struct btd_adapter *device_get_adapter(struct btd_device *device);
+void device_get_address(struct btd_device *device, bdaddr_t *bdaddr);
+const gchar *device_get_path(struct btd_device *device);
+struct agent *device_get_agent(struct btd_device *device);
+gboolean device_is_busy(struct btd_device *device);
+gboolean device_is_temporary(struct btd_device *device);
+gboolean device_is_paired(struct btd_device *device);
+gboolean device_is_trusted(struct btd_device *device);
+void device_set_paired(struct btd_device *device, gboolean paired);
+void device_set_temporary(struct btd_device *device, gboolean temporary);
+void device_set_cap(struct btd_device *device, uint8_t cap);
+void device_set_type(struct btd_device *device, device_type_t type);
+uint8_t device_get_cap(struct btd_device *device);
+void device_set_auth(struct btd_device *device, uint8_t auth);
+uint8_t device_get_auth(struct btd_device *device);
+gboolean device_is_connected(struct btd_device *device);
+DBusMessage *device_create_bonding(struct btd_device *device,
+                               DBusConnection *conn, DBusMessage *msg,
+                               const char *agent_path, uint8_t capability);
+void device_remove_bonding(struct btd_device *device);
+void device_bonding_complete(struct btd_device *device, uint8_t status);
+void device_simple_pairing_complete(struct btd_device *device, uint8_t status);
+gboolean device_is_creating(struct btd_device *device, const char *sender);
+gboolean device_is_bonding(struct btd_device *device, const char *sender);
+void device_cancel_bonding(struct btd_device *device, uint8_t status);
+int device_request_authentication(struct btd_device *device, auth_type_t type,
+                               uint32_t passkey, void *cb);
+void device_cancel_authentication(struct btd_device *device, gboolean aborted);
+gboolean device_is_authenticating(struct btd_device *device);
+gboolean device_is_authorizing(struct btd_device *device);
+void device_set_authorizing(struct btd_device *device, gboolean auth);
+void device_add_connection(struct btd_device *device, DBusConnection *conn);
+void device_remove_connection(struct btd_device *device, DBusConnection *conn);
+void device_request_disconnect(struct btd_device *device, DBusMessage *msg);
+
+typedef void (*disconnect_watch) (struct btd_device *device, gboolean removal,
+                                       void *user_data);
+
+guint device_add_disconnect_watch(struct btd_device *device,
+                               disconnect_watch watch, void *user_data,
+                               GDestroyNotify destroy);
+void device_remove_disconnect_watch(struct btd_device *device, guint id);
+
+#define BTD_UUIDS(args...) ((const char *[]) { args, NULL } )
+
+struct btd_device_driver {
+       const char *name;
+       const char **uuids;
+       int (*probe) (struct btd_device *device, GSList *uuids);
+       void (*remove) (struct btd_device *device);
+};
+
+int btd_register_device_driver(struct btd_device_driver *driver);
+void btd_unregister_device_driver(struct btd_device_driver *driver);
+
+struct btd_device *btd_device_ref(struct btd_device *device);
+void btd_device_unref(struct btd_device *device);
+
+#ifdef __TIZEN_PATCH__
+DBusMessage *device_jsr82_authenticate_link(struct btd_device *device,
+                                       DBusConnection *conn,
+                                       DBusMessage *msg,
+                                       const char *agent_path,
+                                       uint8_t capability);
+#endif
diff --git a/src/error.c b/src/error.c
new file mode 100644 (file)
index 0000000..c2d9baa
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2007-2008  Fabien Chevalier <fabchevalier@free.fr>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <gdbus.h>
+
+#include "error.h"
+
+DBusMessage *btd_error_invalid_args(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid arguments in method call");
+}
+
+DBusMessage *btd_error_busy(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+                                       "Operation already in progress");
+}
+
+DBusMessage *btd_error_already_exists(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists",
+                                       "Already Exists");
+}
+
+DBusMessage *btd_error_not_supported(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported",
+                                       "Operation is not supported");
+}
+
+DBusMessage *btd_error_not_connected(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected",
+                                       "Not Connected");
+}
+
+DBusMessage *btd_error_already_connected(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyConnected",
+                                       "Already Connected");
+}
+
+DBusMessage *btd_error_in_progress(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+                                       "In Progress");
+}
+
+DBusMessage *btd_error_not_available(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+                                       "Operation currently not available");
+}
+
+DBusMessage *btd_error_does_not_exist(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist",
+                                       "Does Not Exist");
+}
+
+DBusMessage *btd_error_not_authorized(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized",
+                                       "Operation Not Authorized");
+}
+
+DBusMessage *btd_error_no_such_adapter(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter",
+                                       "No such adapter");
+}
+
+DBusMessage *btd_error_agent_not_available(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".AgentNotAvailable",
+                                       "Agent Not Available");
+}
+
+DBusMessage *btd_error_not_ready(DBusMessage *msg)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady",
+                                       "Resource Not Ready");
+}
+
+DBusMessage *btd_error_failed(DBusMessage *msg, const char *str)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE
+                                       ".Failed", "%s", str);
+}
diff --git a/src/error.h b/src/error.h
new file mode 100644 (file)
index 0000000..cdb8919
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2007-2008  Fabien Chevalier <fabchevalier@free.fr>
+ *
+ *
+ *  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 <dbus/dbus.h>
+
+#define ERROR_INTERFACE "org.bluez.Error"
+
+DBusMessage *btd_error_invalid_args(DBusMessage *msg);
+DBusMessage *btd_error_busy(DBusMessage *msg);
+DBusMessage *btd_error_already_exists(DBusMessage *msg);
+DBusMessage *btd_error_not_supported(DBusMessage *msg);
+DBusMessage *btd_error_not_connected(DBusMessage *msg);
+DBusMessage *btd_error_already_connected(DBusMessage *msg);
+DBusMessage *btd_error_not_available(DBusMessage *msg);
+DBusMessage *btd_error_in_progress(DBusMessage *msg);
+DBusMessage *btd_error_does_not_exist(DBusMessage *msg);
+DBusMessage *btd_error_not_authorized(DBusMessage *msg);
+DBusMessage *btd_error_no_such_adapter(DBusMessage *msg);
+DBusMessage *btd_error_agent_not_available(DBusMessage *msg);
+DBusMessage *btd_error_not_ready(DBusMessage *msg);
+DBusMessage *btd_error_failed(DBusMessage *msg, const char *str);
diff --git a/src/event.c b/src/event.c
new file mode 100644 (file)
index 0000000..4ca1be5
--- /dev/null
@@ -0,0 +1,731 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "textfile.h"
+
+#include "hcid.h"
+#include "adapter.h"
+#include "manager.h"
+#include "device.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "dbus-common.h"
+#include "agent.h"
+#include "storage.h"
+#include "event.h"
+#include "sdpd.h"
+
+struct eir_data {
+       GSList *services;
+       int flags;
+       char *name;
+       gboolean name_complete;
+};
+
+static gboolean get_adapter_and_device(bdaddr_t *src, bdaddr_t *dst,
+                                       struct btd_adapter **adapter,
+                                       struct btd_device **device,
+                                       gboolean create)
+{
+       DBusConnection *conn = get_dbus_connection();
+       char peer_addr[18];
+
+       *adapter = manager_find_adapter(src);
+       if (!*adapter) {
+               error("Unable to find matching adapter");
+               return FALSE;
+       }
+
+       ba2str(dst, peer_addr);
+
+       if (create)
+               *device = adapter_get_device(conn, *adapter, peer_addr);
+       else
+               *device = adapter_find_device(*adapter, peer_addr);
+
+       if (create && !*device) {
+               error("Unable to get device object!");
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/*****************************************************************
+ *
+ *  Section reserved to HCI commands confirmation handling and low
+ *  level events(eg: device attached/dettached.
+ *
+ *****************************************************************/
+
+static void pincode_cb(struct agent *agent, DBusError *derr,
+                               const char *pincode, struct btd_device *device)
+{
+       struct btd_adapter *adapter = device_get_adapter(device);
+       bdaddr_t sba, dba;
+       int err;
+
+       device_get_address(device, &dba);
+
+       if (derr) {
+               err = btd_adapter_pincode_reply(adapter, &dba, NULL);
+               if (err < 0)
+                       goto fail;
+               return;
+       }
+
+       err = btd_adapter_pincode_reply(adapter, &dba, pincode);
+       if (err < 0)
+               goto fail;
+
+       adapter_get_address(adapter, &sba);
+
+       return;
+
+fail:
+       error("Sending PIN code reply failed: %s (%d)", strerror(-err), -err);
+}
+
+int btd_event_request_pin(bdaddr_t *sba, bdaddr_t *dba)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+       char pin[17];
+       int pinlen;
+
+       if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE))
+               return -ENODEV;
+
+       memset(pin, 0, sizeof(pin));
+       pinlen = read_pin_code(sba, dba, pin);
+       if (pinlen > 0) {
+               btd_adapter_pincode_reply(adapter, dba, pin);
+               return 0;
+       }
+
+       return device_request_authentication(device, AUTH_TYPE_PINCODE, 0,
+                                                               pincode_cb);
+}
+
+static int confirm_reply(struct btd_adapter *adapter,
+                               struct btd_device *device, gboolean success)
+{
+       bdaddr_t bdaddr;
+
+       device_get_address(device, &bdaddr);
+
+       return btd_adapter_confirm_reply(adapter, &bdaddr, success);
+}
+
+static void confirm_cb(struct agent *agent, DBusError *err, void *user_data)
+{
+       struct btd_device *device = user_data;
+       struct btd_adapter *adapter = device_get_adapter(device);
+       gboolean success = (err == NULL) ? TRUE : FALSE;
+
+       confirm_reply(adapter, device, success);
+}
+
+static void passkey_cb(struct agent *agent, DBusError *err, uint32_t passkey,
+                       void *user_data)
+{
+       struct btd_device *device = user_data;
+       struct btd_adapter *adapter = device_get_adapter(device);
+       bdaddr_t bdaddr;
+
+       device_get_address(device, &bdaddr);
+
+       if (err)
+               passkey = INVALID_PASSKEY;
+
+       btd_adapter_passkey_reply(adapter, &bdaddr, passkey);
+}
+
+int btd_event_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+
+       if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE))
+               return -ENODEV;
+
+       return device_request_authentication(device, AUTH_TYPE_CONFIRM,
+                                                       passkey, confirm_cb);
+}
+
+int btd_event_user_passkey(bdaddr_t *sba, bdaddr_t *dba)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+
+       if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE))
+               return -ENODEV;
+
+       return device_request_authentication(device, AUTH_TYPE_PASSKEY, 0,
+                                                               passkey_cb);
+}
+
+int btd_event_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+
+       if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE))
+               return -ENODEV;
+
+       return device_request_authentication(device, AUTH_TYPE_NOTIFY,
+                                                               passkey, NULL);
+}
+
+void btd_event_bonding_complete(bdaddr_t *local, bdaddr_t *peer,
+                                                       uint8_t status)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+       gboolean create;
+
+       DBG("status 0x%02x", status);
+
+       create = status ? FALSE : TRUE;
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, create))
+               return;
+
+       if (device)
+               device_bonding_complete(device, status);
+}
+
+void btd_event_simple_pairing_complete(bdaddr_t *local, bdaddr_t *peer,
+                                                               uint8_t status)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+       gboolean create;
+
+       DBG("status=%02x", status);
+
+       create = status ? FALSE : TRUE;
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, create))
+               return;
+
+       if (!device)
+               return;
+
+       device_simple_pairing_complete(device, status);
+}
+
+static int parse_eir_data(struct eir_data *eir, uint8_t *eir_data,
+                                                       size_t eir_length)
+{
+       uint16_t len = 0;
+       size_t total;
+       size_t uuid16_count = 0;
+       size_t uuid32_count = 0;
+       size_t uuid128_count = 0;
+       uint8_t *uuid16 = NULL;
+       uint8_t *uuid32 = NULL;
+       uint8_t *uuid128 = NULL;
+       uuid_t service;
+       char *uuid_str;
+       unsigned int i;
+
+       eir->flags = -1;
+
+       /* No EIR data to parse */
+       if (eir_data == NULL || eir_length == 0)
+               return 0;
+
+       while (len < eir_length - 1) {
+               uint8_t field_len = eir_data[0];
+
+               /* Check for the end of EIR */
+               if (field_len == 0)
+                       break;
+
+               switch (eir_data[1]) {
+               case EIR_UUID16_SOME:
+               case EIR_UUID16_ALL:
+                       uuid16_count = field_len / 2;
+                       uuid16 = &eir_data[2];
+                       break;
+               case EIR_UUID32_SOME:
+               case EIR_UUID32_ALL:
+                       uuid32_count = field_len / 4;
+                       uuid32 = &eir_data[2];
+                       break;
+               case EIR_UUID128_SOME:
+               case EIR_UUID128_ALL:
+                       uuid128_count = field_len / 16;
+                       uuid128 = &eir_data[2];
+                       break;
+               case EIR_FLAGS:
+                       eir->flags = eir_data[2];
+                       break;
+               case EIR_NAME_SHORT:
+               case EIR_NAME_COMPLETE:
+                       if (g_utf8_validate((char *) &eir_data[2],
+                                                       field_len - 1, NULL))
+                               eir->name = g_strndup((char *) &eir_data[2],
+                                                               field_len - 1);
+                       else
+                               eir->name = g_strdup("");
+                       eir->name_complete = eir_data[1] == EIR_NAME_COMPLETE;
+                       break;
+               }
+
+               len += field_len + 1;
+               eir_data += field_len + 1;
+       }
+
+       /* Bail out if got incorrect length */
+       if (len > eir_length)
+               return -EINVAL;
+
+       total = uuid16_count + uuid32_count + uuid128_count;
+
+       /* No UUIDs were parsed, so skip code below */
+       if (!total)
+               return 0;
+
+       /* Generate uuids in SDP format (EIR data is Little Endian) */
+       service.type = SDP_UUID16;
+       for (i = 0; i < uuid16_count; i++) {
+               uint16_t val16 = uuid16[1];
+
+               val16 = (val16 << 8) + uuid16[0];
+               service.value.uuid16 = val16;
+               uuid_str = bt_uuid2string(&service);
+               eir->services = g_slist_append(eir->services, uuid_str);
+               uuid16 += 2;
+       }
+
+       service.type = SDP_UUID32;
+       for (i = uuid16_count; i < uuid32_count + uuid16_count; i++) {
+               uint32_t val32 = uuid32[3];
+               int k;
+
+               for (k = 2; k >= 0; k--)
+                       val32 = (val32 << 8) + uuid32[k];
+
+               service.value.uuid32 = val32;
+               uuid_str = bt_uuid2string(&service);
+               eir->services = g_slist_append(eir->services, uuid_str);
+               uuid32 += 4;
+       }
+
+       service.type = SDP_UUID128;
+       for (i = uuid32_count + uuid16_count; i < total; i++) {
+               int k;
+
+               for (k = 0; k < 16; k++)
+                       service.value.uuid128.data[k] = uuid128[16 - k - 1];
+
+               uuid_str = bt_uuid2string(&service);
+               eir->services = g_slist_append(eir->services, uuid_str);
+               uuid128 += 16;
+       }
+
+       return 0;
+}
+
+static void free_eir_data(struct eir_data *eir)
+{
+       g_slist_foreach(eir->services, (GFunc) g_free, NULL);
+       g_slist_free(eir->services);
+       g_free(eir->name);
+}
+
+void btd_event_advertising_report(bdaddr_t *local, le_advertising_info *info)
+{
+       struct btd_adapter *adapter;
+       struct eir_data eir_data;
+       int8_t rssi;
+       int err;
+
+       adapter = manager_find_adapter(local);
+       if (adapter == NULL) {
+               error("No matching adapter found");
+               return;
+       }
+
+       memset(&eir_data, 0, sizeof(eir_data));
+       err = parse_eir_data(&eir_data, info->data, info->length);
+       if (err < 0)
+               error("Error parsing advertising data: %s (%d)",
+                                                       strerror(-err), -err);
+
+       rssi = *(info->data + info->length);
+
+       adapter_update_device_from_info(adapter, info->bdaddr, rssi,
+                                       info->evt_type, eir_data.name,
+                                       eir_data.services, eir_data.flags);
+
+       free_eir_data(&eir_data);
+}
+
+static void update_lastseen(bdaddr_t *sba, bdaddr_t *dba)
+{
+       time_t t;
+       struct tm *tm;
+
+       t = time(NULL);
+       tm = gmtime(&t);
+
+       write_lastseen_info(sba, dba, tm);
+}
+
+static void update_lastused(bdaddr_t *sba, bdaddr_t *dba)
+{
+       time_t t;
+       struct tm *tm;
+
+       t = time(NULL);
+       tm = gmtime(&t);
+
+       write_lastused_info(sba, dba, tm);
+}
+
+void btd_event_device_found(bdaddr_t *local, bdaddr_t *peer, uint32_t class,
+                               int8_t rssi, uint8_t *data)
+{
+       char filename[PATH_MAX + 1];
+       struct btd_adapter *adapter;
+       char local_addr[18], peer_addr[18], *alias, *name;
+       name_status_t name_status;
+       struct eir_data eir_data;
+       int state, err;
+       dbus_bool_t legacy;
+       unsigned char features[8];
+       const char *dev_name;
+
+       ba2str(local, local_addr);
+       ba2str(peer, peer_addr);
+
+       adapter = manager_find_adapter(local);
+       if (!adapter) {
+               error("No matching adapter found");
+               return;
+       }
+
+       update_lastseen(local, peer);
+       write_remote_class(local, peer, class);
+
+       if (data)
+               write_remote_eir(local, peer, data);
+
+       /*
+        * Workaround to identify periodic inquiry: inquiry complete event is
+        * sent after each window, however there isn't an event to indicate the
+        * beginning of a new periodic inquiry window.
+        */
+       state = adapter_get_state(adapter);
+       if (!(state & (STATE_STDINQ | STATE_LE_SCAN | STATE_PINQ))) {
+               state |= STATE_PINQ;
+               adapter_set_state(adapter, state);
+       }
+
+       /* the inquiry result can be triggered by NON D-Bus client */
+       if (adapter_get_discover_type(adapter) & DISC_RESOLVNAME &&
+                               adapter_has_discov_sessions(adapter))
+               name_status = NAME_REQUIRED;
+       else
+               name_status = NAME_NOT_REQUIRED;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, local_addr, "aliases");
+       alias = textfile_get(filename, peer_addr);
+
+       create_name(filename, PATH_MAX, STORAGEDIR, local_addr, "names");
+       name = textfile_get(filename, peer_addr);
+
+       if (data)
+               legacy = FALSE;
+       else if (name == NULL)
+               legacy = TRUE;
+       else if (read_remote_features(local, peer, NULL, features) == 0) {
+               if (features[0] & 0x01)
+                       legacy = FALSE;
+               else
+                       legacy = TRUE;
+       } else
+               legacy = TRUE;
+
+       memset(&eir_data, 0, sizeof(eir_data));
+       err = parse_eir_data(&eir_data, data, EIR_DATA_LENGTH);
+       if (err < 0)
+               error("Error parsing EIR data: %s (%d)", strerror(-err), -err);
+
+       /* Complete EIR names are always used. Shortened EIR names are only
+        * used if there is no name already in storage. */
+       dev_name = name;
+       if (eir_data.name != NULL) {
+               if (eir_data.name_complete) {
+                       write_device_name(local, peer, eir_data.name);
+                       name_status = NAME_NOT_REQUIRED;
+                       dev_name = eir_data.name;
+               } else if (name == NULL)
+                       dev_name = eir_data.name;
+       }
+
+       adapter_update_found_devices(adapter, peer, rssi, class, dev_name,
+                                       alias, legacy, eir_data.services,
+                                       name_status);
+
+       free_eir_data(&eir_data);
+       free(name);
+       free(alias);
+}
+
+void btd_event_set_legacy_pairing(bdaddr_t *local, bdaddr_t *peer,
+                                                       gboolean legacy)
+{
+       struct btd_adapter *adapter;
+       struct remote_dev_info *dev, match;
+
+       adapter = manager_find_adapter(local);
+       if (!adapter) {
+               error("No matching adapter found");
+               return;
+       }
+
+       memset(&match, 0, sizeof(struct remote_dev_info));
+       bacpy(&match.bdaddr, peer);
+       match.name_status = NAME_ANY;
+
+       dev = adapter_search_found_devices(adapter, &match);
+       if (dev)
+               dev->legacy = legacy;
+}
+
+void btd_event_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class)
+{
+       uint32_t old_class = 0;
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+       const gchar *dev_path;
+       DBusConnection *conn = get_dbus_connection();
+
+       read_remote_class(local, peer, &old_class);
+
+       if (old_class == class)
+               return;
+
+       write_remote_class(local, peer, class);
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE))
+               return;
+
+       if (!device)
+               return;
+
+       dev_path = device_get_path(device);
+
+       emit_property_changed(conn, dev_path, DEVICE_INTERFACE, "Class",
+                               DBUS_TYPE_UINT32, &class);
+}
+
+void btd_event_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status,
+                               char *name)
+{
+       struct btd_adapter *adapter;
+       char srcaddr[18], dstaddr[18];
+       int state;
+       struct btd_device *device;
+       struct remote_dev_info match, *dev_info;
+
+       if (status == 0) {
+               char *end;
+
+               /* It's ok to cast end between const and non-const since
+                * we know it points to inside of name which is non-const */
+               if (!g_utf8_validate(name, -1, (const char **) &end))
+                       *end = '\0';
+
+               write_device_name(local, peer, name);
+       }
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE))
+               return;
+
+       ba2str(local, srcaddr);
+       ba2str(peer, dstaddr);
+
+       if (status != 0)
+               goto proceed;
+
+       bacpy(&match.bdaddr, peer);
+       match.name_status = NAME_ANY;
+
+       dev_info = adapter_search_found_devices(adapter, &match);
+       if (dev_info) {
+               g_free(dev_info->name);
+               dev_info->name = g_strdup(name);
+               adapter_emit_device_found(adapter, dev_info);
+       }
+
+       if (device)
+               device_set_name(device, name);
+
+proceed:
+       /* remove from remote name request list */
+       adapter_remove_found_device(adapter, peer);
+
+       /* check if there is more devices to request names */
+       if (adapter_resolve_names(adapter) == 0)
+               return;
+
+       state = adapter_get_state(adapter);
+       state &= ~STATE_RESOLVNAME;
+       adapter_set_state(adapter, state);
+}
+
+int btd_event_link_key_notify(bdaddr_t *local, bdaddr_t *peer,
+                               uint8_t *key, uint8_t key_type,
+                               uint8_t pin_length)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+       int ret;
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE))
+               return -ENODEV;
+
+       DBG("storing link key of type 0x%02x", key_type);
+
+       ret = write_link_key(local, peer, key, key_type, pin_length);
+
+       if (ret == 0 && device_is_temporary(device))
+               device_set_temporary(device, FALSE);
+
+       return ret;
+}
+
+void btd_event_conn_complete(bdaddr_t *local, bdaddr_t *peer)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE))
+               return;
+
+       update_lastused(local, peer);
+
+       adapter_add_connection(adapter, device);
+}
+
+void btd_event_conn_failed(bdaddr_t *local, bdaddr_t *peer, uint8_t status)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+       DBusConnection *conn = get_dbus_connection();
+
+       DBG("status 0x%02x", status);
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE))
+               return;
+
+       if (!device)
+               return;
+
+       if (device_is_temporary(device))
+               adapter_remove_device(conn, adapter, device, TRUE);
+}
+
+void btd_event_disconn_complete(bdaddr_t *local, bdaddr_t *peer)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+
+       DBG("");
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE))
+               return;
+
+       if (!device)
+               return;
+
+       adapter_remove_connection(adapter, device);
+}
+
+/* Section reserved to device HCI callbacks */
+
+void btd_event_le_set_scan_enable_complete(bdaddr_t *local, uint8_t status)
+{
+       struct btd_adapter *adapter;
+       int state;
+
+       adapter = manager_find_adapter(local);
+       if (!adapter) {
+               error("No matching adapter found");
+               return;
+       }
+
+       if (status) {
+               error("Can't enable/disable LE scan");
+               return;
+       }
+
+       state = adapter_get_state(adapter);
+
+       /* Enabling or disabling ? */
+       if (state & STATE_LE_SCAN)
+               state &= ~STATE_LE_SCAN;
+       else
+               state |= STATE_LE_SCAN;
+
+       adapter_set_state(adapter, state);
+}
+
+void btd_event_returned_link_key(bdaddr_t *local, bdaddr_t *peer)
+{
+       struct btd_adapter *adapter;
+       struct btd_device *device;
+
+       if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE))
+               return;
+
+       device_set_paired(device, TRUE);
+}
diff --git a/src/event.h b/src/event.h
new file mode 100644 (file)
index 0000000..765390a
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 btd_event_request_pin(bdaddr_t *sba, bdaddr_t *dba);
+void btd_event_advertising_report(bdaddr_t *local, le_advertising_info *info);
+void btd_event_device_found(bdaddr_t *local, bdaddr_t *peer, uint32_t class,
+                                               int8_t rssi, uint8_t *data);
+void btd_event_set_legacy_pairing(bdaddr_t *local, bdaddr_t *peer, gboolean legacy);
+void btd_event_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class);
+void btd_event_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status, char *name);
+void btd_event_conn_complete(bdaddr_t *local, bdaddr_t *peer);
+void btd_event_conn_failed(bdaddr_t *local, bdaddr_t *peer, uint8_t status);
+void btd_event_disconn_complete(bdaddr_t *local, bdaddr_t *peer);
+void btd_event_bonding_complete(bdaddr_t *local, bdaddr_t *peer,
+                                                       uint8_t status);
+void btd_event_simple_pairing_complete(bdaddr_t *local, bdaddr_t *peer, uint8_t status);
+void btd_event_le_set_scan_enable_complete(bdaddr_t *local, uint8_t status);
+void btd_event_returned_link_key(bdaddr_t *local, bdaddr_t *peer);
+int btd_event_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey);
+int btd_event_user_passkey(bdaddr_t *sba, bdaddr_t *dba);
+int btd_event_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey);
+int btd_event_link_key_notify(bdaddr_t *local, bdaddr_t *peer, uint8_t *key,
+                                       uint8_t key_type, uint8_t pin_length);
diff --git a/src/genbuiltin b/src/genbuiltin
new file mode 100755 (executable)
index 0000000..8b6f047
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+for i in $*
+do
+       echo "extern struct bluetooth_plugin_desc __bluetooth_builtin_$i;"
+done
+
+echo
+echo "static struct bluetooth_plugin_desc *__bluetooth_builtin[] = {"
+
+for i in $*
+do
+       echo "  &__bluetooth_builtin_$i,"
+done
+
+echo "  NULL"
+echo "};"
diff --git a/src/glib-helper.c b/src/glib-helper.c
new file mode 100644 (file)
index 0000000..22c14e7
--- /dev/null
@@ -0,0 +1,587 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+
+#include "btio.h"
+#include "sdpd.h"
+#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;
+
+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);
+}
+
+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,
+                                                       gpointer 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;
+}
+
+static gint find_by_bdaddr(gconstpointer data, gconstpointer 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 match, *ctxt;
+       GSList *l;
+
+       memset(&match, 0, sizeof(match));
+       bacpy(&match.src, src);
+       bacpy(&match.dst, dst);
+
+       /* Ongoing SDP Discovery */
+       l = g_slist_find_custom(context_list, &match, find_by_bdaddr);
+       if (l == NULL)
+               return -ENOENT;
+
+       ctxt = l->data;
+
+       if (!ctxt->session)
+               return -ENOTCONN;
+
+       if (ctxt->io_id)
+               g_source_remove(ctxt->io_id);
+
+       if (ctxt->session)
+               sdp_close(ctxt->session);
+
+       search_context_cleanup(ctxt);
+
+       return 0;
+}
+
+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            },
+       { }
+};
+
+static uint16_t name2class(const char *pattern)
+{
+       int i;
+
+       for (i = 0; bt_services[i].name; i++) {
+               if (strcasecmp(bt_services[i].name, pattern) == 0)
+                       return bt_services[i].class;
+       }
+
+       return 0;
+}
+
+static inline gboolean is_uuid128(const char *string)
+{
+       return (strlen(string) == 36 &&
+                       string[8] == '-' &&
+                       string[13] == '-' &&
+                       string[18] == '-' &&
+                       string[23] == '-');
+}
+
+static int string2uuid16(uuid_t *uuid, const char *string)
+{
+       int length = strlen(string);
+       char *endptr = NULL;
+       uint16_t u16;
+
+       if (length != 4 && length != 6)
+               return -EINVAL;
+
+       u16 = strtol(string, &endptr, 16);
+       if (endptr && *endptr == '\0') {
+               sdp_uuid16_create(uuid, u16);
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+char *bt_name2string(const char *pattern)
+{
+       uuid_t uuid;
+       uint16_t uuid16;
+       int i;
+
+       /* UUID 128 string format */
+       if (is_uuid128(pattern))
+               return g_strdup(pattern);
+
+       /* Friendly service name format */
+       uuid16 = 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 = name2class(string);
+               if (class) {
+                       sdp_uuid16_create(uuid, class);
+                       return 0;
+               }
+
+               return string2uuid16(uuid, string);
+       }
+}
+
+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;
+}
diff --git a/src/glib-helper.h b/src/glib-helper.h
new file mode 100644 (file)
index 0000000..510d61a
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <bluetooth/bluetooth.h>
+typedef void (*bt_callback_t) (sdp_list_t *recs, int err, gpointer user_data);
+typedef void (*bt_destroy_t) (gpointer user_data);
+
+int bt_search_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);
+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);
+
+#ifdef NEED_G_SLIST_FREE_FULL
+static inline void g_slist_free_full(GSList *list, GDestroyNotify free_func)
+{
+       g_slist_foreach(list, (GFunc) free_func, NULL);
+       g_slist_free(list);
+}
+#endif
diff --git a/src/hcid.h b/src/hcid.h
new file mode 100644 (file)
index 0000000..e3064d8
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 HCID_DEFAULT_DISCOVERABLE_TIMEOUT 180 /* 3 minutes */
+
+/* Timeout for hci_send_req (milliseconds) */
+#define HCI_REQ_TIMEOUT                5000
+struct main_opts {
+       char            host_name[40];
+       unsigned long   flags;
+       char            *name;
+       uint32_t        class;
+       uint16_t        pageto;
+       uint32_t        discovto;
+       uint32_t        pairto;
+       uint16_t        link_mode;
+       uint16_t        link_policy;
+       gboolean        remember_powered;
+       gboolean        reverse_sdp;
+       gboolean        name_resolv;
+       gboolean        debug_keys;
+       gboolean        attrib_server;
+       gboolean        le;
+
+       uint8_t         scan;
+       uint8_t         mode;
+       uint8_t         discov_interval;
+       char            deviceid[15]; /* FIXME: */
+};
+
+enum {
+       HCID_SET_NAME,
+       HCID_SET_CLASS,
+       HCID_SET_PAGETO,
+       HCID_SET_DISCOVTO,
+};
+
+extern struct main_opts main_opts;
+
+void btd_start_exit_timer(void);
+void btd_stop_exit_timer(void);
+
+gboolean plugin_init(GKeyFile *config, const char *enable,
+                                                       const char *disable);
+void plugin_cleanup(void);
+
+void rfkill_init(void);
+void rfkill_exit(void);
diff --git a/src/log.c b/src/log.c
new file mode 100644 (file)
index 0000000..4ec4633
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,136 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+#include <glib.h>
+
+#include "log.h"
+
+void info(const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+
+       vsyslog(LOG_INFO, format, ap);
+
+       va_end(ap);
+}
+
+void error(const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+
+       vsyslog(LOG_ERR, format, ap);
+
+       va_end(ap);
+}
+
+void btd_debug(const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+
+       vsyslog(LOG_DEBUG, format, ap);
+
+       va_end(ap);
+}
+
+extern struct btd_debug_desc __start___debug[];
+extern struct btd_debug_desc __stop___debug[];
+
+static gchar **enabled = NULL;
+
+static gboolean is_enabled(struct btd_debug_desc *desc)
+{
+       int i;
+
+       if (enabled == NULL)
+               return 0;
+
+       for (i = 0; enabled[i] != NULL; i++) {
+               if (desc->name != NULL && g_pattern_match_simple(enabled[i],
+                                                       desc->name) == TRUE)
+                       return 1;
+               if (desc->file != NULL && g_pattern_match_simple(enabled[i],
+                                                       desc->file) == TRUE)
+                       return 1;
+       }
+
+       return 0;
+}
+
+void __btd_toggle_debug()
+{
+       struct btd_debug_desc *desc;
+
+       for (desc = __start___debug; desc < __stop___debug; desc++)
+               desc->flags |= BTD_DEBUG_FLAG_PRINT;
+}
+
+void __btd_log_init(const char *debug, int detach)
+{
+       int option = LOG_NDELAY | LOG_PID;
+       struct btd_debug_desc *desc;
+       const char *name = NULL, *file = NULL;
+
+       if (debug != NULL)
+               enabled = g_strsplit_set(debug, ":, ", 0);
+
+       for (desc = __start___debug; desc < __stop___debug; desc++) {
+               if (file != NULL || name != NULL) {
+                       if (g_strcmp0(desc->file, file) == 0) {
+                               if (desc->name == NULL)
+                                       desc->name = name;
+                       } else
+                               file = NULL;
+               }
+
+               if (is_enabled(desc))
+                       desc->flags |= BTD_DEBUG_FLAG_PRINT;
+       }
+
+       if (!detach)
+               option |= LOG_PERROR;
+
+       openlog("bluetoothd", option, LOG_DAEMON);
+
+       syslog(LOG_INFO, "Bluetooth deamon %s", VERSION);
+}
+
+void __btd_log_cleanup(void)
+{
+       closelog();
+
+       g_strfreev(enabled);
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644 (file)
index 0000000..cc45cbf
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,57 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 info(const char *format, ...) __attribute__((format(printf, 1, 2)));
+void error(const char *format, ...) __attribute__((format(printf, 1, 2)));
+
+void btd_debug(const char *format, ...) __attribute__((format(printf, 1, 2)));
+
+void __btd_log_init(const char *debug, int detach);
+void __btd_log_cleanup(void);
+void __btd_toggle_debug();
+
+struct btd_debug_desc {
+       const char *name;
+       const char *file;
+#define BTD_DEBUG_FLAG_DEFAULT (0)
+#define BTD_DEBUG_FLAG_PRINT   (1 << 0)
+       unsigned int flags;
+} __attribute__((aligned(8)));
+
+/**
+ * DBG:
+ * @fmt: format string
+ * @arg...: list of arguments
+ *
+ * Simple macro around btd_debug() which also include the function
+ * name it is called in.
+ */
+#define DBG(fmt, arg...) do { \
+       static struct btd_debug_desc __btd_debug_desc \
+       __attribute__((used, section("__debug"), aligned(8))) = { \
+               .file = __FILE__, .flags = BTD_DEBUG_FLAG_DEFAULT, \
+       }; \
+       if (__btd_debug_desc.flags & BTD_DEBUG_FLAG_PRINT) \
+               btd_debug("%s:%s() " fmt,  __FILE__, __FUNCTION__ , ## arg); \
+} while (0)
+
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..9a890ad
--- /dev/null
@@ -0,0 +1,515 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include <gdbus.h>
+
+#include "log.h"
+
+#include "hcid.h"
+#include "sdpd.h"
+#include "attrib-server.h"
+#include "adapter.h"
+#include "event.h"
+#include "dbus-common.h"
+#include "agent.h"
+#include "manager.h"
+
+#ifdef HAVE_CAPNG
+#include <cap-ng.h>
+#endif
+#include <bluetooth/sdp_lib.h>
+#define BLUEZ_NAME "org.bluez"
+
+#define LAST_ADAPTER_EXIT_TIMEOUT 30
+
+#define DEFAULT_DISCOVERABLE_TIMEOUT 180 /* 3 minutes */
+
+struct main_opts main_opts;
+sdp_session_t *g_cached_session = NULL;
+
+static GKeyFile *load_config(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 parse_config(GKeyFile *config)
+{
+       GError *err = NULL;
+       char *str;
+       int val;
+       gboolean boolean;
+
+       if (!config)
+               return;
+
+       DBG("parsing main.conf");
+
+       val = g_key_file_get_integer(config, "General",
+                                               "DiscoverableTimeout", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else {
+               DBG("discovto=%d", val);
+               main_opts.discovto = val;
+               main_opts.flags |= 1 << HCID_SET_DISCOVTO;
+       }
+
+       val = g_key_file_get_integer(config, "General",
+                                               "PairableTimeout", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else {
+               DBG("pairto=%d", val);
+               main_opts.pairto = val;
+       }
+
+       val = g_key_file_get_integer(config, "General", "PageTimeout", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else {
+               DBG("pageto=%d", val);
+               main_opts.pageto = val;
+               main_opts.flags |= 1 << HCID_SET_PAGETO;
+       }
+
+       str = g_key_file_get_string(config, "General", "Name", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else {
+               DBG("name=%s", str);
+               g_free(main_opts.name);
+               main_opts.name = g_strdup(str);
+               main_opts.flags |= 1 << HCID_SET_NAME;
+               g_free(str);
+       }
+
+       str = g_key_file_get_string(config, "General", "Class", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else {
+               DBG("class=%s", str);
+               main_opts.class = strtol(str, NULL, 16);
+               main_opts.flags |= 1 << HCID_SET_CLASS;
+               g_free(str);
+       }
+
+       val = g_key_file_get_integer(config, "General",
+                                       "DiscoverSchedulerInterval", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else {
+               DBG("discov_interval=%d", val);
+               main_opts.discov_interval = val;
+       }
+
+       boolean = g_key_file_get_boolean(config, "General",
+                                               "InitiallyPowered", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else if (boolean == FALSE)
+               main_opts.mode = MODE_OFF;
+
+       boolean = g_key_file_get_boolean(config, "General",
+                                               "RememberPowered", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else
+               main_opts.remember_powered = boolean;
+
+       str = g_key_file_get_string(config, "General", "DeviceID", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else {
+               DBG("deviceid=%s", str);
+               strncpy(main_opts.deviceid, str,
+                                       sizeof(main_opts.deviceid) - 1);
+               g_free(str);
+       }
+
+       boolean = g_key_file_get_boolean(config, "General",
+                                               "ReverseServiceDiscovery", &err);
+       if (err) {
+               DBG("%s", err->message);
+               g_clear_error(&err);
+       } else
+               main_opts.reverse_sdp = boolean;
+
+       boolean = g_key_file_get_boolean(config, "General",
+                                               "NameResolving", &err);
+       if (err)
+               g_clear_error(&err);
+       else
+               main_opts.name_resolv = boolean;
+
+       boolean = g_key_file_get_boolean(config, "General",
+                                               "DebugKeys", &err);
+       if (err)
+               g_clear_error(&err);
+       else
+               main_opts.debug_keys = boolean;
+
+       boolean = g_key_file_get_boolean(config, "General",
+                                               "AttributeServer", &err);
+       if (err)
+               g_clear_error(&err);
+       else
+               main_opts.attrib_server = boolean;
+
+       boolean = g_key_file_get_boolean(config, "General",
+                                               "EnableLE", &err);
+       if (err)
+               g_clear_error(&err);
+       else
+               main_opts.le = boolean;
+
+       main_opts.link_mode = HCI_LM_ACCEPT;
+
+       main_opts.link_policy = HCI_LP_RSWITCH | HCI_LP_SNIFF |
+                                               HCI_LP_HOLD | HCI_LP_PARK;
+}
+
+static void init_defaults(void)
+{
+       /* Default HCId settings */
+       memset(&main_opts, 0, sizeof(main_opts));
+       main_opts.scan  = SCAN_PAGE;
+#ifdef __TIZEN_PATCH__
+       main_opts.mode  = MODE_DISCOVERABLE;
+#else
+       main_opts.mode  = MODE_CONNECTABLE;
+#endif
+       main_opts.name  = g_strdup("BlueZ");
+       main_opts.discovto      = DEFAULT_DISCOVERABLE_TIMEOUT;
+       main_opts.remember_powered = TRUE;
+       main_opts.reverse_sdp = TRUE;
+       main_opts.name_resolv = TRUE;
+
+       if (gethostname(main_opts.host_name, sizeof(main_opts.host_name) - 1) < 0)
+               strcpy(main_opts.host_name, "noname");
+}
+
+static GMainLoop *event_loop;
+
+static void sig_term(int sig)
+{
+       g_main_loop_quit(event_loop);
+}
+
+static void sig_debug(int sig)
+{
+       __btd_toggle_debug();
+}
+
+static gchar *option_debug = NULL;
+static gchar *option_plugin = NULL;
+static gchar *option_noplugin = NULL;
+static gboolean option_detach = TRUE;
+static gboolean option_version = FALSE;
+static gboolean option_udev = FALSE;
+
+static guint last_adapter_timeout = 0;
+
+static gboolean exit_timeout(gpointer data)
+{
+       g_main_loop_quit(event_loop);
+       last_adapter_timeout = 0;
+       return FALSE;
+}
+
+void btd_start_exit_timer(void)
+{
+       if (option_udev == FALSE)
+               return;
+
+       if (last_adapter_timeout > 0)
+               g_source_remove(last_adapter_timeout);
+
+       last_adapter_timeout = g_timeout_add_seconds(LAST_ADAPTER_EXIT_TIMEOUT,
+                                               exit_timeout, NULL);
+}
+
+void btd_stop_exit_timer(void)
+{
+       if (last_adapter_timeout == 0)
+               return;
+
+       g_source_remove(last_adapter_timeout);
+       last_adapter_timeout = 0;
+}
+
+static void disconnect_dbus(void)
+{
+       DBusConnection *conn = get_dbus_connection();
+
+       if (!conn || !dbus_connection_get_is_connected(conn))
+               return;
+
+       manager_cleanup(conn, "/");
+
+       set_dbus_connection(NULL);
+
+       dbus_connection_unref(conn);
+}
+
+static int connect_dbus(void)
+{
+       DBusConnection *conn;
+       DBusError err;
+
+       dbus_error_init(&err);
+
+       conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, BLUEZ_NAME, &err);
+       if (!conn) {
+               if (dbus_error_is_set(&err)) {
+                       dbus_error_free(&err);
+                       return -EIO;
+               }
+               return -EALREADY;
+       }
+
+       if (!manager_init(conn, "/"))
+               return -EIO;
+
+       set_dbus_connection(conn);
+
+       return 0;
+}
+
+static gboolean parse_debug(const char *key, const char *value,
+                               gpointer user_data, GError **error)
+{
+       if (value)
+               option_debug = g_strdup(value);
+       else
+               option_debug = g_strdup("*");
+
+       return TRUE;
+}
+
+static GOptionEntry options[] = {
+       { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG,
+                               G_OPTION_ARG_CALLBACK, parse_debug,
+                               "Specify debug options to enable", "DEBUG" },
+       { "plugin", 'p', 0, G_OPTION_ARG_STRING, &option_plugin,
+                               "Specify plugins to load", "NAME,..," },
+       { "noplugin", 'P', 0, G_OPTION_ARG_STRING, &option_noplugin,
+                               "Specify plugins not to load", "NAME,..." },
+       { "nodetach", 'n', G_OPTION_FLAG_REVERSE,
+                               G_OPTION_ARG_NONE, &option_detach,
+                               "Don't run as daemon in background" },
+       { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version,
+                               "Show version information and exit" },
+       { "udev", 'u', 0, G_OPTION_ARG_NONE, &option_udev,
+                               "Run from udev mode of operation" },
+       { NULL },
+};
+
+int main(int argc, char *argv[])
+{
+       GOptionContext *context;
+       GError *err = NULL;
+       struct sigaction sa;
+       uint16_t mtu = 0;
+       GKeyFile *config;
+       info("(info)Bluetoothd main starting .......\n");
+
+       init_defaults();
+
+#ifdef HAVE_CAPNG
+       /* Drop capabilities */
+       capng_clear(CAPNG_SELECT_BOTH);
+       capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED,
+                                       CAP_NET_BIND_SERVICE, CAP_NET_ADMIN,
+                                               CAP_NET_RAW, CAP_IPC_LOCK, -1);
+       capng_apply(CAPNG_SELECT_BOTH);
+#endif
+
+       context = g_option_context_new(NULL);
+       g_option_context_add_main_entries(context, options, NULL);
+
+       if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) {
+               if (err != NULL) {
+                       g_printerr("%s\n", err->message);
+                       g_error_free(err);
+               } else
+                       g_printerr("An unknown error occurred\n");
+               exit(1);
+       }
+
+       g_option_context_free(context);
+
+       if (option_version == TRUE) {
+               printf("%s\n", VERSION);
+               exit(0);
+       }
+
+       if (option_udev == TRUE) {
+               int err;
+
+               option_detach = TRUE;
+               err = connect_dbus();
+               if (err < 0) {
+                       if (err == -EALREADY)
+                               exit(0);
+                       exit(1);
+               }
+       }
+
+       if (option_detach == TRUE && option_udev == FALSE) {
+               if (daemon(0, 0)) {
+                       perror("Can't start daemon");
+                       exit(1);
+               }
+       }
+
+       umask(0077);
+
+       __btd_log_init(option_debug, option_detach);
+
+       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_debug;
+       sigaction(SIGUSR2, &sa, NULL);
+
+       sa.sa_handler = SIG_IGN;
+       sigaction(SIGPIPE, &sa, NULL);
+
+       config = load_config(CONFIGDIR "/main.conf");
+
+       parse_config(config);
+
+       agent_init();
+
+       if (option_udev == FALSE) {
+               if (connect_dbus() < 0) {
+                       error("Unable to get on D-Bus");
+                       exit(1);
+               }
+       } else {
+               if (daemon(0, 0)) {
+                       perror("Can't start daemon");
+                       exit(1);
+               }
+       }
+
+       start_sdp_server(mtu, main_opts.deviceid, SDP_SERVER_COMPAT);
+
+       if (main_opts.attrib_server) {
+               if (attrib_server_init() < 0)
+                       error("Can't initialize attribute server");
+       }
+
+       /* Loading plugins has to be done after D-Bus has been setup since
+        * the plugins might wanna expose some paths on the bus. However the
+        * best order of how to init various subsystems of the Bluetooth
+        * daemon needs to be re-worked. */
+       plugin_init(config, option_plugin, option_noplugin);
+
+       event_loop = g_main_loop_new(NULL, FALSE);
+
+       if (adapter_ops_setup() < 0) {
+               error("adapter_ops_setup failed");
+               exit(1);
+       }
+
+       rfkill_init();
+
+       DBG("Entering main loop");
+
+       g_main_loop_run(event_loop);
+
+       disconnect_dbus();
+
+       rfkill_exit();
+
+       plugin_cleanup();
+
+       if (main_opts.attrib_server)
+               attrib_server_exit();
+
+       stop_sdp_server();
+
+       agent_exit();
+
+       g_main_loop_unref(event_loop);
+
+       if (config)
+               g_key_file_free(config);
+
+       info("Exit");
+
+       __btd_log_cleanup();
+
+       return 0;
+}
diff --git a/src/main.conf b/src/main.conf
new file mode 100644 (file)
index 0000000..9f472a0
--- /dev/null
@@ -0,0 +1,67 @@
+[General]
+
+# List of plugins that should not be loaded on bluetoothd startup
+#DisablePlugins = network,input
+
+# Default adaper name
+# %h - substituted for hostname
+# %d - substituted for adapter id
+Name = %h-%d
+
+# Default device class. Only the major and minor device class bits are
+# considered.
+#Class = 0x000100      # Computer
+Class = 0x5A020C       # Smart phone
+
+# How long to stay in discoverable mode before going back to non-discoverable
+# The value is in seconds. Default is 180, i.e. 3 minutes.
+# 0 = disable timer, i.e. stay discoverable forever
+DiscoverableTimeout = 0
+
+# How long to stay in pairable mode before going back to non-discoverable
+# The value is in seconds. Default is 0.
+# 0 = disable timer, i.e. stay pairable forever
+PairableTimeout = 0
+
+# Use some other page timeout than the controller default one
+# which is 16384 (10 seconds).
+PageTimeout = 8192
+
+# Discover scheduler interval used in Adapter.DiscoverDevices
+# The value is in seconds. Defaults is 0 to use controller scheduler.
+DiscoverSchedulerInterval = 0
+
+# What value should be assumed for the adapter Powered property when
+# SetProperty(Powered, ...) hasn't been called yet. Defaults to true
+InitiallyPowered = true
+
+# Remember the previously stored Powered state when initializing adapters
+RememberPowered = true
+
+# Use vendor, product and version information for DID profile support.
+# The values are separated by ":" and VID, PID and version.
+#DeviceID = 1234:5678:abcd
+
+# Do reverse service discovery for previously unknown devices that connect to
+# us. This option is really only needed for qualification since the BITE tester
+# doesn't like us doing reverse SDP for some test cases (though there could in
+# theory be other useful purposes for this too). Defaults to true.
+ReverseServiceDiscovery = true
+
+# Enable name resolving after inquiry. Set it to 'false' if you don't need
+# remote devices name and want shorter discovery cycle. Defaults to 'true'.
+NameResolving = true
+
+# Enable runtime persistency of debug link keys. Default is false which
+# makes debug link keys valid only for the duration of the connection
+# that they were created for.
+DebugKeys = false
+
+# Enable Low Energy support if the dongle supports. Default is false.
+# Enable/Disable interleave discovery and attribute server over LE.
+EnableLE = false
+
+# Enable the GATT Attribute Server. Default is false, because it is only
+# useful for testing. Attribute server is not enabled over LE if EnableLE
+# is false.
+AttributeServer = false
diff --git a/src/manager.c b/src/manager.c
new file mode 100644 (file)
index 0000000..e805e0c
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "dbus-common.h"
+#include "log.h"
+#include "adapter.h"
+#include "error.h"
+#include "manager.h"
+
+static char base_path[50] = "/org/bluez";
+
+static DBusConnection *connection = NULL;
+static int default_adapter_id = -1;
+static GSList *adapters = NULL;
+
+const char *manager_get_base_path(void)
+{
+       return base_path;
+}
+
+static DBusMessage *default_adapter(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+       struct btd_adapter *adapter;
+       const gchar *path;
+
+       adapter = manager_find_adapter_by_id(default_adapter_id);
+       if (!adapter)
+               return btd_error_no_such_adapter(msg);
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       path = adapter_get_path(adapter);
+
+       dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID);
+
+       return reply;
+}
+
+static DBusMessage *find_adapter(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+       struct btd_adapter *adapter;
+       const char *pattern;
+       int dev_id;
+       const gchar *path;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern,
+                                                       DBUS_TYPE_INVALID))
+               return NULL;
+
+       /* hci_devid() would make sense to use here, except it is
+        * restricted to devices which are up */
+       if (!strcmp(pattern, "any") || !strcmp(pattern, "00:00:00:00:00:00")) {
+               path = adapter_any_get_path();
+               if (path != NULL)
+                       goto done;
+               return btd_error_no_such_adapter(msg);
+       } else if (!strncmp(pattern, "hci", 3) && strlen(pattern) >= 4) {
+               dev_id = atoi(pattern + 3);
+               adapter = manager_find_adapter_by_id(dev_id);
+       } else {
+               bdaddr_t bdaddr;
+               str2ba(pattern, &bdaddr);
+               adapter = manager_find_adapter(&bdaddr);
+       }
+
+       if (!adapter)
+               return btd_error_no_such_adapter(msg);
+
+       path = adapter_get_path(adapter);
+
+done:
+       reply = dbus_message_new_method_return(msg);
+       if (!reply)
+               return NULL;
+
+       dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path,
+                                                       DBUS_TYPE_INVALID);
+
+       return reply;
+}
+
+static DBusMessage *list_adapters(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       DBusMessageIter iter;
+       DBusMessageIter array_iter;
+       DBusMessage *reply;
+       GSList *l;
+
+       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_TYPE_OBJECT_PATH_AS_STRING, &array_iter);
+
+       for (l = adapters; l; l = l->next) {
+               struct btd_adapter *adapter = l->data;
+               const gchar *path = adapter_get_path(adapter);
+
+               dbus_message_iter_append_basic(&array_iter,
+                                       DBUS_TYPE_OBJECT_PATH, &path);
+       }
+
+       dbus_message_iter_close_container(&iter, &array_iter);
+
+       return reply;
+}
+
+static DBusMessage *get_properties(DBusConnection *conn,
+                                       DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+       DBusMessageIter iter;
+       DBusMessageIter dict;
+       GSList *list;
+       char **array;
+       int i;
+
+       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);
+
+       array = g_new0(char *, g_slist_length(adapters) + 1);
+       for (i = 0, list = adapters; list; list = list->next) {
+               struct btd_adapter *adapter = list->data;
+
+               array[i] = (char *) adapter_get_path(adapter);
+               i++;
+       }
+       dict_append_array(&dict, "Adapters", DBUS_TYPE_OBJECT_PATH, &array, i);
+       g_free(array);
+
+       dbus_message_iter_close_container(&iter, &dict);
+
+       return reply;
+}
+
+static GDBusMethodTable manager_methods[] = {
+       { "GetProperties",      "",     "a{sv}",get_properties  },
+       { "DefaultAdapter",     "",     "o",    default_adapter },
+       { "FindAdapter",        "s",    "o",    find_adapter    },
+       { "ListAdapters",       "",     "ao",   list_adapters,
+                                               G_DBUS_METHOD_FLAG_DEPRECATED},
+       { }
+};
+
+static GDBusSignalTable manager_signals[] = {
+       { "PropertyChanged",            "sv"    },
+       { "AdapterAdded",               "o"     },
+       { "AdapterRemoved",             "o"     },
+       { "DefaultAdapterChanged",      "o"     },
+       { }
+};
+
+dbus_bool_t manager_init(DBusConnection *conn, const char *path)
+{
+       connection = conn;
+
+       snprintf(base_path, sizeof(base_path), "/org/bluez/%d", getpid());
+
+       return g_dbus_register_interface(conn, "/", MANAGER_INTERFACE,
+                                       manager_methods, manager_signals,
+                                       NULL, NULL, NULL);
+}
+
+static void manager_update_adapters(void)
+{
+       GSList *list;
+       char **array;
+       int i;
+
+       array = g_new0(char *, g_slist_length(adapters) + 1);
+       for (i = 0, list = adapters; list; list = list->next) {
+               struct btd_adapter *adapter = list->data;
+
+               array[i] = (char *) adapter_get_path(adapter);
+               i++;
+       }
+
+       emit_array_property_changed(connection, "/",
+                                       MANAGER_INTERFACE, "Adapters",
+                                       DBUS_TYPE_OBJECT_PATH, &array, i);
+
+       g_free(array);
+}
+
+static void manager_set_default_adapter(int id)
+{
+       struct btd_adapter *adapter;
+       const gchar *path;
+
+       default_adapter_id = id;
+
+       adapter = manager_find_adapter_by_id(id);
+       if (!adapter)
+               return;
+
+       path = adapter_get_path(adapter);
+
+       g_dbus_emit_signal(connection, "/",
+                       MANAGER_INTERFACE,
+                       "DefaultAdapterChanged",
+                       DBUS_TYPE_OBJECT_PATH, &path,
+                       DBUS_TYPE_INVALID);
+}
+
+static void manager_remove_adapter(struct btd_adapter *adapter)
+{
+       uint16_t dev_id = adapter_get_dev_id(adapter);
+       const gchar *path = adapter_get_path(adapter);
+
+       adapters = g_slist_remove(adapters, adapter);
+
+       manager_update_adapters();
+
+       if (default_adapter_id == dev_id || default_adapter_id < 0) {
+               int new_default = hci_get_route(NULL);
+
+               manager_set_default_adapter(new_default);
+       }
+
+       g_dbus_emit_signal(connection, "/",
+                       MANAGER_INTERFACE, "AdapterRemoved",
+                       DBUS_TYPE_OBJECT_PATH, &path,
+                       DBUS_TYPE_INVALID);
+
+       adapter_remove(adapter);
+
+       if (adapters == NULL)
+               btd_start_exit_timer();
+}
+
+void manager_cleanup(DBusConnection *conn, const char *path)
+{
+       g_slist_foreach(adapters, (GFunc) manager_remove_adapter, NULL);
+       g_slist_free(adapters);
+
+       g_dbus_unregister_interface(conn, "/", MANAGER_INTERFACE);
+}
+
+static gint adapter_id_cmp(gconstpointer a, gconstpointer b)
+{
+       struct btd_adapter *adapter = (struct btd_adapter *) a;
+       uint16_t id = GPOINTER_TO_UINT(b);
+       uint16_t dev_id = adapter_get_dev_id(adapter);
+
+       return dev_id == id ? 0 : -1;
+}
+
+static gint adapter_cmp(gconstpointer a, gconstpointer b)
+{
+       struct btd_adapter *adapter = (struct btd_adapter *) a;
+       const bdaddr_t *bdaddr = b;
+       bdaddr_t src;
+
+       adapter_get_address(adapter, &src);
+
+       return bacmp(&src, bdaddr);
+}
+
+struct btd_adapter *manager_find_adapter(const bdaddr_t *sba)
+{
+       GSList *match;
+
+       match = g_slist_find_custom(adapters, sba, adapter_cmp);
+       if (!match)
+               return NULL;
+
+       return match->data;
+}
+
+struct btd_adapter *manager_find_adapter_by_id(int id)
+{
+       GSList *match;
+
+       match = g_slist_find_custom(adapters, GINT_TO_POINTER(id),
+                                                       adapter_id_cmp);
+       if (!match)
+               return NULL;
+
+       return match->data;
+}
+
+void manager_foreach_adapter(adapter_cb func, gpointer user_data)
+{
+       g_slist_foreach(adapters, (GFunc) func, user_data);
+}
+
+GSList *manager_get_adapters(void)
+{
+       return adapters;
+}
+
+void manager_add_adapter(const char *path)
+{
+       g_dbus_emit_signal(connection, "/",
+                               MANAGER_INTERFACE, "AdapterAdded",
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID);
+
+       manager_update_adapters();
+
+       btd_stop_exit_timer();
+}
+
+struct btd_adapter *btd_manager_register_adapter(int id)
+{
+       struct btd_adapter *adapter;
+       const char *path;
+
+       adapter = manager_find_adapter_by_id(id);
+       if (adapter) {
+               error("Unable to register adapter: hci%d already exist", id);
+               return NULL;
+       }
+
+       adapter = adapter_create(connection, id);
+       if (!adapter)
+               return NULL;
+
+       adapters = g_slist_append(adapters, adapter);
+
+       if (!adapter_init(adapter)) {
+               btd_adapter_unref(adapter);
+               return NULL;
+       }
+
+       path = adapter_get_path(adapter);
+       g_dbus_emit_signal(connection, "/",
+                               MANAGER_INTERFACE, "AdapterAdded",
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID);
+
+       manager_update_adapters();
+
+       btd_stop_exit_timer();
+
+       if (default_adapter_id < 0)
+               manager_set_default_adapter(id);
+
+       DBG("Adapter %s registered", path);
+
+       return btd_adapter_ref(adapter);
+}
+
+int btd_manager_unregister_adapter(int id)
+{
+       struct btd_adapter *adapter;
+       const gchar *path;
+
+       adapter = manager_find_adapter_by_id(id);
+       if (!adapter)
+               return -1;
+
+       path = adapter_get_path(adapter);
+
+       info("Unregister path: %s", path);
+
+       manager_remove_adapter(adapter);
+
+       return 0;
+}
+
+void btd_manager_set_did(uint16_t vendor, uint16_t product, uint16_t version)
+{
+       GSList *l;
+
+       for (l = adapters; l != NULL; l = g_slist_next(l)) {
+               struct btd_adapter *adapter = l->data;
+
+               btd_adapter_set_did(adapter, vendor, product, version);
+       }
+}
diff --git a/src/manager.h b/src/manager.h
new file mode 100644 (file)
index 0000000..05c38b3
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <bluetooth/bluetooth.h>
+#include <dbus/dbus.h>
+
+#define MANAGER_INTERFACE "org.bluez.Manager"
+
+typedef void (*adapter_cb) (struct btd_adapter *adapter, gpointer user_data);
+
+dbus_bool_t manager_init(DBusConnection *conn, const char *path);
+void manager_cleanup(DBusConnection *conn, const char *path);
+
+const char *manager_get_base_path(void);
+struct btd_adapter *manager_find_adapter(const bdaddr_t *sba);
+struct btd_adapter *manager_find_adapter_by_id(int id);
+void manager_foreach_adapter(adapter_cb func, gpointer user_data);
+GSList *manager_get_adapters(void);
+struct btd_adapter *btd_manager_register_adapter(int id);
+int btd_manager_unregister_adapter(int id);
+void manager_add_adapter(const char *path);
+void btd_manager_set_did(uint16_t vendor, uint16_t product, uint16_t version);
diff --git a/src/oui.c b/src/oui.c
new file mode 100644 (file)
index 0000000..80bb3d3
--- /dev/null
+++ b/src/oui.c
@@ -0,0 +1,101 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "oui.h"
+
+/* http://standards.ieee.org/regauth/oui/oui.txt */
+
+char *ouitocomp(const char *oui)
+{
+       struct stat st;
+       char *str, *map, *off, *end;
+       int fd;
+
+       fd = open(OUIFILE, 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/src/oui.h b/src/oui.h
new file mode 100644 (file)
index 0000000..ad66e56
--- /dev/null
+++ b/src/oui.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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/src/plugin.c b/src/plugin.c
new file mode 100644 (file)
index 0000000..3506553
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "hcid.h"
+#include "btio.h"
+
+static GSList *plugins = NULL;
+
+struct bluetooth_plugin {
+       void *handle;
+       gboolean active;
+       struct bluetooth_plugin_desc *desc;
+};
+
+static gint compare_priority(gconstpointer a, gconstpointer b)
+{
+       const struct bluetooth_plugin *plugin1 = a;
+       const struct bluetooth_plugin *plugin2 = b;
+
+       return plugin2->desc->priority - plugin1->desc->priority;
+}
+
+static gboolean add_plugin(void *handle, struct bluetooth_plugin_desc *desc)
+{
+       struct bluetooth_plugin *plugin;
+
+       if (desc->init == NULL)
+               return FALSE;
+
+       if (g_str_equal(desc->version, VERSION) == FALSE) {
+               error("Version mismatch for %s", desc->name);
+               return FALSE;
+       }
+
+       DBG("Loading %s plugin", desc->name);
+
+       plugin = g_try_new0(struct bluetooth_plugin, 1);
+       if (plugin == NULL)
+               return FALSE;
+
+       plugin->handle = handle;
+       plugin->active = FALSE;
+       plugin->desc = desc;
+
+       plugins = g_slist_insert_sorted(plugins, plugin, compare_priority);
+
+       return TRUE;
+}
+
+static gboolean enable_plugin(const char *name, char **conf_disable,
+                                       char **cli_enable, char **cli_disable)
+{
+       if (conf_disable) {
+               for (; *conf_disable; conf_disable++)
+                       if (g_pattern_match_simple(*conf_disable, name))
+                               break;
+               if (*conf_disable) {
+                       info("Excluding (conf) %s", name);
+                       return FALSE;
+               }
+       }
+
+       if (cli_disable) {
+               for (; *cli_disable; cli_disable++)
+                       if (g_pattern_match_simple(*cli_disable, name))
+                               break;
+               if (*cli_disable) {
+                       info("Excluding (cli) %s", name);
+                       return FALSE;
+               }
+       }
+
+       if (cli_enable) {
+               for (; *cli_enable; cli_enable++)
+                       if (g_pattern_match_simple(*cli_enable, name))
+                               break;
+               if (!*cli_enable) {
+                       info("Ignoring (cli) %s", name);
+                       return FALSE;
+               }
+       }
+
+       return TRUE;
+}
+
+#include "builtin.h"
+
+gboolean plugin_init(GKeyFile *config, const char *enable, const char *disable)
+{
+       GSList *list;
+       GDir *dir;
+       const gchar *file;
+       char **conf_disabled, **cli_disabled, **cli_enabled;
+       unsigned int i;
+
+       /* Make a call to BtIO API so its symbols got resolved before the
+        * plugins are loaded. */
+       bt_io_error_quark();
+
+       if (config)
+               conf_disabled = g_key_file_get_string_list(config, "General",
+                                                       "DisablePlugins",
+                                                       NULL, NULL);
+       else
+               conf_disabled = NULL;
+
+       if (enable)
+               cli_enabled = g_strsplit_set(enable, ", ", -1);
+       else
+               cli_enabled = NULL;
+
+       if (disable)
+               cli_disabled = g_strsplit_set(disable, ", ", -1);
+       else
+               cli_disabled = NULL;
+
+       DBG("Loading builtin plugins");
+
+       for (i = 0; __bluetooth_builtin[i]; i++) {
+               if (!enable_plugin(__bluetooth_builtin[i]->name, conf_disabled,
+                                               cli_enabled, cli_disabled))
+                       continue;
+
+               add_plugin(NULL,  __bluetooth_builtin[i]);
+       }
+
+       if (strlen(PLUGINDIR) == 0)
+               goto start;
+
+       DBG("Loading plugins %s", PLUGINDIR);
+
+       dir = g_dir_open(PLUGINDIR, 0, NULL);
+       if (!dir)
+               goto start;
+
+       while ((file = g_dir_read_name(dir)) != NULL) {
+               struct bluetooth_plugin_desc *desc;
+               void *handle;
+               gchar *filename;
+
+               if (g_str_has_prefix(file, "lib") == TRUE ||
+                               g_str_has_suffix(file, ".so") == FALSE)
+                       continue;
+
+               filename = g_build_filename(PLUGINDIR, file, NULL);
+
+               handle = dlopen(filename, RTLD_NOW);
+               if (handle == NULL) {
+                       error("Can't load plugin %s: %s", filename,
+                                                               dlerror());
+                       g_free(filename);
+                       continue;
+               }
+
+               g_free(filename);
+
+               desc = dlsym(handle, "bluetooth_plugin_desc");
+               if (desc == NULL) {
+                       error("Can't load plugin description: %s", dlerror());
+                       dlclose(handle);
+                       continue;
+               }
+
+               if (!enable_plugin(desc->name, conf_disabled,
+                                               cli_enabled, cli_disabled)) {
+                       dlclose(handle);
+                       continue;
+               }
+
+               if (add_plugin(handle, desc) == FALSE)
+                       dlclose(handle);
+       }
+
+       g_dir_close(dir);
+
+start:
+       for (list = plugins; list; list = list->next) {
+               struct bluetooth_plugin *plugin = list->data;
+
+               if (plugin->desc->init() < 0) {
+                       error("Failed to init %s plugin", plugin->desc->name);
+                       continue;
+               }
+
+               plugin->active = TRUE;
+       }
+
+       g_strfreev(conf_disabled);
+       g_strfreev(cli_enabled);
+       g_strfreev(cli_disabled);
+
+       return TRUE;
+}
+
+void plugin_cleanup(void)
+{
+       GSList *list;
+
+       DBG("Cleanup plugins");
+
+       for (list = plugins; list; list = list->next) {
+               struct bluetooth_plugin *plugin = list->data;
+
+               if (plugin->active == TRUE && plugin->desc->exit)
+                       plugin->desc->exit();
+
+               if (plugin->handle != NULL)
+                       dlclose(plugin->handle);
+
+               g_free(plugin);
+       }
+
+       g_slist_free(plugins);
+}
diff --git a/src/plugin.h b/src/plugin.h
new file mode 100644 (file)
index 0000000..30bd415
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 BLUETOOTH_PLUGIN_PRIORITY_LOW      -100
+#define BLUETOOTH_PLUGIN_PRIORITY_DEFAULT     0
+#define BLUETOOTH_PLUGIN_PRIORITY_HIGH      100
+
+struct bluetooth_plugin_desc {
+       const char *name;
+       const char *version;
+       int priority;
+       int (*init) (void);
+       void (*exit) (void);
+};
+
+#ifdef BLUETOOTH_PLUGIN_BUILTIN
+#define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit) \
+               struct bluetooth_plugin_desc __bluetooth_builtin_ ## name = { \
+                       #name, version, priority, init, exit \
+               };
+#else
+#define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit) \
+               extern struct bluetooth_plugin_desc bluetooth_plugin_desc \
+                               __attribute__ ((visibility("default"))); \
+               struct bluetooth_plugin_desc bluetooth_plugin_desc = { \
+                       #name, version, priority, init, exit \
+               };
+#endif
diff --git a/src/ppoll.h b/src/ppoll.h
new file mode 100644 (file)
index 0000000..7d09d44
--- /dev/null
@@ -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/src/rfkill.c b/src/rfkill.c
new file mode 100644 (file)
index 0000000..b40c6e7
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "log.h"
+#include "adapter.h"
+#include "manager.h"
+#include "hcid.h"
+
+enum rfkill_type {
+       RFKILL_TYPE_ALL = 0,
+       RFKILL_TYPE_WLAN,
+       RFKILL_TYPE_BLUETOOTH,
+       RFKILL_TYPE_UWB,
+       RFKILL_TYPE_WIMAX,
+       RFKILL_TYPE_WWAN,
+};
+
+enum rfkill_operation {
+       RFKILL_OP_ADD = 0,
+       RFKILL_OP_DEL,
+       RFKILL_OP_CHANGE,
+       RFKILL_OP_CHANGE_ALL,
+};
+
+struct rfkill_event {
+       uint32_t idx;
+       uint8_t  type;
+       uint8_t  op;
+       uint8_t  soft;
+       uint8_t  hard;
+};
+
+static gboolean rfkill_event(GIOChannel *chan,
+                               GIOCondition cond, gpointer data)
+{
+       unsigned char buf[32];
+       struct rfkill_event *event = (void *) buf;
+       struct btd_adapter *adapter;
+       char sysname[PATH_MAX];
+       ssize_t len;
+       int fd, id;
+
+       if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
+               return FALSE;
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       memset(buf, 0, sizeof(buf));
+
+       len = read(fd, buf, sizeof(buf));
+       if (len < 0) {
+               if (errno == EAGAIN)
+                       return TRUE;
+               return FALSE;
+       }
+
+       if (len != sizeof(struct rfkill_event))
+               return TRUE;
+
+       DBG("RFKILL event idx %u type %u op %u soft %u hard %u",
+                                       event->idx, event->type, event->op,
+                                               event->soft, event->hard);
+
+       if (event->soft || event->hard)
+               return TRUE;
+
+       if (event->op != RFKILL_OP_CHANGE)
+               return TRUE;
+
+       if (event->type != RFKILL_TYPE_BLUETOOTH &&
+                                       event->type != RFKILL_TYPE_ALL)
+               return TRUE;
+
+       snprintf(sysname, sizeof(sysname) - 1,
+                       "/sys/class/rfkill/rfkill%u/name", event->idx);
+
+       fd = open(sysname, O_RDONLY);
+       if (fd < 0)
+               return TRUE;
+
+       memset(sysname, 0, sizeof(sysname));
+
+       if (read(fd, sysname, sizeof(sysname)) < 4) {
+               close(fd);
+               return TRUE;
+       }
+
+       close(fd);
+
+       if (g_str_has_prefix(sysname, "hci") == FALSE)
+               return TRUE;
+
+       id = atoi(sysname + 3);
+       if (id < 0)
+               return TRUE;
+
+       adapter = manager_find_adapter_by_id(id);
+       if (!adapter)
+               return TRUE;
+
+       DBG("RFKILL unblock for hci%d", id);
+
+       btd_adapter_restore_powered(adapter);
+
+       return TRUE;
+}
+
+static GIOChannel *channel = NULL;
+
+void rfkill_init(void)
+{
+       int fd;
+
+       if (!main_opts.remember_powered)
+               return;
+
+       fd = open("/dev/rfkill", O_RDWR);
+       if (fd < 0) {
+               error("Failed to open RFKILL control device");
+               return;
+       }
+
+       channel = g_io_channel_unix_new(fd);
+       g_io_channel_set_close_on_unref(channel, TRUE);
+
+       g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+                                                       rfkill_event, NULL);
+}
+
+void rfkill_exit(void)
+{
+       if (!channel)
+               return;
+
+       g_io_channel_shutdown(channel, TRUE, NULL);
+       g_io_channel_unref(channel);
+
+       channel = NULL;
+}
diff --git a/src/sdp-xml.c b/src/sdp-xml.c
new file mode 100644 (file)
index 0000000..3aa9df0
--- /dev/null
@@ -0,0 +1,789 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.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, "<nil/>\n");
+               break;
+
+       case SDP_BOOL:
+               appender(data, indent);
+               appender(data, "<boolean value=\"");
+               appender(data, value->val.uint8 ? "true" : "false");
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_UINT8:
+               appender(data, indent);
+               appender(data, "<uint8 value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "0x%02x", value->val.uint8);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_UINT16:
+               appender(data, indent);
+               appender(data, "<uint16 value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "0x%04x", value->val.uint16);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_UINT32:
+               appender(data, indent);
+               appender(data, "<uint32 value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "0x%08x", value->val.uint32);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_UINT64:
+               appender(data, indent);
+               appender(data, "<uint64 value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "0x%016jx", value->val.uint64);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_UINT128:
+               appender(data, indent);
+               appender(data, "<uint128 value=\"");
+
+               for (i = 0; i < 16; i++) {
+                       sprintf(&buf[i * 2], "%02x",
+                               (unsigned char) value->val.uint128.data[i]);
+               }
+
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_INT8:
+               appender(data, indent);
+               appender(data, "<int8 value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "%d", value->val.int8);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_INT16:
+               appender(data, indent);
+               appender(data, "<int16 value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "%d", value->val.int16);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_INT32:
+               appender(data, indent);
+               appender(data, "<int32 value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "%d", value->val.int32);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_INT64:
+               appender(data, indent);
+               appender(data, "<int64 value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "%jd", value->val.int64);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_INT128:
+               appender(data, indent);
+               appender(data, "<int128 value=\"");
+
+               for (i = 0; i < 16; i++) {
+                       sprintf(&buf[i * 2], "%02x",
+                               (unsigned char) value->val.int128.data[i]);
+               }
+               appender(data, buf);
+
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_UUID16:
+               appender(data, indent);
+               appender(data, "<uuid value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "0x%04x", value->val.uuid.value.uuid16);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_UUID32:
+               appender(data, indent);
+               appender(data, "<uuid value=\"");
+               snprintf(buf, STRBUFSIZE - 1, "0x%08x", value->val.uuid.value.uuid32);
+               appender(data, buf);
+               appender(data, "\" />\n");
+               break;
+
+       case SDP_UUID128:
+               appender(data, indent);
+               appender(data, "<uuid value=\"");
+
+               snprintf(buf, STRBUFSIZE - 1,
+                        "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+                        (unsigned char) value->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, "<text ");
+
+               if (hex) {
+                       appender(data, "encoding=\"hex\" ");
+                       strBuf = malloc(sizeof(char)
+                                                * ((value->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 = 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, "<url value=\"");
+               strBuf = strndup(value->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, "<sequence>\n");
+
+               convert_raw_data_to_xml(value->val.dataseq,
+                                       indent_level + 1, data, appender);
+
+               appender(data, indent);
+               appender(data, "</sequence>\n");
+
+               break;
+
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+               appender(data, indent);
+
+               appender(data, "<alternate>\n");
+
+               convert_raw_data_to_xml(value->val.dataseq,
+                                       indent_level + 1, data, appender);
+               appender(data, indent);
+
+               appender(data, "</alternate>\n");
+
+               break;
+       }
+
+       convert_raw_data_to_xml(value->next, indent_level, data, appender);
+}
+
+struct conversion_data {
+       void *data;
+       void (*appender)(void *data, const char *);
+};
+
+static void convert_raw_attr_to_xml_func(void *val, void *data)
+{
+       struct conversion_data *cd = data;
+       sdp_data_t *value = val;
+       char buf[STRBUFSIZE];
+
+       buf[STRBUFSIZE - 1] = '\0';
+       snprintf(buf, STRBUFSIZE - 1, "\t<attribute id=\"0x%04x\">\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</attribute>\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, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\n");
+               appender(data, "<record>\n");
+               sdp_list_foreach(rec->attrlist,
+                                convert_raw_attr_to_xml_func, &cd);
+               appender(data, "</record>\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);
+
+       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);
+
+       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);
+
+       free(elem->name);
+       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/src/sdp-xml.h b/src/sdp-xml.h
new file mode 100644 (file)
index 0000000..7031276
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <bluetooth/sdp.h>
+
+#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/src/sdpd-database.c b/src/sdpd-database.c
new file mode 100644 (file)
index 0000000..08f542f
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "sdpd.h"
+#include "log.h"
+#include "adapter.h"
+#include "manager.h"
+
+static sdp_list_t *service_db;
+static sdp_list_t *access_db;
+
+typedef struct {
+       uint32_t handle;
+       bdaddr_t device;
+} sdp_access_t;
+
+/*
+ * Ordering function called when inserting a service record.
+ * The service repository is a linked list in sorted order
+ * and the service record handle is the sort key
+ */
+int record_sort(const void *r1, const void *r2)
+{
+       const sdp_record_t *rec1 = r1;
+       const sdp_record_t *rec2 = r2;
+
+       if (!rec1 || !rec2) {
+               error("NULL RECORD LIST FATAL");
+               return -1;
+       }
+
+       return rec1->handle - rec2->handle;
+}
+
+static int access_sort(const void *r1, const void *r2)
+{
+       const sdp_access_t *rec1 = r1;
+       const sdp_access_t *rec2 = r2;
+
+       if (!rec1 || !rec2) {
+               error("NULL RECORD LIST FATAL");
+               return -1;
+       }
+
+       return rec1->handle - rec2->handle;
+}
+
+static void access_free(void *p)
+{
+       free(p);
+}
+
+/*
+ * Reset the service repository by deleting its contents
+ */
+void sdp_svcdb_reset()
+{
+       sdp_list_free(service_db, (sdp_free_func_t) sdp_record_free);
+       sdp_list_free(access_db, access_free);
+}
+
+typedef struct _indexed {
+       int sock;
+       sdp_record_t *record;
+} sdp_indexed_t;
+
+static sdp_list_t *socket_index;
+
+/*
+ * collect all services registered over this socket
+ */
+void sdp_svcdb_collect_all(int sock)
+{
+       sdp_list_t *p, *q;
+
+       for (p = socket_index, q = 0; p; ) {
+               sdp_indexed_t *item = p->data;
+               if (item->sock == sock) {
+                       sdp_list_t *next = p->next;
+                       sdp_record_remove(item->record->handle);
+                       sdp_record_free(item->record);
+                       free(item);
+                       if (q)
+                               q->next = next;
+                       else
+                               socket_index = next;
+                       free(p);
+                       p = next;
+               } else if (item->sock > sock)
+                       return;
+               else {
+                       q = p;
+                       p = p->next;
+               }
+       }
+}
+
+void sdp_svcdb_collect(sdp_record_t *rec)
+{
+       sdp_list_t *p, *q;
+
+       for (p = socket_index, q = 0; p; q = p, p = p->next) {
+               sdp_indexed_t *item = p->data;
+               if (rec == item->record) {
+                       free(item);
+                       if (q)
+                               q->next = p->next;
+                       else
+                               socket_index = p->next;
+                       free(p);
+                       return;
+               }
+       }
+}
+
+static int compare_indices(const void *i1, const void *i2)
+{
+       const sdp_indexed_t *s1 = i1;
+       const sdp_indexed_t *s2 = i2;
+       return s1->sock - s2->sock;
+}
+
+void sdp_svcdb_set_collectable(sdp_record_t *record, int sock)
+{
+       sdp_indexed_t *item = malloc(sizeof(sdp_indexed_t));
+       item->sock = sock;
+       item->record = record;
+       socket_index = sdp_list_insert_sorted(socket_index, item, compare_indices);
+}
+
+/*
+ * Add a service record to the repository
+ */
+void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec)
+{
+       struct btd_adapter *adapter;
+       sdp_access_t *dev;
+
+       SDPDBG("Adding rec : 0x%lx", (long) rec);
+       SDPDBG("with handle : 0x%x", rec->handle);
+
+       service_db = sdp_list_insert_sorted(service_db, rec, record_sort);
+
+       dev = malloc(sizeof(*dev));
+       if (!dev)
+               return;
+
+       bacpy(&dev->device, device);
+       dev->handle = rec->handle;
+
+       access_db = sdp_list_insert_sorted(access_db, dev, access_sort);
+
+       if (bacmp(device, BDADDR_ANY) == 0) {
+               manager_foreach_adapter(adapter_service_insert, rec);
+               return;
+       }
+
+       adapter = manager_find_adapter(device);
+       if (adapter)
+               adapter_service_insert(adapter, rec);
+}
+
+static sdp_list_t *record_locate(uint32_t handle)
+{
+       if (service_db) {
+               sdp_list_t *p;
+               sdp_record_t r;
+
+               r.handle = handle;
+               p = sdp_list_find(service_db, &r, record_sort);
+               return p;
+       }
+
+       SDPDBG("Could not find svcRec for : 0x%x", handle);
+       return NULL;
+}
+
+static sdp_list_t *access_locate(uint32_t handle)
+{
+       if (access_db) {
+               sdp_list_t *p;
+               sdp_access_t a;
+
+               a.handle = handle;
+               p = sdp_list_find(access_db, &a, access_sort);
+               return p;
+       }
+
+       SDPDBG("Could not find access data for : 0x%x", handle);
+       return NULL;
+}
+
+/*
+ * Given a service record handle, find the record associated with it.
+ */
+sdp_record_t *sdp_record_find(uint32_t handle)
+{
+       sdp_list_t *p = record_locate(handle);
+
+       if (!p) {
+               SDPDBG("Couldn't find record for : 0x%x", handle);
+               return 0;
+       }
+
+       return p->data;
+}
+
+/*
+ * Given a service record handle, remove its record from the repository
+ */
+int sdp_record_remove(uint32_t handle)
+{
+       sdp_list_t *p = record_locate(handle);
+       sdp_record_t *r;
+       sdp_access_t *a;
+
+       if (!p) {
+               error("Remove : Couldn't find record for : 0x%x", handle);
+               return -1;
+       }
+
+       r = p->data;
+       if (r)
+               service_db = sdp_list_remove(service_db, r);
+
+       p = access_locate(handle);
+       if (p == NULL || p->data == NULL)
+               return 0;
+
+       a = p->data;
+
+       if (bacmp(&a->device, BDADDR_ANY) != 0) {
+               struct btd_adapter *adapter = manager_find_adapter(&a->device);
+               if (adapter)
+                       adapter_service_remove(adapter, r);
+       } else
+               manager_foreach_adapter(adapter_service_remove, r);
+
+       access_db = sdp_list_remove(access_db, a);
+       access_free(a);
+
+       return 0;
+}
+
+/*
+ * Return a pointer to the linked list containing the records in sorted order
+ */
+sdp_list_t *sdp_get_record_list(void)
+{
+       return service_db;
+}
+
+sdp_list_t *sdp_get_access_list(void)
+{
+       return access_db;
+}
+
+int sdp_check_access(uint32_t handle, bdaddr_t *device)
+{
+       sdp_list_t *p = access_locate(handle);
+       sdp_access_t *a;
+
+       if (!p)
+               return 1;
+
+       a = p->data;
+       if (!a)
+               return 1;
+
+       if (bacmp(&a->device, device) &&
+                       bacmp(&a->device, BDADDR_ANY) &&
+                       bacmp(device, BDADDR_ANY))
+               return 0;
+
+       return 1;
+}
+
+uint32_t sdp_next_handle(void)
+{
+       uint32_t handle = 0x10000;
+
+       while (sdp_record_find(handle))
+               handle++;
+
+       return handle;
+}
+
+void sdp_init_services_list(bdaddr_t *device)
+{
+       sdp_list_t *p;
+
+       DBG("");
+
+       for (p = access_db; p != NULL; p = p->next) {
+               sdp_access_t *access = p->data;
+               sdp_record_t *rec;
+
+               if (bacmp(BDADDR_ANY, &access->device))
+                       continue;
+
+               rec = sdp_record_find(access->handle);
+               if (rec == NULL)
+                       continue;
+
+               SDPDBG("adding record with handle %x", access->handle);
+
+               manager_foreach_adapter(adapter_service_insert, rec);
+       }
+}
diff --git a/src/sdpd-request.c b/src/sdpd-request.c
new file mode 100644 (file)
index 0000000..1722f78
--- /dev/null
@@ -0,0 +1,1094 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <netinet/in.h>
+
+#include "sdpd.h"
+#include "log.h"
+
+typedef struct {
+       uint32_t timestamp;
+       union {
+               uint16_t maxBytesSent;
+               uint16_t lastIndexSent;
+       } cStateValue;
+} sdp_cont_state_t;
+
+#define SDP_CONT_STATE_SIZE (sizeof(uint8_t) + sizeof(sdp_cont_state_t))
+
+#define MIN(x, y) ((x) < (y)) ? (x): (y)
+
+typedef struct _sdp_cstate_list sdp_cstate_list_t;
+
+struct _sdp_cstate_list {
+       sdp_cstate_list_t *next;
+       uint32_t timestamp;
+       sdp_buf_t buf;
+};
+
+static sdp_cstate_list_t *cstates;
+
+// FIXME: should probably remove it when it's found
+static sdp_buf_t *sdp_get_cached_rsp(sdp_cont_state_t *cstate)
+{
+       sdp_cstate_list_t *p;
+
+       for (p = cstates; p; p = p->next)
+               if (p->timestamp == cstate->timestamp)
+                       return &p->buf;
+       return 0;
+}
+
+static uint32_t sdp_cstate_alloc_buf(sdp_buf_t *buf)
+{
+       sdp_cstate_list_t *cstate = malloc(sizeof(sdp_cstate_list_t));
+       uint8_t *data = malloc(buf->data_size);
+
+       memcpy(data, buf->data, buf->data_size);
+       memset((char *)cstate, 0, sizeof(sdp_cstate_list_t));
+       cstate->buf.data = data;
+       cstate->buf.data_size = buf->data_size;
+       cstate->buf.buf_size = buf->data_size;
+       cstate->timestamp = sdp_get_time();
+       cstate->next = cstates;
+       cstates = cstate;
+       return cstate->timestamp;
+}
+
+/* Additional values for checking datatype (not in spec) */
+#define SDP_TYPE_UUID  0xfe
+#define SDP_TYPE_ATTRID        0xff
+
+struct attrid {
+       uint8_t dtd;
+       union {
+               uint16_t uint16;
+               uint32_t uint32;
+       };
+};
+
+/*
+ * Generic data element sequence extractor. Builds
+ * a list whose elements are those found in the
+ * sequence. The data type of elements found in the
+ * sequence is returned in the reference pDataType
+ */
+static int extract_des(uint8_t *buf, int len, sdp_list_t **svcReqSeq, uint8_t *pDataType, uint8_t expectedType)
+{
+       uint8_t seqType;
+       int scanned, data_size = 0;
+       short numberOfElements = 0;
+       int seqlen = 0;
+       sdp_list_t *pSeq = NULL;
+       uint8_t dataType;
+       int status = 0;
+       const uint8_t *p;
+       size_t bufsize;
+
+       scanned = sdp_extract_seqtype(buf, len, &seqType, &data_size);
+
+       SDPDBG("Seq type : %d", seqType);
+       if (!scanned || (seqType != SDP_SEQ8 && seqType != SDP_SEQ16)) {
+               error("Unknown seq type");
+               return -1;
+       }
+       p = buf + scanned;
+       bufsize = len - scanned;
+
+       SDPDBG("Data size : %d", data_size);
+
+       for (;;) {
+               char *pElem = NULL;
+               int localSeqLength = 0;
+
+               if (bufsize < sizeof(uint8_t)) {
+                       SDPDBG("->Unexpected end of buffer");
+                       goto failed;
+               }
+
+               dataType = *p;
+
+               SDPDBG("Data type: 0x%02x", dataType);
+
+               if (expectedType == SDP_TYPE_UUID) {
+                       if (dataType != SDP_UUID16 && dataType != SDP_UUID32 && dataType != SDP_UUID128) {
+                               SDPDBG("->Unexpected Data type (expected UUID_ANY)");
+                               goto failed;
+                       }
+               } else if (expectedType == SDP_TYPE_ATTRID &&
+                               (dataType != SDP_UINT16 && dataType != SDP_UINT32)) {
+                       SDPDBG("->Unexpected Data type (expected 0x%02x or 0x%02x)",
+                                                               SDP_UINT16, SDP_UINT32);
+                       goto failed;
+               } else if (expectedType != SDP_TYPE_ATTRID && dataType != expectedType) {
+                       SDPDBG("->Unexpected Data type (expected 0x%02x)", expectedType);
+                       goto failed;
+               }
+
+               switch (dataType) {
+               case SDP_UINT16:
+                       p += sizeof(uint8_t);
+                       seqlen += sizeof(uint8_t);
+                       bufsize -= sizeof(uint8_t);
+                       if (bufsize < sizeof(uint16_t)) {
+                               SDPDBG("->Unexpected end of buffer");
+                               goto failed;
+                       }
+
+                       if (expectedType == SDP_TYPE_ATTRID) {
+                               struct attrid *aid;
+                               aid = malloc(sizeof(struct attrid));
+                               aid->dtd = dataType;
+                               bt_put_unaligned(ntohs(bt_get_unaligned((uint16_t *)p)), (uint16_t *)&aid->uint16);
+                               pElem = (char *) aid;
+                       } else {
+                               pElem = malloc(sizeof(uint16_t));
+                               bt_put_unaligned(ntohs(bt_get_unaligned((uint16_t *)p)), (uint16_t *)pElem);
+                       }
+                       p += sizeof(uint16_t);
+                       seqlen += sizeof(uint16_t);
+                       bufsize -= sizeof(uint16_t);
+                       break;
+               case SDP_UINT32:
+                       p += sizeof(uint8_t);
+                       seqlen += sizeof(uint8_t);
+                       bufsize -= sizeof(uint8_t);
+                       if (bufsize < (int)sizeof(uint32_t)) {
+                               SDPDBG("->Unexpected end of buffer");
+                               goto failed;
+                       }
+
+                       if (expectedType == SDP_TYPE_ATTRID) {
+                               struct attrid *aid;
+                               aid = malloc(sizeof(struct attrid));
+                               aid->dtd = dataType;
+                               bt_put_unaligned(ntohl(bt_get_unaligned((uint32_t *)p)), (uint32_t *)&aid->uint32);
+                               pElem = (char *) aid;
+                       } else {
+                               pElem = malloc(sizeof(uint32_t));
+                               bt_put_unaligned(ntohl(bt_get_unaligned((uint32_t *)p)), (uint32_t *)pElem);
+                       }
+                       p += sizeof(uint32_t);
+                       seqlen += sizeof(uint32_t);
+                       bufsize -= sizeof(uint32_t);
+                       break;
+               case SDP_UUID16:
+               case SDP_UUID32:
+               case SDP_UUID128:
+                       pElem = malloc(sizeof(uuid_t));
+                       status = sdp_uuid_extract(p, bufsize, (uuid_t *) pElem, &localSeqLength);
+                       if (status < 0) {
+                               free(pElem);
+                               goto failed;
+                       }
+                       seqlen += localSeqLength;
+                       p += localSeqLength;
+                       bufsize -= localSeqLength;
+                       break;
+               default:
+                       return -1;
+               }
+               if (status == 0) {
+                       pSeq = sdp_list_append(pSeq, pElem);
+                       numberOfElements++;
+                       SDPDBG("No of elements : %d", numberOfElements);
+
+                       if (seqlen == data_size)
+                               break;
+                       else if (seqlen > data_size || seqlen > len)
+                               goto failed;
+               } else
+                       free(pElem);
+       }
+       *svcReqSeq = pSeq;
+       scanned += seqlen;
+       *pDataType = dataType;
+       return scanned;
+
+failed:
+       sdp_list_free(pSeq, free);
+       return -1;
+}
+
+static int sdp_set_cstate_pdu(sdp_buf_t *buf, sdp_cont_state_t *cstate)
+{
+       uint8_t *pdata = buf->data + buf->data_size;
+       int length = 0;
+
+       if (cstate) {
+               SDPDBG("Non null sdp_cstate_t id : 0x%x", cstate->timestamp);
+               *(uint8_t *)pdata = sizeof(sdp_cont_state_t);
+               pdata += sizeof(uint8_t);
+               length += sizeof(uint8_t);
+               memcpy(pdata, cstate, sizeof(sdp_cont_state_t));
+               length += sizeof(sdp_cont_state_t);
+       } else {
+               // set "null" continuation state
+               *(uint8_t *)pdata = 0;
+               pdata += sizeof(uint8_t);
+               length += sizeof(uint8_t);
+       }
+       buf->data_size += length;
+       return length;
+}
+
+static int sdp_cstate_get(uint8_t *buffer, size_t len,
+                                               sdp_cont_state_t **cstate)
+{
+       uint8_t cStateSize = *buffer;
+
+       SDPDBG("Continuation State size : %d", cStateSize);
+
+       if (cStateSize == 0) {
+               *cstate = NULL;
+               return 0;
+       }
+
+       buffer++;
+       len--;
+
+       if (len < sizeof(sdp_cont_state_t))
+               return -EINVAL;
+
+       /*
+        * Check if continuation state exists, if yes attempt
+        * to get response remainder from cache, else send error
+        */
+
+       *cstate = malloc(sizeof(sdp_cont_state_t));
+       if (!(*cstate))
+               return -ENOMEM;
+
+       memcpy(*cstate, buffer, sizeof(sdp_cont_state_t));
+
+       SDPDBG("Cstate TS : 0x%x", (*cstate)->timestamp);
+       SDPDBG("Bytes sent : %d", (*cstate)->cStateValue.maxBytesSent);
+
+       return 0;
+}
+
+/*
+ * The matching process is defined as "each and every UUID
+ * specified in the "search pattern" must be present in the
+ * "target pattern". Here "search pattern" is the set of UUIDs
+ * specified by the service discovery client and "target pattern"
+ * is the set of UUIDs present in a service record.
+ *
+ * Return 1 if each and every UUID in the search
+ * pattern exists in the target pattern, 0 if the
+ * match succeeds and -1 on error.
+ */
+static int sdp_match_uuid(sdp_list_t *search, sdp_list_t *pattern)
+{
+       /*
+        * The target is a sorted list, so we need not look
+        * at all elements to confirm existence of an element
+        * from the search pattern
+        */
+       int patlen = sdp_list_len(pattern);
+
+       if (patlen < sdp_list_len(search))
+               return -1;
+       for (; search; search = search->next) {
+               uuid_t *uuid128;
+               void *data = search->data;
+               sdp_list_t *list;
+               if (data == NULL)
+                       return -1;
+
+               // create 128-bit form of the search UUID
+               uuid128 = sdp_uuid_to_uuid128((uuid_t *)data);
+               list = sdp_list_find(pattern, uuid128, sdp_uuid128_cmp);
+               bt_free(uuid128);
+               if (!list)
+                       return 0;
+       }
+       return 1;
+}
+
+/*
+ * Service search request PDU. This method extracts the search pattern
+ * (a sequence of UUIDs) and calls the matching function
+ * to find matching services
+ */
+static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
+{
+       int status = 0, i, plen, mlen, mtu, scanned;
+       sdp_list_t *pattern = NULL;
+       uint16_t expected, actual, rsp_count = 0;
+       uint8_t dtd;
+       sdp_cont_state_t *cstate = NULL;
+       uint8_t *pCacheBuffer = NULL;
+       int handleSize = 0;
+       uint32_t cStateId = 0;
+       short *pTotalRecordCount, *pCurrentRecordCount;
+       uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t);
+       size_t data_left = req->len - sizeof(sdp_pdu_hdr_t);
+
+       scanned = extract_des(pdata, data_left, &pattern, &dtd, SDP_TYPE_UUID);
+
+       if (scanned == -1) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+       pdata += scanned;
+       data_left -= scanned;
+
+       plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen);
+       mlen = scanned + sizeof(uint16_t) + 1;
+       // ensure we don't read past buffer
+       if (plen < mlen || plen != mlen + *(uint8_t *)(pdata+sizeof(uint16_t))) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       if (data_left < sizeof(uint16_t)) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       expected = ntohs(bt_get_unaligned((uint16_t *)pdata));
+
+       SDPDBG("Expected count: %d", expected);
+       SDPDBG("Bytes scanned : %d", scanned);
+
+       pdata += sizeof(uint16_t);
+       data_left -= sizeof(uint16_t);
+
+       /*
+        * Check if continuation state exists, if yes attempt
+        * to get rsp remainder from cache, else send error
+        */
+       if (sdp_cstate_get(pdata, data_left, &cstate) < 0) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       mtu = req->mtu - sizeof(sdp_pdu_hdr_t) - sizeof(uint16_t) - sizeof(uint16_t) - SDP_CONT_STATE_SIZE;
+       actual = MIN(expected, mtu >> 2);
+
+       /* make space in the rsp buffer for total and current record counts */
+       pdata = buf->data;
+
+       /* total service record count = 0 */
+       pTotalRecordCount = (short *)pdata;
+       bt_put_unaligned(0, (uint16_t *)pdata);
+       pdata += sizeof(uint16_t);
+       buf->data_size += sizeof(uint16_t);
+
+       /* current service record count = 0 */
+       pCurrentRecordCount = (short *)pdata;
+       bt_put_unaligned(0, (uint16_t *)pdata);
+       pdata += sizeof(uint16_t);
+       buf->data_size += sizeof(uint16_t);
+
+       if (cstate == NULL) {
+               /* for every record in the DB, do a pattern search */
+               sdp_list_t *list = sdp_get_record_list();
+
+               handleSize = 0;
+               for (; list && rsp_count < expected; list = list->next) {
+                       sdp_record_t *rec = list->data;
+
+                       SDPDBG("Checking svcRec : 0x%x", rec->handle);
+
+                       if (sdp_match_uuid(pattern, rec->pattern) > 0 &&
+                                       sdp_check_access(rec->handle, &req->device)) {
+                               rsp_count++;
+                               bt_put_unaligned(htonl(rec->handle), (uint32_t *)pdata);
+                               pdata += sizeof(uint32_t);
+                               handleSize += sizeof(uint32_t);
+                       }
+               }
+
+               SDPDBG("Match count: %d", rsp_count);
+
+               buf->data_size += handleSize;
+               bt_put_unaligned(htons(rsp_count), (uint16_t *)pTotalRecordCount);
+               bt_put_unaligned(htons(rsp_count), (uint16_t *)pCurrentRecordCount);
+
+               if (rsp_count > actual) {
+                       /* cache the rsp and generate a continuation state */
+                       cStateId = sdp_cstate_alloc_buf(buf);
+                       /*
+                        * subtract handleSize since we now send only
+                        * a subset of handles
+                        */
+                       buf->data_size -= handleSize;
+               } else {
+                       /* NULL continuation state */
+                       sdp_set_cstate_pdu(buf, NULL);
+               }
+       }
+
+       /* under both the conditions below, the rsp buffer is not built yet */
+       if (cstate || cStateId > 0) {
+               short lastIndex = 0;
+
+               if (cstate) {
+                       /*
+                        * Get the previous sdp_cont_state_t and obtain
+                        * the cached rsp
+                        */
+                       sdp_buf_t *pCache = sdp_get_cached_rsp(cstate);
+                       if (pCache) {
+                               pCacheBuffer = pCache->data;
+                               /* get the rsp_count from the cached buffer */
+                               rsp_count = ntohs(bt_get_unaligned((uint16_t *)pCacheBuffer));
+
+                               /* get index of the last sdp_record_t sent */
+                               lastIndex = cstate->cStateValue.lastIndexSent;
+                       } else {
+                               status = SDP_INVALID_CSTATE;
+                               goto done;
+                       }
+               } else {
+                       pCacheBuffer = buf->data;
+                       lastIndex = 0;
+               }
+
+               /*
+                * Set the local buffer pointer to after the
+                * current record count and increment the cached
+                * buffer pointer to beyond the counters
+                */
+               pdata = (uint8_t *) pCurrentRecordCount + sizeof(uint16_t);
+
+               /* increment beyond the totalCount and the currentCount */
+               pCacheBuffer += 2 * sizeof(uint16_t);
+
+               if (cstate) {
+                       handleSize = 0;
+                       for (i = lastIndex; (i - lastIndex) < actual && i < rsp_count; i++) {
+                               bt_put_unaligned(bt_get_unaligned((uint32_t *)(pCacheBuffer + i * sizeof(uint32_t))), (uint32_t *)pdata);
+                               pdata += sizeof(uint32_t);
+                               handleSize += sizeof(uint32_t);
+                       }
+               } else {
+                       handleSize = actual << 2;
+                       i = actual;
+               }
+
+               buf->data_size += handleSize;
+               bt_put_unaligned(htons(rsp_count), (uint16_t *)pTotalRecordCount);
+               bt_put_unaligned(htons(i - lastIndex), (uint16_t *)pCurrentRecordCount);
+
+               if (i == rsp_count) {
+                       /* set "null" continuationState */
+                       sdp_set_cstate_pdu(buf, NULL);
+               } else {
+                       /*
+                        * there's more: set lastIndexSent to
+                        * the new value and move on
+                        */
+                       sdp_cont_state_t newState;
+
+                       SDPDBG("Setting non-NULL sdp_cstate_t");
+
+                       if (cstate)
+                               memcpy(&newState, cstate, sizeof(sdp_cont_state_t));
+                       else {
+                               memset(&newState, 0, sizeof(sdp_cont_state_t));
+                               newState.timestamp = cStateId;
+                       }
+                       newState.cStateValue.lastIndexSent = i;
+                       sdp_set_cstate_pdu(buf, &newState);
+               }
+       }
+
+done:
+       free(cstate);
+       if (pattern)
+               sdp_list_free(pattern, free);
+
+       return status;
+}
+
+/*
+ * Extract attribute identifiers from the request PDU.
+ * Clients could request a subset of attributes (by id)
+ * from a service record, instead of the whole set. The
+ * requested identifiers are present in the PDU form of
+ * the request
+ */
+static int extract_attrs(sdp_record_t *rec, sdp_list_t *seq, sdp_buf_t *buf)
+{
+       sdp_buf_t pdu;
+
+       if (!rec)
+               return SDP_INVALID_RECORD_HANDLE;
+
+       if (seq == NULL) {
+               SDPDBG("Attribute sequence is NULL");
+               return 0;
+       }
+
+       SDPDBG("Entries in attr seq : %d", sdp_list_len(seq));
+
+       sdp_gen_record_pdu(rec, &pdu);
+
+       for (; seq; seq = seq->next) {
+               struct attrid *aid = seq->data;
+
+               SDPDBG("AttrDataType : %d", aid->dtd);
+
+               if (aid->dtd == SDP_UINT16) {
+                       uint16_t attr = bt_get_unaligned((uint16_t *)&aid->uint16);
+                       sdp_data_t *a = sdp_data_get(rec, attr);
+                       if (a)
+                               sdp_append_to_pdu(buf, a);
+               } else if (aid->dtd == SDP_UINT32) {
+                       uint32_t range = bt_get_unaligned((uint32_t *)&aid->uint32);
+                       uint16_t attr;
+                       uint16_t low = (0xffff0000 & range) >> 16;
+                       uint16_t high = 0x0000ffff & range;
+                       sdp_data_t *data;
+
+                       SDPDBG("attr range : 0x%x", range);
+                       SDPDBG("Low id : 0x%x", low);
+                       SDPDBG("High id : 0x%x", high);
+
+                       if (low == 0x0000 && high == 0xffff && pdu.data_size <= buf->buf_size) {
+                               /* copy it */
+                               memcpy(buf->data, pdu.data, pdu.data_size);
+                               buf->data_size = pdu.data_size;
+                               break;
+                       }
+                       /* (else) sub-range of attributes */
+                       for (attr = low; attr < high; attr++) {
+                               data = sdp_data_get(rec, attr);
+                               if (data)
+                                       sdp_append_to_pdu(buf, data);
+                       }
+                       data = sdp_data_get(rec, high);
+                       if (data)
+                               sdp_append_to_pdu(buf, data);
+               } else {
+                       error("Unexpected data type : 0x%x", aid->dtd);
+                       error("Expect uint16_t or uint32_t");
+                       free(pdu.data);
+                       return SDP_INVALID_SYNTAX;
+               }
+       }
+
+       free(pdu.data);
+
+       return 0;
+}
+
+/*
+ * A request for the attributes of a service record.
+ * First check if the service record (specified by
+ * service record handle) exists, then call the attribute
+ * streaming function
+ */
+static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf)
+{
+       sdp_cont_state_t *cstate = NULL;
+       uint8_t *pResponse = NULL;
+       short cstate_size = 0;
+       sdp_list_t *seq = NULL;
+       uint8_t dtd = 0;
+       int scanned = 0;
+       unsigned int max_rsp_size;
+       int status = 0, plen, mlen;
+       uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t);
+       size_t data_left = req->len - sizeof(sdp_pdu_hdr_t);
+       uint32_t handle;
+
+       if (data_left < sizeof(uint32_t)) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       handle = ntohl(bt_get_unaligned((uint32_t *)pdata));
+
+       pdata += sizeof(uint32_t);
+       data_left -= sizeof(uint32_t);
+
+       if (data_left < sizeof(uint16_t)) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       max_rsp_size = ntohs(bt_get_unaligned((uint16_t *)pdata));
+
+       pdata += sizeof(uint16_t);
+       data_left -= sizeof(uint16_t);
+
+       if (data_left < sizeof(sdp_pdu_hdr_t)) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       /* extract the attribute list */
+       scanned = extract_des(pdata, data_left, &seq, &dtd, SDP_TYPE_ATTRID);
+       if (scanned == -1) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+       pdata += scanned;
+       data_left -= scanned;
+
+       plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen);
+       mlen = scanned + sizeof(uint32_t) + sizeof(uint16_t) + 1;
+       // ensure we don't read past buffer
+       if (plen < mlen || plen != mlen + *(uint8_t *)pdata) {
+               status = SDP_INVALID_PDU_SIZE;
+               goto done;
+       }
+
+       /*
+        * if continuation state exists, attempt
+        * to get rsp remainder from cache, else send error
+        */
+       if (sdp_cstate_get(pdata, data_left, &cstate) < 0) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       SDPDBG("SvcRecHandle : 0x%x", handle);
+       SDPDBG("max_rsp_size : %d", max_rsp_size);
+
+       /*
+        * Check that max_rsp_size is within valid range
+        * a minimum size of 0x0007 has to be used for data field
+        */
+       if (max_rsp_size < 0x0007) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       /*
+        * Calculate Attribute size acording to MTU
+        * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t))
+        */
+       max_rsp_size = MIN(max_rsp_size, req->mtu - sizeof(sdp_pdu_hdr_t) -
+                       sizeof(uint32_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t));
+
+       /* pull header for AttributeList byte count */
+       buf->data += sizeof(uint16_t);
+       buf->buf_size -= sizeof(uint16_t);
+
+       if (cstate) {
+               sdp_buf_t *pCache = sdp_get_cached_rsp(cstate);
+
+               SDPDBG("Obtained cached rsp : %p", pCache);
+
+               if (pCache) {
+                       short sent = MIN(max_rsp_size, pCache->data_size - cstate->cStateValue.maxBytesSent);
+                       pResponse = pCache->data;
+                       memcpy(buf->data, pResponse + cstate->cStateValue.maxBytesSent, sent);
+                       buf->data_size += sent;
+                       cstate->cStateValue.maxBytesSent += sent;
+
+                       SDPDBG("Response size : %d sending now : %d bytes sent so far : %d",
+                               pCache->data_size, sent, cstate->cStateValue.maxBytesSent);
+                       if (cstate->cStateValue.maxBytesSent == pCache->data_size)
+                               cstate_size = sdp_set_cstate_pdu(buf, NULL);
+                       else
+                               cstate_size = sdp_set_cstate_pdu(buf, cstate);
+               } else {
+                       status = SDP_INVALID_CSTATE;
+                       error("NULL cache buffer and non-NULL continuation state");
+               }
+       } else {
+               sdp_record_t *rec = sdp_record_find(handle);
+               status = extract_attrs(rec, seq, buf);
+               if (buf->data_size > max_rsp_size) {
+                       sdp_cont_state_t newState;
+
+                       memset((char *)&newState, 0, sizeof(sdp_cont_state_t));
+                       newState.timestamp = sdp_cstate_alloc_buf(buf);
+                       /*
+                        * Reset the buffer size to the maximum expected and
+                        * set the sdp_cont_state_t
+                        */
+                       SDPDBG("Creating continuation state of size : %d", buf->data_size);
+                       buf->data_size = max_rsp_size;
+                       newState.cStateValue.maxBytesSent = max_rsp_size;
+                       cstate_size = sdp_set_cstate_pdu(buf, &newState);
+               } else {
+                       if (buf->data_size == 0)
+                               sdp_append_to_buf(buf, 0, 0);
+                       cstate_size = sdp_set_cstate_pdu(buf, NULL);
+               }
+       }
+
+       // push header
+       buf->data -= sizeof(uint16_t);
+       buf->buf_size += sizeof(uint16_t);
+
+done:
+       free(cstate);
+       if (seq)
+               sdp_list_free(seq, free);
+       if (status)
+               return status;
+
+       /* set attribute list byte count */
+       bt_put_unaligned(htons(buf->data_size - cstate_size), (uint16_t *)buf->data);
+       buf->data_size += sizeof(uint16_t);
+       return 0;
+}
+
+/*
+ * combined service search and attribute extraction
+ */
+static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf)
+{
+       int status = 0, plen, totscanned;
+       uint8_t *pdata, *pResponse = NULL;
+       unsigned int max;
+       int scanned, rsp_count = 0;
+       sdp_list_t *pattern = NULL, *seq = NULL, *svcList;
+       sdp_cont_state_t *cstate = NULL;
+       short cstate_size = 0;
+       uint8_t dtd = 0;
+       sdp_buf_t tmpbuf;
+       size_t data_left = req->len;
+
+       tmpbuf.data = NULL;
+       pdata = req->buf + sizeof(sdp_pdu_hdr_t);
+       data_left = req->len - sizeof(sdp_pdu_hdr_t);
+       scanned = extract_des(pdata, data_left, &pattern, &dtd, SDP_TYPE_UUID);
+       if (scanned == -1) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+       totscanned = scanned;
+
+       SDPDBG("Bytes scanned: %d", scanned);
+
+       pdata += scanned;
+       data_left -= scanned;
+
+       if (data_left < sizeof(uint16_t)) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       max = ntohs(bt_get_unaligned((uint16_t *)pdata));
+
+       pdata += sizeof(uint16_t);
+       data_left -= sizeof(uint16_t);
+
+       SDPDBG("Max Attr expected: %d", max);
+
+       if (data_left < sizeof(sdp_pdu_hdr_t)) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       /* extract the attribute list */
+       scanned = extract_des(pdata, data_left, &seq, &dtd, SDP_TYPE_ATTRID);
+       if (scanned == -1) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       pdata += scanned;
+       data_left -= scanned;
+
+       totscanned += scanned + sizeof(uint16_t) + 1;
+
+       plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen);
+       if (plen < totscanned || plen != totscanned + *(uint8_t *)pdata) {
+               status = SDP_INVALID_PDU_SIZE;
+               goto done;
+       }
+
+       /*
+        * if continuation state exists attempt
+        * to get rsp remainder from cache, else send error
+        */
+       if (sdp_cstate_get(pdata, data_left, &cstate) < 0) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       svcList = sdp_get_record_list();
+
+       tmpbuf.data = malloc(USHRT_MAX);
+       tmpbuf.data_size = 0;
+       tmpbuf.buf_size = USHRT_MAX;
+       memset(tmpbuf.data, 0, USHRT_MAX);
+
+       /*
+        * Calculate Attribute size acording to MTU
+        * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t))
+        */
+       max = MIN(max, req->mtu - sizeof(sdp_pdu_hdr_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t));
+
+       /* pull header for AttributeList byte count */
+       buf->data += sizeof(uint16_t);
+       buf->buf_size -= sizeof(uint16_t);
+
+       if (cstate == NULL) {
+               /* no continuation state -> create new response */
+               sdp_list_t *p;
+               for (p = svcList; p; p = p->next) {
+                       sdp_record_t *rec = p->data;
+                       if (sdp_match_uuid(pattern, rec->pattern) > 0 &&
+                                       sdp_check_access(rec->handle, &req->device)) {
+                               rsp_count++;
+                               status = extract_attrs(rec, seq, &tmpbuf);
+
+                               SDPDBG("Response count : %d", rsp_count);
+                               SDPDBG("Local PDU size : %d", tmpbuf.data_size);
+                               if (status) {
+                                       SDPDBG("Extract attr from record returns err");
+                                       break;
+                               }
+                               if (buf->data_size + tmpbuf.data_size < buf->buf_size) {
+                                       // to be sure no relocations
+                                       sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size);
+                                       tmpbuf.data_size = 0;
+                                       memset(tmpbuf.data, 0, USHRT_MAX);
+                               } else {
+                                       error("Relocation needed");
+                                       break;
+                               }
+                               SDPDBG("Net PDU size : %d", buf->data_size);
+                       }
+               }
+               if (buf->data_size > max) {
+                       sdp_cont_state_t newState;
+
+                       memset((char *)&newState, 0, sizeof(sdp_cont_state_t));
+                       newState.timestamp = sdp_cstate_alloc_buf(buf);
+                       /*
+                        * Reset the buffer size to the maximum expected and
+                        * set the sdp_cont_state_t
+                        */
+                       buf->data_size = max;
+                       newState.cStateValue.maxBytesSent = max;
+                       cstate_size = sdp_set_cstate_pdu(buf, &newState);
+               } else
+                       cstate_size = sdp_set_cstate_pdu(buf, NULL);
+       } else {
+               /* continuation State exists -> get from cache */
+               sdp_buf_t *pCache = sdp_get_cached_rsp(cstate);
+               if (pCache) {
+                       uint16_t sent = MIN(max, pCache->data_size - cstate->cStateValue.maxBytesSent);
+                       pResponse = pCache->data;
+                       memcpy(buf->data, pResponse + cstate->cStateValue.maxBytesSent, sent);
+                       buf->data_size += sent;
+                       cstate->cStateValue.maxBytesSent += sent;
+                       if (cstate->cStateValue.maxBytesSent == pCache->data_size)
+                               cstate_size = sdp_set_cstate_pdu(buf, NULL);
+                       else
+                               cstate_size = sdp_set_cstate_pdu(buf, cstate);
+               } else {
+                       status = SDP_INVALID_CSTATE;
+                       SDPDBG("Non-null continuation state, but null cache buffer");
+               }
+       }
+
+       if (!rsp_count && !cstate) {
+               // found nothing
+               buf->data_size = 0;
+               sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size);
+               sdp_set_cstate_pdu(buf, NULL);
+       }
+
+       // push header
+       buf->data -= sizeof(uint16_t);
+       buf->buf_size += sizeof(uint16_t);
+
+       if (!status) {
+               /* set attribute list byte count */
+               bt_put_unaligned(htons(buf->data_size - cstate_size), (uint16_t *)buf->data);
+               buf->data_size += sizeof(uint16_t);
+       }
+
+done:
+       free(cstate);
+       free(tmpbuf.data);
+       if (pattern)
+               sdp_list_free(pattern, free);
+       if (seq)
+               sdp_list_free(seq, free);
+       return status;
+}
+
+/*
+ * Top level request processor. Calls the appropriate processing
+ * function based on request type. Handles service registration
+ * client requests also.
+ */
+static void process_request(sdp_req_t *req)
+{
+       sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *)req->buf;
+       sdp_pdu_hdr_t *rsphdr;
+       sdp_buf_t rsp;
+       uint8_t *buf = malloc(USHRT_MAX);
+       int sent = 0;
+       int status = SDP_INVALID_SYNTAX;
+
+       memset(buf, 0, USHRT_MAX);
+       rsp.data = buf + sizeof(sdp_pdu_hdr_t);
+       rsp.data_size = 0;
+       rsp.buf_size = USHRT_MAX - sizeof(sdp_pdu_hdr_t);
+       rsphdr = (sdp_pdu_hdr_t *)buf;
+
+       if (ntohs(reqhdr->plen) != req->len - sizeof(sdp_pdu_hdr_t)) {
+               status = SDP_INVALID_PDU_SIZE;
+               goto send_rsp;
+       }
+       switch (reqhdr->pdu_id) {
+       case SDP_SVC_SEARCH_REQ:
+               SDPDBG("Got a svc srch req");
+               status = service_search_req(req, &rsp);
+               rsphdr->pdu_id = SDP_SVC_SEARCH_RSP;
+               break;
+       case SDP_SVC_ATTR_REQ:
+               SDPDBG("Got a svc attr req");
+               status = service_attr_req(req, &rsp);
+               rsphdr->pdu_id = SDP_SVC_ATTR_RSP;
+               break;
+       case SDP_SVC_SEARCH_ATTR_REQ:
+               SDPDBG("Got a svc srch attr req");
+               status = service_search_attr_req(req, &rsp);
+               rsphdr->pdu_id = SDP_SVC_SEARCH_ATTR_RSP;
+               break;
+       /* Following requests are allowed only for local connections */
+       case SDP_SVC_REGISTER_REQ:
+               SDPDBG("Service register request");
+               if (req->local) {
+                       status = service_register_req(req, &rsp);
+                       rsphdr->pdu_id = SDP_SVC_REGISTER_RSP;
+               }
+               break;
+       case SDP_SVC_UPDATE_REQ:
+               SDPDBG("Service update request");
+               if (req->local) {
+                       status = service_update_req(req, &rsp);
+                       rsphdr->pdu_id = SDP_SVC_UPDATE_RSP;
+               }
+               break;
+       case SDP_SVC_REMOVE_REQ:
+               SDPDBG("Service removal request");
+               if (req->local) {
+                       status = service_remove_req(req, &rsp);
+                       rsphdr->pdu_id = SDP_SVC_REMOVE_RSP;
+               }
+               break;
+       default:
+               error("Unknown PDU ID : 0x%x received", reqhdr->pdu_id);
+               status = SDP_INVALID_SYNTAX;
+               break;
+       }
+
+send_rsp:
+       if (status) {
+               rsphdr->pdu_id = SDP_ERROR_RSP;
+               bt_put_unaligned(htons(status), (uint16_t *)rsp.data);
+               rsp.data_size = sizeof(uint16_t);
+       }
+
+       SDPDBG("Sending rsp. status %d", status);
+
+       rsphdr->tid  = reqhdr->tid;
+       rsphdr->plen = htons(rsp.data_size);
+
+       /* point back to the real buffer start and set the real rsp length */
+       rsp.data_size += sizeof(sdp_pdu_hdr_t);
+       rsp.data = buf;
+
+       /* stream the rsp PDU */
+       sent = send(req->sock, rsp.data, rsp.data_size, 0);
+
+       SDPDBG("Bytes Sent : %d", sent);
+
+       free(rsp.data);
+       free(req->buf);
+}
+
+void handle_request(int sk, uint8_t *data, int len)
+{
+       struct sockaddr_l2 sa;
+       socklen_t size;
+       sdp_req_t req;
+
+       size = sizeof(sa);
+       if (getpeername(sk, (struct sockaddr *) &sa, &size) < 0) {
+               error("getpeername: %s", strerror(errno));
+               return;
+       }
+
+       if (sa.l2_family == AF_BLUETOOTH) {
+               struct l2cap_options lo;
+
+               memset(&lo, 0, sizeof(lo));
+               size = sizeof(lo);
+
+               if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &lo, &size) < 0) {
+                       error("getsockopt: %s", strerror(errno));
+                       return;
+               }
+
+               bacpy(&req.bdaddr, &sa.l2_bdaddr);
+               req.mtu = lo.omtu;
+               req.local = 0;
+               memset(&sa, 0, sizeof(sa));
+               size = sizeof(sa);
+
+               if (getsockname(sk, (struct sockaddr *) &sa, &size) < 0) {
+                       error("getsockname: %s", strerror(errno));
+                       return;
+               }
+
+               bacpy(&req.device, &sa.l2_bdaddr);
+       } else {
+               bacpy(&req.device, BDADDR_ANY);
+               bacpy(&req.bdaddr, BDADDR_LOCAL);
+               req.mtu = 2048;
+               req.local = 1;
+       }
+
+       req.sock = sk;
+       req.buf  = data;
+       req.len  = len;
+
+       process_request(&req);
+}
diff --git a/src/sdpd-server.c b/src/sdpd-server.c
new file mode 100644 (file)
index 0000000..a92ae2c
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <sys/un.h>
+#include <netinet/in.h>
+
+#include <glib.h>
+
+#include "log.h"
+#include "sdpd.h"
+
+static guint l2cap_id = 0, unix_id = 0;
+
+static int l2cap_sock, unix_sock;
+
+/*
+ * SDP server initialization on startup includes creating the
+ * l2cap and unix sockets over which discovery and registration clients
+ * access us respectively
+ */
+static int init_server(uint16_t mtu, int master, int compat)
+{
+       struct l2cap_options opts;
+       struct sockaddr_l2 l2addr;
+       struct sockaddr_un unaddr;
+       socklen_t optlen;
+
+       /* Register the public browse group root */
+       register_public_browse_group();
+
+       /* Register the SDP server's service record */
+       register_server_service();
+
+       /* Create L2CAP socket */
+       l2cap_sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+       if (l2cap_sock < 0) {
+               error("opening L2CAP socket: %s", strerror(errno));
+               return -1;
+       }
+
+       memset(&l2addr, 0, sizeof(l2addr));
+       l2addr.l2_family = AF_BLUETOOTH;
+       bacpy(&l2addr.l2_bdaddr, BDADDR_ANY);
+       l2addr.l2_psm = htobs(SDP_PSM);
+
+       if (bind(l2cap_sock, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
+               error("binding L2CAP socket: %s", strerror(errno));
+               return -1;
+       }
+
+       if (master) {
+               int opt = L2CAP_LM_MASTER;
+               if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) {
+                       error("setsockopt: %s", strerror(errno));
+                       return -1;
+               }
+       }
+
+       if (mtu > 0) {
+               memset(&opts, 0, sizeof(opts));
+               optlen = sizeof(opts);
+
+               if (getsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) {
+                       error("getsockopt: %s", strerror(errno));
+                       return -1;
+               }
+
+               opts.omtu = mtu;
+               opts.imtu = mtu;
+
+               if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) {
+                       error("setsockopt: %s", strerror(errno));
+                       return -1;
+               }
+       }
+
+       if (listen(l2cap_sock, 5) < 0) {
+               error("listen: %s", strerror(errno));
+               return -1;
+       }
+
+       if (!compat) {
+               unix_sock = -1;
+               return 0;
+       }
+
+       /* Create local Unix socket */
+       unix_sock = socket(PF_UNIX, SOCK_STREAM, 0);
+       if (unix_sock < 0) {
+               error("opening UNIX socket: %s", strerror(errno));
+               return -1;
+       }
+
+       memset(&unaddr, 0, sizeof(unaddr));
+       unaddr.sun_family = AF_UNIX;
+       strcpy(unaddr.sun_path, SDP_UNIX_PATH);
+
+       unlink(unaddr.sun_path);
+
+       if (bind(unix_sock, (struct sockaddr *) &unaddr, sizeof(unaddr)) < 0) {
+               error("binding UNIX socket: %s", strerror(errno));
+               return -1;
+       }
+
+       if (listen(unix_sock, 5) < 0) {
+               error("listen UNIX socket: %s", strerror(errno));
+               return -1;
+       }
+
+       chmod(SDP_UNIX_PATH, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+
+       return 0;
+}
+
+static gboolean io_session_event(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       sdp_pdu_hdr_t hdr;
+       uint8_t *buf;
+       int sk, len, size;
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       sk = g_io_channel_unix_get_fd(chan);
+
+       if (cond & (G_IO_HUP | G_IO_ERR)) {
+               sdp_svcdb_collect_all(sk);
+               return FALSE;
+       }
+
+       len = recv(sk, &hdr, sizeof(sdp_pdu_hdr_t), MSG_PEEK);
+       if (len <= 0) {
+               sdp_svcdb_collect_all(sk);
+               return FALSE;
+       }
+
+       size = sizeof(sdp_pdu_hdr_t) + ntohs(hdr.plen);
+       buf = malloc(size);
+       if (!buf)
+               return TRUE;
+
+       len = recv(sk, buf, size, 0);
+       if (len <= 0) {
+               sdp_svcdb_collect_all(sk);
+               free(buf);
+               return FALSE;
+       }
+
+       handle_request(sk, buf, len);
+
+       return TRUE;
+}
+
+static gboolean io_accept_event(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       GIOChannel *io;
+       int nsk;
+
+       if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL))
+               return FALSE;
+
+       if (data == &l2cap_sock) {
+               struct sockaddr_l2 addr;
+               socklen_t len = sizeof(addr);
+
+               nsk = accept(l2cap_sock, (struct sockaddr *) &addr, &len);
+       } else if (data == &unix_sock) {
+               struct sockaddr_un addr;
+               socklen_t len = sizeof(addr);
+
+               nsk = accept(unix_sock, (struct sockaddr *) &addr, &len);
+       } else
+               return FALSE;
+
+       if (nsk < 0) {
+               error("Can't accept connection: %s", strerror(errno));
+               return TRUE;
+       }
+
+       io = g_io_channel_unix_new(nsk);
+       g_io_channel_set_close_on_unref(io, TRUE);
+
+       g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                       io_session_event, data);
+
+       g_io_channel_unref(io);
+
+       return TRUE;
+}
+
+int start_sdp_server(uint16_t mtu, const char *did, uint32_t flags)
+{
+       int compat = flags & SDP_SERVER_COMPAT;
+       int master = flags & SDP_SERVER_MASTER;
+       GIOChannel *io;
+
+       info("Starting SDP server");
+
+       if (init_server(mtu, master, compat) < 0) {
+               error("Server initialization failed");
+               return -1;
+       }
+
+       if (did && strlen(did) > 0) {
+               const char *ptr = did;
+               uint16_t vid = 0x0000, pid = 0x0000, ver = 0x0000;
+
+               vid = (uint16_t) strtol(ptr, NULL, 16);
+               ptr = strchr(ptr, ':');
+               if (ptr) {
+                       pid = (uint16_t) strtol(ptr + 1, NULL, 16);
+                       ptr = strchr(ptr + 1, ':');
+                       if (ptr)
+                               ver = (uint16_t) strtol(ptr + 1, NULL, 16);
+                       register_device_id(vid, pid, ver);
+               }
+       }
+
+       io = g_io_channel_unix_new(l2cap_sock);
+       g_io_channel_set_close_on_unref(io, TRUE);
+
+       l2cap_id = g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                       io_accept_event, &l2cap_sock);
+       g_io_channel_unref(io);
+
+       if (compat && unix_sock > fileno(stderr)) {
+               io = g_io_channel_unix_new(unix_sock);
+               g_io_channel_set_close_on_unref(io, TRUE);
+
+               unix_id = g_io_add_watch(io,
+                                       G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                       io_accept_event, &unix_sock);
+               g_io_channel_unref(io);
+       }
+
+       return 0;
+}
+
+void stop_sdp_server(void)
+{
+       info("Stopping SDP server");
+
+       sdp_svcdb_reset();
+
+       if (unix_id > 0)
+               g_source_remove(unix_id);
+
+       if (l2cap_id > 0)
+               g_source_remove(l2cap_id);
+
+       l2cap_id = unix_id = 0;
+       l2cap_sock = unix_sock = -1;
+}
diff --git a/src/sdpd-service.c b/src/sdpd-service.c
new file mode 100644 (file)
index 0000000..c52ee63
--- /dev/null
@@ -0,0 +1,614 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <netinet/in.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "sdpd.h"
+#include "log.h"
+#include "adapter.h"
+#include "manager.h"
+
+static sdp_record_t *server = NULL;
+
+/*
+ * List of version numbers supported by the SDP server.
+ * Add to this list when newer versions are supported.
+ */
+static sdp_version_t sdpVnumArray[1] = {
+       { 1, 0 }
+};
+static const int sdpServerVnumEntries = 1;
+
+/*
+ * A simple function which returns the time of day in
+ * seconds. Used for updating the service db state
+ * attribute of the service record of the SDP server
+ */
+uint32_t sdp_get_time()
+{
+       /*
+        * To handle failure in gettimeofday, so an old
+        * value is returned and service does not fail
+        */
+       static struct timeval tm;
+
+       gettimeofday(&tm, NULL);
+       return (uint32_t) tm.tv_sec;
+}
+
+/*
+ * The service database state is an attribute of the service record
+ * of the SDP server itself. This attribute is guaranteed to
+ * change if any of the contents of the service repository
+ * changes. This function updates the timestamp of value of
+ * the svcDBState attribute
+ * Set the SDP server DB. Simply a timestamp which is the marker
+ * when the DB was modified.
+ */
+void update_db_timestamp(void)
+{
+       uint32_t dbts = sdp_get_time();
+       sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &dbts);
+       sdp_attr_replace(server, SDP_ATTR_SVCDB_STATE, d);
+}
+
+#ifdef __TIZEN_PATCH__
+static void update_adapter_svclass_list(struct btd_adapter *adapter)
+{
+       sdp_list_t *list = adapter_get_services(adapter);
+       uint8_t val = 0;
+
+       for (; list; list = list->next) {
+               sdp_record_t *rec = (sdp_record_t *) list->data;
+
+               if (rec->svclass.type != SDP_UUID16)
+                       continue;
+
+               switch (rec->svclass.value.uuid16) {
+               case DIALUP_NET_SVCLASS_ID:
+               case CIP_SVCLASS_ID:
+                       val |= 0x42;    /* Telephony & Networking */
+                       break;
+               case IRMC_SYNC_SVCLASS_ID:
+               case OBEX_OBJPUSH_SVCLASS_ID:
+               case OBEX_FILETRANS_SVCLASS_ID:
+               case IRMC_SYNC_CMD_SVCLASS_ID:
+               case PBAP_PSE_SVCLASS_ID:
+                       val |= 0x10;    /* Object Transfer */
+                       break;
+               case HEADSET_SVCLASS_ID:
+               case HANDSFREE_SVCLASS_ID:
+                       val |= 0x20;    /* Audio */
+                       break;
+               case CORDLESS_TELEPHONY_SVCLASS_ID:
+               case INTERCOM_SVCLASS_ID:
+               case FAX_SVCLASS_ID:
+               case SAP_SVCLASS_ID:
+               /*
+                * Setting the telephony bit for the handsfree audio gateway
+                * role is not required by the HFP specification, but the
+                * Nokia 616 carkit is just plain broken! It will refuse
+                * pairing without this bit set.
+                */
+               case HANDSFREE_AGW_SVCLASS_ID:
+                       val |= 0x40;    /* Telephony */
+                       break;
+               case AUDIO_SOURCE_SVCLASS_ID:
+               case VIDEO_SOURCE_SVCLASS_ID:
+                       val |= 0x08;    /* Capturing */
+                       break;
+               case AUDIO_SINK_SVCLASS_ID:
+               case VIDEO_SINK_SVCLASS_ID:
+                       val |= 0x04;    /* Rendering */
+                       break;
+               case PANU_SVCLASS_ID:
+               case NAP_SVCLASS_ID:
+               case GN_SVCLASS_ID:
+                       val |= 0x02;    /* Networking */
+                       break;
+               }
+       }
+
+       SDPDBG("Service classes 0x%02x", val);
+
+//     manager_update_svc(adapter, val);
+}
+void update_svclass_list(const bdaddr_t *src)
+{
+       GSList *adapters = manager_get_adapters();
+
+       for (; adapters; adapters = adapters->next) {
+               struct btd_adapter *adapter = adapters->data;
+               bdaddr_t bdaddr;
+
+               adapter_get_address(adapter, &bdaddr);
+
+               if (bacmp(src, BDADDR_ANY) == 0 || bacmp(src, &bdaddr) == 0)
+                       update_adapter_svclass_list(adapter);
+       }
+
+}
+#endif
+void register_public_browse_group(void)
+{
+       sdp_list_t *browselist;
+       uuid_t bgscid, pbgid;
+       sdp_data_t *sdpdata;
+       sdp_record_t *browse = sdp_record_alloc();
+
+       browse->handle = SDP_SERVER_RECORD_HANDLE + 1;
+
+       sdp_record_add(BDADDR_ANY, browse);
+       sdpdata = sdp_data_alloc(SDP_UINT32, &browse->handle);
+       sdp_attr_add(browse, SDP_ATTR_RECORD_HANDLE, sdpdata);
+
+       sdp_uuid16_create(&bgscid, BROWSE_GRP_DESC_SVCLASS_ID);
+       browselist = sdp_list_append(0, &bgscid);
+       sdp_set_service_classes(browse, browselist);
+       sdp_list_free(browselist, 0);
+
+       sdp_uuid16_create(&pbgid, PUBLIC_BROWSE_GROUP);
+       sdp_attr_add_new(browse, SDP_ATTR_GROUP_ID,
+                               SDP_UUID16, &pbgid.value.uuid16);
+}
+
+/*
+ * The SDP server must present its own service record to
+ * the service repository. This can be accessed by service
+ * discovery clients. This method constructs a service record
+ * and stores it in the repository
+ */
+void register_server_service(void)
+{
+       sdp_list_t *classIDList;
+       uuid_t classID;
+       void **versions, **versionDTDs;
+       uint8_t dtd;
+       sdp_data_t *pData;
+       int i;
+
+       server = sdp_record_alloc();
+       server->pattern = NULL;
+
+       /* Force the record to be SDP_SERVER_RECORD_HANDLE */
+       server->handle = SDP_SERVER_RECORD_HANDLE;
+
+       sdp_record_add(BDADDR_ANY, server);
+       sdp_attr_add(server, SDP_ATTR_RECORD_HANDLE,
+                               sdp_data_alloc(SDP_UINT32, &server->handle));
+
+       sdp_uuid16_create(&classID, SDP_SERVER_SVCLASS_ID);
+       classIDList = sdp_list_append(0, &classID);
+       sdp_set_service_classes(server, classIDList);
+       sdp_list_free(classIDList, 0);
+
+       /*
+        * Set the version numbers supported, these are passed as arguments
+        * to the server on command line. Now defaults to 1.0
+        * Build the version number sequence first
+        */
+       versions = malloc(sdpServerVnumEntries * sizeof(void *));
+       versionDTDs = malloc(sdpServerVnumEntries * sizeof(void *));
+       dtd = SDP_UINT16;
+       for (i = 0; i < sdpServerVnumEntries; i++) {
+               uint16_t *version = malloc(sizeof(uint16_t));
+               *version = sdpVnumArray[i].major;
+               *version = (*version << 8);
+               *version |= sdpVnumArray[i].minor;
+               versions[i] = version;
+               versionDTDs[i] = &dtd;
+       }
+       pData = sdp_seq_alloc(versionDTDs, versions, sdpServerVnumEntries);
+       for (i = 0; i < sdpServerVnumEntries; i++)
+               free(versions[i]);
+       free(versions);
+       free(versionDTDs);
+       sdp_attr_add(server, SDP_ATTR_VERSION_NUM_LIST, pData);
+
+       update_db_timestamp();
+}
+
+void register_device_id(const uint16_t vendor, const uint16_t product,
+                                               const uint16_t version)
+{
+       const uint16_t spec = 0x0102, source = 0x0002;
+       const uint8_t primary = 1;
+       sdp_list_t *class_list, *group_list, *profile_list;
+       uuid_t class_uuid, group_uuid;
+       sdp_data_t *sdp_data, *primary_data, *source_data;
+       sdp_data_t *spec_data, *vendor_data, *product_data, *version_data;
+       sdp_profile_desc_t profile;
+       sdp_record_t *record = sdp_record_alloc();
+
+       info("Adding device id record for %04x:%04x", vendor, product);
+
+       btd_manager_set_did(vendor, product, version);
+
+       record->handle = sdp_next_handle();
+
+       sdp_record_add(BDADDR_ANY, record);
+       sdp_data = sdp_data_alloc(SDP_UINT32, &record->handle);
+       sdp_attr_add(record, SDP_ATTR_RECORD_HANDLE, sdp_data);
+
+       sdp_uuid16_create(&class_uuid, PNP_INFO_SVCLASS_ID);
+       class_list = sdp_list_append(0, &class_uuid);
+       sdp_set_service_classes(record, class_list);
+       sdp_list_free(class_list, NULL);
+
+       sdp_uuid16_create(&group_uuid, PUBLIC_BROWSE_GROUP);
+       group_list = sdp_list_append(NULL, &group_uuid);
+       sdp_set_browse_groups(record, group_list);
+       sdp_list_free(group_list, NULL);
+
+       sdp_uuid16_create(&profile.uuid, PNP_INFO_PROFILE_ID);
+       profile.version = spec;
+       profile_list = sdp_list_append(NULL, &profile);
+       sdp_set_profile_descs(record, profile_list);
+       sdp_list_free(profile_list, NULL);
+
+       spec_data = sdp_data_alloc(SDP_UINT16, &spec);
+       sdp_attr_add(record, 0x0200, spec_data);
+
+       vendor_data = sdp_data_alloc(SDP_UINT16, &vendor);
+       sdp_attr_add(record, 0x0201, vendor_data);
+
+       product_data = sdp_data_alloc(SDP_UINT16, &product);
+       sdp_attr_add(record, 0x0202, product_data);
+
+       version_data = sdp_data_alloc(SDP_UINT16, &version);
+       sdp_attr_add(record, 0x0203, version_data);
+
+       primary_data = sdp_data_alloc(SDP_BOOL, &primary);
+       sdp_attr_add(record, 0x0204, primary_data);
+
+       source_data = sdp_data_alloc(SDP_UINT16, &source);
+       sdp_attr_add(record, 0x0205, source_data);
+
+       update_db_timestamp();
+}
+
+int add_record_to_server(const bdaddr_t *src, sdp_record_t *rec)
+{
+       sdp_data_t *data;
+       sdp_list_t *pattern;
+
+       if (rec->handle == 0xffffffff) {
+               rec->handle = sdp_next_handle();
+               if (rec->handle < 0x10000)
+                       return -ENOSPC;
+       } else {
+               if (sdp_record_find(rec->handle))
+                       return -EEXIST;
+       }
+
+       DBG("Adding record with handle 0x%05x", rec->handle);
+
+       sdp_record_add(src, rec);
+
+       data = sdp_data_alloc(SDP_UINT32, &rec->handle);
+       sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data);
+
+       if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) {
+               uuid_t uuid;
+               sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP);
+               sdp_pattern_add_uuid(rec, &uuid);
+       }
+
+       for (pattern = rec->pattern; pattern; pattern = pattern->next) {
+               char uuid[32];
+
+               if (pattern->data == NULL)
+                       continue;
+
+               sdp_uuid2strn((uuid_t *) pattern->data, uuid, sizeof(uuid));
+               DBG("Record pattern UUID %s", uuid);
+       }
+
+       update_db_timestamp();
+
+       return 0;
+}
+
+int remove_record_from_server(uint32_t handle)
+{
+       sdp_record_t *rec;
+
+       DBG("Removing record with handle 0x%05x", handle);
+
+       rec = sdp_record_find(handle);
+       if (!rec)
+               return -ENOENT;
+
+       if (sdp_record_remove(handle) == 0)
+               update_db_timestamp();
+
+       sdp_record_free(rec);
+
+       return 0;
+}
+
+/* FIXME: refactor for server-side */
+static sdp_record_t *extract_pdu_server(bdaddr_t *device, uint8_t *p,
+                                       unsigned int bufsize,
+                                       uint32_t handleExpected, int *scanned)
+{
+       int extractStatus = -1, localExtractedLength = 0;
+       uint8_t dtd;
+       int seqlen = 0;
+       sdp_record_t *rec = NULL;
+       uint16_t attrId, lookAheadAttrId;
+       sdp_data_t *pAttr = NULL;
+       uint32_t handle = 0xffffffff;
+
+       *scanned = sdp_extract_seqtype(p, bufsize, &dtd, &seqlen);
+       p += *scanned;
+       bufsize -= *scanned;
+
+       if (bufsize < sizeof(uint8_t) + sizeof(uint8_t)) {
+               SDPDBG("Unexpected end of packet");
+               return NULL;
+       }
+
+       lookAheadAttrId = ntohs(bt_get_unaligned((uint16_t *) (p + sizeof(uint8_t))));
+
+       SDPDBG("Look ahead attr id : %d", lookAheadAttrId);
+
+       if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) {
+               if (bufsize < (sizeof(uint8_t) * 2) +
+                                       sizeof(uint16_t) + sizeof(uint32_t)) {
+                       SDPDBG("Unexpected end of packet");
+                       return NULL;
+               }
+               handle = ntohl(bt_get_unaligned((uint32_t *) (p +
+                               sizeof(uint8_t) + sizeof(uint16_t) +
+                               sizeof(uint8_t))));
+               SDPDBG("SvcRecHandle : 0x%x", handle);
+               rec = sdp_record_find(handle);
+       } else if (handleExpected != 0xffffffff)
+               rec = sdp_record_find(handleExpected);
+
+       if (!rec) {
+               rec = sdp_record_alloc();
+               rec->attrlist = NULL;
+               if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) {
+                       rec->handle = handle;
+                       sdp_record_add(device, rec);
+               } else if (handleExpected != 0xffffffff) {
+                       rec->handle = handleExpected;
+                       sdp_record_add(device, rec);
+               }
+       } else {
+               sdp_list_free(rec->attrlist, (sdp_free_func_t) sdp_data_free);
+               rec->attrlist = NULL;
+       }
+
+       while (localExtractedLength < seqlen) {
+               int attrSize = sizeof(uint8_t);
+               int attrValueLength = 0;
+
+               if (bufsize < attrSize + sizeof(uint16_t)) {
+                       SDPDBG("Unexpected end of packet: Terminating extraction of attributes");
+                       break;
+               }
+
+               SDPDBG("Extract PDU, sequenceLength: %d localExtractedLength: %d",
+                                                       seqlen, localExtractedLength);
+               dtd = *(uint8_t *) p;
+
+               attrId = ntohs(bt_get_unaligned((uint16_t *) (p + attrSize)));
+               attrSize += sizeof(uint16_t);
+
+               SDPDBG("DTD of attrId : %d Attr id : 0x%x", dtd, attrId);
+
+               pAttr = sdp_extract_attr(p + attrSize, bufsize - attrSize,
+                                                       &attrValueLength, rec);
+
+               SDPDBG("Attr id : 0x%x attrValueLength : %d", attrId, attrValueLength);
+
+               attrSize += attrValueLength;
+               if (pAttr == NULL) {
+                       SDPDBG("Terminating extraction of attributes");
+                       break;
+               }
+               localExtractedLength += attrSize;
+               p += attrSize;
+               bufsize -= attrSize;
+               sdp_attr_replace(rec, attrId, pAttr);
+               extractStatus = 0;
+               SDPDBG("Extract PDU, seqLength: %d localExtractedLength: %d",
+                                       seqlen, localExtractedLength);
+       }
+
+       if (extractStatus == 0) {
+               SDPDBG("Successful extracting of Svc Rec attributes");
+#ifdef SDP_DEBUG
+               sdp_print_service_attr(rec->attrlist);
+#endif
+               *scanned += seqlen;
+       }
+       return rec;
+}
+
+/*
+ * Add the newly created service record to the service repository
+ */
+int service_register_req(sdp_req_t *req, sdp_buf_t *rsp)
+{
+       int scanned = 0;
+       sdp_data_t *handle;
+       uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t);
+       int bufsize = req->len - sizeof(sdp_pdu_hdr_t);
+       sdp_record_t *rec;
+
+       req->flags = *p++;
+       if (req->flags & SDP_DEVICE_RECORD) {
+               bacpy(&req->device, (bdaddr_t *) p);
+               p += sizeof(bdaddr_t);
+               bufsize -= sizeof(bdaddr_t);
+       }
+
+       // save image of PDU: we need it when clients request this attribute
+       rec = extract_pdu_server(&req->device, p, bufsize, 0xffffffff, &scanned);
+       if (!rec)
+               goto invalid;
+
+       if (rec->handle == 0xffffffff) {
+               rec->handle = sdp_next_handle();
+               if (rec->handle < 0x10000) {
+                       sdp_record_free(rec);
+                       goto invalid;
+               }
+       } else {
+               if (sdp_record_find(rec->handle)) {
+                       /* extract_pdu_server will add the record handle
+                        * if it is missing. So instead of failing, skip
+                        * the record adding to avoid duplication. */
+                       goto success;
+               }
+       }
+
+       sdp_record_add(&req->device, rec);
+       if (!(req->flags & SDP_RECORD_PERSIST))
+               sdp_svcdb_set_collectable(rec, req->sock);
+
+       handle = sdp_data_alloc(SDP_UINT32, &rec->handle);
+       sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, handle);
+
+success:
+       /* if the browse group descriptor is NULL,
+        * ensure that the record belongs to the ROOT group */
+       if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) {
+               uuid_t uuid;
+               sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP);
+               sdp_pattern_add_uuid(rec, &uuid);
+       }
+
+       update_db_timestamp();
+
+       /* Build a rsp buffer */
+       bt_put_unaligned(htonl(rec->handle), (uint32_t *) rsp->data);
+       rsp->data_size = sizeof(uint32_t);
+
+       return 0;
+
+invalid:
+       bt_put_unaligned(htons(SDP_INVALID_SYNTAX), (uint16_t *) rsp->data);
+       rsp->data_size = sizeof(uint16_t);
+
+       return -1;
+}
+
+/*
+ * Update a service record
+ */
+int service_update_req(sdp_req_t *req, sdp_buf_t *rsp)
+{
+       sdp_record_t *orec, *nrec;
+       int status = 0, scanned = 0;
+       uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t);
+       int bufsize = req->len - sizeof(sdp_pdu_hdr_t);
+       uint32_t handle = ntohl(bt_get_unaligned((uint32_t *) p));
+
+       SDPDBG("Svc Rec Handle: 0x%x", handle);
+
+       p += sizeof(uint32_t);
+       bufsize -= sizeof(uint32_t);
+
+       orec = sdp_record_find(handle);
+
+       SDPDBG("SvcRecOld: %p", orec);
+
+       if (!orec) {
+               status = SDP_INVALID_RECORD_HANDLE;
+               goto done;
+       }
+
+       nrec = extract_pdu_server(BDADDR_ANY, p, bufsize, handle, &scanned);
+       if (!nrec) {
+               status = SDP_INVALID_SYNTAX;
+               goto done;
+       }
+
+       assert(nrec == orec);
+
+       update_db_timestamp();
+
+done:
+       p = rsp->data;
+       bt_put_unaligned(htons(status), (uint16_t *) p);
+       rsp->data_size = sizeof(uint16_t);
+       return status;
+}
+
+/*
+ * Remove a registered service record
+ */
+int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp)
+{
+       uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t);
+       uint32_t handle = ntohl(bt_get_unaligned((uint32_t *) p));
+       sdp_record_t *rec;
+       int status = 0;
+
+       /* extract service record handle */
+       p += sizeof(uint32_t);
+
+       rec = sdp_record_find(handle);
+       if (rec) {
+               sdp_svcdb_collect(rec);
+               status = sdp_record_remove(handle);
+               sdp_record_free(rec);
+               if (status == 0)
+                       update_db_timestamp();
+       } else {
+               status = SDP_INVALID_RECORD_HANDLE;
+               SDPDBG("Could not find record : 0x%x", handle);
+       }
+
+       p = rsp->data;
+       bt_put_unaligned(htons(status), (uint16_t *) p);
+       rsp->data_size = sizeof(uint16_t);
+
+       return status;
+}
diff --git a/src/sdpd.h b/src/sdpd.h
new file mode 100644 (file)
index 0000000..48efab6
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ *  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 <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#ifdef SDP_DEBUG
+#include <syslog.h>
+#define SDPDBG(fmt, arg...) syslog(LOG_DEBUG, "%s: " fmt "\n", __func__ , ## arg)
+#else
+#define SDPDBG(fmt...)
+#endif
+
+#define EIR_DATA_LENGTH  240
+
+#define EIR_FLAGS                   0x01  /* flags */
+#define EIR_UUID16_SOME             0x02  /* 16-bit UUID, more available */
+#define EIR_UUID16_ALL              0x03  /* 16-bit UUID, all listed */
+#define EIR_UUID32_SOME             0x04  /* 32-bit UUID, more available */
+#define EIR_UUID32_ALL              0x05  /* 32-bit UUID, all listed */
+#define EIR_UUID128_SOME            0x06  /* 128-bit UUID, more available */
+#define EIR_UUID128_ALL             0x07  /* 128-bit UUID, all listed */
+#define EIR_NAME_SHORT              0x08  /* shortened local name */
+#define EIR_NAME_COMPLETE           0x09  /* complete local name */
+#define EIR_TX_POWER                0x0A  /* transmit power level */
+#define EIR_DEVICE_ID               0x10  /* device ID */
+
+typedef struct request {
+       bdaddr_t device;
+       bdaddr_t bdaddr;
+       int      local;
+       int      sock;
+       int      mtu;
+       int      flags;
+       uint8_t  *buf;
+       int      len;
+} sdp_req_t;
+
+void handle_request(int sk, uint8_t *data, int len);
+
+int service_register_req(sdp_req_t *req, sdp_buf_t *rsp);
+int service_update_req(sdp_req_t *req, sdp_buf_t *rsp);
+int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp);
+
+void register_public_browse_group(void);
+void register_server_service(void);
+void register_device_id(const uint16_t vendor, const uint16_t product,
+                                               const uint16_t version);
+
+int record_sort(const void *r1, const void *r2);
+void sdp_svcdb_reset(void);
+void sdp_svcdb_collect_all(int sock);
+void sdp_svcdb_set_collectable(sdp_record_t *rec, int sock);
+void sdp_svcdb_collect(sdp_record_t *rec);
+sdp_record_t *sdp_record_find(uint32_t handle);
+void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec);
+int sdp_record_remove(uint32_t handle);
+sdp_list_t *sdp_get_record_list(void);
+sdp_list_t *sdp_get_access_list(void);
+int sdp_check_access(uint32_t handle, bdaddr_t *device);
+uint32_t sdp_next_handle(void);
+
+uint32_t sdp_get_time();
+
+#define SDP_SERVER_COMPAT (1 << 0)
+#define SDP_SERVER_MASTER (1 << 1)
+
+int start_sdp_server(uint16_t mtu, const char *did, uint32_t flags);
+void stop_sdp_server(void);
+
+int add_record_to_server(const bdaddr_t *src, sdp_record_t *rec);
+int remove_record_from_server(uint32_t handle);
+
+void sdp_init_services_list(bdaddr_t *device);
+#ifdef __TIZEN_PATCH__
+sdp_record_t *sdp_extract_pdu_safe(const uint8_t *buf, int bufsize, int *scanned);
+#endif
+
diff --git a/src/storage.c b/src/storage.c
new file mode 100644 (file)
index 0000000..d7862e2
--- /dev/null
@@ -0,0 +1,1496 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "textfile.h"
+#include "adapter.h"
+#include "device.h"
+#include "glib-helper.h"
+#include "storage.h"
+
+struct match {
+       GSList *keys;
+       char *pattern;
+};
+
+static inline int create_filename(char *buf, size_t size,
+                               const bdaddr_t *bdaddr, const char *name)
+{
+       char addr[18];
+
+       ba2str(bdaddr, addr);
+
+       return create_name(buf, size, STORAGEDIR, addr, name);
+}
+
+int read_device_alias(const char *src, const char *dst, char *alias, size_t size)
+{
+       char filename[PATH_MAX + 1], *tmp;
+       int err;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "aliases");
+
+       tmp = textfile_get(filename, dst);
+       if (!tmp)
+               return -ENXIO;
+
+       err = snprintf(alias, size, "%s", tmp);
+
+       free(tmp);
+
+       return err < 0 ? -EIO : 0;
+}
+
+int write_device_alias(const char *src, const char *dst, const char *alias)
+{
+       char filename[PATH_MAX + 1];
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "aliases");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_put(filename, dst, alias);
+}
+
+int write_discoverable_timeout(bdaddr_t *bdaddr, int timeout)
+{
+       char filename[PATH_MAX + 1], str[32];
+
+       snprintf(str, sizeof(str), "%d", timeout);
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_put(filename, "discovto", str);
+}
+
+int read_discoverable_timeout(const char *src, int *timeout)
+{
+       char filename[PATH_MAX + 1], *str;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "config");
+
+       str = textfile_get(filename, "discovto");
+       if (!str)
+               return -ENOENT;
+
+       if (sscanf(str, "%d", timeout) != 1) {
+               free(str);
+               return -ENOENT;
+       }
+
+       free(str);
+
+       return 0;
+}
+
+int write_pairable_timeout(bdaddr_t *bdaddr, int timeout)
+{
+       char filename[PATH_MAX + 1], str[32];
+
+       snprintf(str, sizeof(str), "%d", timeout);
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_put(filename, "pairto", str);
+}
+
+int read_pairable_timeout(const char *src, int *timeout)
+{
+       char filename[PATH_MAX + 1], *str;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "config");
+
+       str = textfile_get(filename, "pairto");
+       if (!str)
+               return -ENOENT;
+
+       if (sscanf(str, "%d", timeout) != 1) {
+               free(str);
+               return -ENOENT;
+       }
+
+       free(str);
+
+       return 0;
+}
+
+int write_device_mode(bdaddr_t *bdaddr, const char *mode)
+{
+       char filename[PATH_MAX + 1];
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       if (strcmp(mode, "off") != 0)
+               textfile_put(filename, "onmode", mode);
+
+       return textfile_put(filename, "mode", mode);
+}
+
+int read_device_mode(const char *src, char *mode, int length)
+{
+       char filename[PATH_MAX + 1], *str;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "config");
+
+       str = textfile_get(filename, "mode");
+       if (!str)
+               return -ENOENT;
+
+       strncpy(mode, str, length);
+       mode[length - 1] = '\0';
+
+       free(str);
+
+       return 0;
+}
+
+int read_on_mode(const char *src, char *mode, int length)
+{
+       char filename[PATH_MAX + 1], *str;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "config");
+
+       str = textfile_get(filename, "onmode");
+       if (!str)
+               return -ENOENT;
+
+       strncpy(mode, str, length);
+       mode[length - 1] = '\0';
+
+       free(str);
+
+       return 0;
+}
+
+int write_local_name(bdaddr_t *bdaddr, const char *name)
+{
+       char filename[PATH_MAX + 1], str[249];
+       int i;
+
+       memset(str, 0, sizeof(str));
+       for (i = 0; i < 248 && name[i]; i++)
+               if ((unsigned char) name[i] < 32 || name[i] == 127)
+                       str[i] = '.';
+               else
+                       str[i] = name[i];
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_put(filename, "name", str);
+}
+
+int read_local_name(bdaddr_t *bdaddr, char *name)
+{
+       char filename[PATH_MAX + 1], *str;
+       int len;
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       str = textfile_get(filename, "name");
+       if (!str)
+               return -ENOENT;
+
+       len = strlen(str);
+       if (len > 248)
+               str[248] = '\0';
+       strcpy(name, str);
+
+       free(str);
+
+       return 0;
+}
+
+int write_local_class(bdaddr_t *bdaddr, uint8_t *class)
+{
+       char filename[PATH_MAX + 1], str[9];
+
+       sprintf(str, "0x%2.2x%2.2x%2.2x", class[2], class[1], class[0]);
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_put(filename, "class", str);
+}
+
+int read_local_class(bdaddr_t *bdaddr, uint8_t *class)
+{
+       char filename[PATH_MAX + 1], tmp[3], *str;
+       int i;
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       str = textfile_get(filename, "class");
+       if (!str)
+               return -ENOENT;
+
+       memset(tmp, 0, sizeof(tmp));
+       for (i = 0; i < 3; i++) {
+               memcpy(tmp, str + (i * 2) + 2, 2);
+               class[2 - i] = (uint8_t) strtol(tmp, NULL, 16);
+       }
+
+       free(str);
+
+       return 0;
+}
+
+int write_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class)
+{
+       char filename[PATH_MAX + 1], addr[18], str[9];
+
+       create_filename(filename, PATH_MAX, local, "classes");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(peer, addr);
+       sprintf(str, "0x%6.6x", class);
+
+       return textfile_put(filename, addr, str);
+}
+
+int read_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t *class)
+{
+       char filename[PATH_MAX + 1], addr[18], *str;
+
+       create_filename(filename, PATH_MAX, local, "classes");
+
+       ba2str(peer, addr);
+
+       str = textfile_get(filename, addr);
+       if (!str)
+               return -ENOENT;
+
+       if (sscanf(str, "%x", class) != 1) {
+               free(str);
+               return -ENOENT;
+       }
+
+       free(str);
+
+       return 0;
+}
+
+int write_device_name(bdaddr_t *local, bdaddr_t *peer, char *name)
+{
+       char filename[PATH_MAX + 1], addr[18], str[249];
+       int i;
+
+       memset(str, 0, sizeof(str));
+       for (i = 0; i < 248 && name[i]; i++)
+               if ((unsigned char) name[i] < 32 || name[i] == 127)
+                       str[i] = '.';
+               else
+                       str[i] = name[i];
+
+       create_filename(filename, PATH_MAX, local, "names");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(peer, addr);
+       return textfile_put(filename, addr, str);
+}
+
+int read_device_name(const char *src, const char *dst, char *name)
+{
+       char filename[PATH_MAX + 1], *str;
+       int len;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "names");
+
+       str = textfile_get(filename, dst);
+       if (!str)
+               return -ENOENT;
+
+       len = strlen(str);
+       if (len > 248)
+               str[248] = '\0';
+       strcpy(name, str);
+
+       free(str);
+
+       return 0;
+}
+
+int write_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data)
+{
+       char filename[PATH_MAX + 1], addr[18], str[481];
+       int i;
+
+       memset(str, 0, sizeof(str));
+       for (i = 0; i < 240; i++)
+               sprintf(str + (i * 2), "%2.2X", data[i]);
+
+       create_filename(filename, PATH_MAX, local, "eir");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(peer, addr);
+       return textfile_put(filename, addr, str);
+}
+
+int read_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data)
+{
+       char filename[PATH_MAX + 1], addr[18], *str;
+       int i;
+
+       create_filename(filename, PATH_MAX, local, "eir");
+
+       ba2str(peer, addr);
+
+       str = textfile_get(filename, addr);
+       if (!str)
+               return -ENOENT;
+
+       if (!data) {
+               free(str);
+               return 0;
+       }
+
+       if (strlen(str) < 480) {
+               free(str);
+               return -EIO;
+       }
+
+       for (i = 0; i < 240; i++)
+               sscanf(str + (i * 2), "%02hhX", &data[i]);
+
+       free(str);
+
+       return 0;
+}
+
+int write_version_info(bdaddr_t *local, bdaddr_t *peer, uint16_t manufacturer,
+                                       uint8_t lmp_ver, uint16_t lmp_subver)
+{
+       char filename[PATH_MAX + 1], addr[18], str[16];
+
+       memset(str, 0, sizeof(str));
+       sprintf(str, "%d %d %d", manufacturer, lmp_ver, lmp_subver);
+
+       create_filename(filename, PATH_MAX, local, "manufacturers");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(peer, addr);
+       return textfile_put(filename, addr, str);
+}
+
+int write_features_info(bdaddr_t *local, bdaddr_t *peer,
+                               unsigned char *page1, unsigned char *page2)
+{
+       char filename[PATH_MAX + 1], addr[18];
+       char str[] = "0000000000000000 0000000000000000";
+       char *old_value;
+       int i;
+
+       ba2str(peer, addr);
+
+       create_filename(filename, PATH_MAX, local, "features");
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       old_value = textfile_get(filename, addr);
+
+       if (page1)
+               for (i = 0; i < 8; i++)
+                       sprintf(str + (i * 2), "%2.2X", page1[i]);
+       else if (old_value && strlen(old_value) >= 16)
+               strncpy(str, old_value, 16);
+
+       if (page2)
+               for (i = 0; i < 8; i++)
+                       sprintf(str + 17 + (i * 2), "%2.2X", page2[i]);
+       else if (old_value && strlen(old_value) >= 33)
+               strncpy(str + 17, old_value + 17, 16);
+
+       free(old_value);
+
+       return textfile_put(filename, addr, str);
+}
+
+static int decode_bytes(const char *str, unsigned char *bytes, size_t len)
+{
+       unsigned int i;
+
+       for (i = 0; i < len; i++) {
+               if (sscanf(str + (i * 2), "%02hhX", &bytes[i]) != 1)
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+int read_remote_features(bdaddr_t *local, bdaddr_t *peer,
+                               unsigned char *page1, unsigned char *page2)
+{
+       char filename[PATH_MAX + 1], addr[18], *str;
+       size_t len;
+       int err;
+
+       if (page1 == NULL && page2 == NULL)
+               return -EINVAL;
+
+       create_filename(filename, PATH_MAX, local, "features");
+
+       ba2str(peer, addr);
+
+       str = textfile_get(filename, addr);
+       if (!str)
+               return -ENOENT;
+
+       len = strlen(str);
+
+       err = -ENOENT;
+
+       if (page1 && len >= 16)
+               err = decode_bytes(str, page1, 8);
+
+       if (page2 && len >= 33)
+               err = decode_bytes(str + 17, page2, 8);
+
+       free(str);
+
+       return err;
+}
+
+int write_lastseen_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm)
+{
+       char filename[PATH_MAX + 1], addr[18], str[24];
+
+       memset(str, 0, sizeof(str));
+       strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", tm);
+
+       create_filename(filename, PATH_MAX, local, "lastseen");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(peer, addr);
+       return textfile_put(filename, addr, str);
+}
+
+int write_lastused_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm)
+{
+       char filename[PATH_MAX + 1], addr[18], str[24];
+
+       memset(str, 0, sizeof(str));
+       strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", tm);
+
+       create_filename(filename, PATH_MAX, local, "lastused");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(peer, addr);
+       return textfile_put(filename, addr, str);
+}
+
+int write_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t type, int length)
+{
+       char filename[PATH_MAX + 1], addr[18], str[38];
+       int i;
+
+       memset(str, 0, sizeof(str));
+       for (i = 0; i < 16; i++)
+               sprintf(str + (i * 2), "%2.2X", key[i]);
+       sprintf(str + 32, " %d %d", type, length);
+
+       create_filename(filename, PATH_MAX, local, "linkkeys");
+
+       create_file(filename, S_IRUSR | S_IWUSR);
+
+       ba2str(peer, addr);
+
+       if (length < 0) {
+               char *tmp = textfile_get(filename, addr);
+               if (tmp) {
+                       if (strlen(tmp) > 34)
+                               memcpy(str + 34, tmp + 34, 3);
+                       free(tmp);
+               }
+       }
+
+       return textfile_put(filename, addr, str);
+}
+
+int read_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t *type)
+{
+       char filename[PATH_MAX + 1], addr[18], tmp[3], *str;
+       int i;
+
+       create_filename(filename, PATH_MAX, local, "linkkeys");
+
+       ba2str(peer, addr);
+       str = textfile_get(filename, addr);
+       if (!str)
+               return -ENOENT;
+
+       if (!key) {
+               free(str);
+               return 0;
+       }
+
+       memset(tmp, 0, sizeof(tmp));
+       for (i = 0; i < 16; i++) {
+               memcpy(tmp, str + (i * 2), 2);
+               key[i] = (uint8_t) strtol(tmp, NULL, 16);
+       }
+
+       if (type) {
+               memcpy(tmp, str + 33, 2);
+               *type = (uint8_t) strtol(tmp, NULL, 10);
+       }
+
+       free(str);
+
+       return 0;
+}
+#ifdef __TIZEN_PATCH__
+int read_pin_length(bdaddr_t *local, bdaddr_t *peer)
+{
+       char filename[PATH_MAX + 1], addr[18], *str;
+       int len;
+
+       create_filename(filename, PATH_MAX, local, "linkkeys");
+
+       ba2str(peer, addr);
+       str = textfile_get(filename, addr);
+       if (!str)
+               return -ENOENT;
+
+       if (strlen(str) < 36) {
+               free(str);
+               return -ENOENT;
+       }
+
+       len = atoi(str + 35);
+
+       free(str);
+
+       return len;
+}
+#endif
+int read_pin_code(bdaddr_t *local, bdaddr_t *peer, char *pin)
+{
+       char filename[PATH_MAX + 1], addr[18], *str;
+       int len;
+
+       create_filename(filename, PATH_MAX, local, "pincodes");
+
+       ba2str(peer, addr);
+       str = textfile_get(filename, addr);
+       if (!str)
+               return -ENOENT;
+
+       strncpy(pin, str, 16);
+       len = strlen(pin);
+
+       free(str);
+
+       return len;
+}
+
+static GSList *service_string_to_list(char *services)
+{
+       GSList *l = NULL;
+       char *start = services;
+       int i, finished = 0;
+
+       for (i = 0; !finished; i++) {
+               if (services[i] == '\0')
+                       finished = 1;
+
+               if (services[i] == ' ' || services[i] == '\0') {
+                       services[i] = '\0';
+                       l = g_slist_append(l, start);
+                       start = services + i + 1;
+               }
+       }
+
+       return l;
+}
+
+static char *service_list_to_string(GSList *services)
+{
+       char str[1024];
+       int len = 0;
+
+       if (!services)
+               return g_strdup("");
+
+       memset(str, 0, sizeof(str));
+
+       while (services) {
+               int ret;
+               char *ident = services->data;
+
+               ret = snprintf(str + len, sizeof(str) - len - 1, "%s%s",
+                               ident, services->next ? " " : "");
+
+               if (ret > 0)
+                       len += ret;
+
+               services = services->next;
+       }
+
+       return g_strdup(str);
+}
+
+int write_trust(const char *src, const char *addr, const char *service,
+               gboolean trust)
+{
+       char filename[PATH_MAX + 1], *str;
+       GSList *services = NULL, *match;
+       gboolean trusted;
+       int ret;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "trusts");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       str = textfile_caseget(filename, addr);
+       if (str)
+               services = service_string_to_list(str);
+
+       match = g_slist_find_custom(services, service, (GCompareFunc) strcmp);
+       trusted = match ? TRUE : FALSE;
+
+       /* If the old setting is the same as the requested one, we're done */
+       if (trusted == trust) {
+               g_slist_free(services);
+               free(str);
+               return 0;
+       }
+
+       if (trust)
+               services = g_slist_append(services, (void *) service);
+       else
+               services = g_slist_remove(services, match->data);
+
+       /* Remove the entry if the last trusted service was removed */
+       if (!trust && !services)
+               ret = textfile_casedel(filename, addr);
+       else {
+               char *new_str = service_list_to_string(services);
+               ret = textfile_caseput(filename, addr, new_str);
+               free(new_str);
+       }
+
+       g_slist_free(services);
+
+       free(str);
+
+       return ret;
+}
+
+gboolean read_trust(const bdaddr_t *local, const char *addr, const char *service)
+{
+       char filename[PATH_MAX + 1], *str;
+       GSList *services;
+       gboolean ret;
+
+       create_filename(filename, PATH_MAX, local, "trusts");
+
+       str = textfile_caseget(filename, addr);
+       if (!str)
+               return FALSE;
+
+       services = service_string_to_list(str);
+
+       if (g_slist_find_custom(services, service, (GCompareFunc) strcmp))
+               ret = TRUE;
+       else
+               ret = FALSE;
+
+       g_slist_free(services);
+       free(str);
+
+       return ret;
+}
+
+struct trust_list {
+       GSList *trusts;
+       const char *service;
+};
+
+static void append_trust(char *key, char *value, void *data)
+{
+       struct trust_list *list = data;
+
+       if (strstr(value, list->service))
+               list->trusts = g_slist_append(list->trusts, g_strdup(key));
+}
+
+GSList *list_trusts(bdaddr_t *local, const char *service)
+{
+       char filename[PATH_MAX + 1];
+       struct trust_list list;
+
+       create_filename(filename, PATH_MAX, local, "trusts");
+
+       list.trusts = NULL;
+       list.service = service;
+
+       if (textfile_foreach(filename, append_trust, &list) < 0)
+               return NULL;
+
+       return list.trusts;
+}
+
+int write_device_profiles(bdaddr_t *src, bdaddr_t *dst, const char *profiles)
+{
+       char filename[PATH_MAX + 1], addr[18];
+
+       if (!profiles)
+               return -EINVAL;
+
+       create_filename(filename, PATH_MAX, src, "profiles");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dst, addr);
+       return textfile_put(filename, addr, profiles);
+}
+
+int delete_entry(bdaddr_t *src, const char *storage, const char *key)
+{
+       char filename[PATH_MAX + 1];
+
+       create_filename(filename, PATH_MAX, src, storage);
+
+       return textfile_del(filename, key);
+}
+
+int store_record(const gchar *src, const gchar *dst, sdp_record_t *rec)
+{
+       char filename[PATH_MAX + 1], key[28];
+       sdp_buf_t buf;
+       int err, size, i;
+       char *str;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       snprintf(key, sizeof(key), "%17s#%08X", dst, rec->handle);
+
+       if (sdp_gen_record_pdu(rec, &buf) < 0)
+               return -1;
+
+       size = buf.data_size;
+
+       str = g_malloc0(size*2+1);
+
+       for (i = 0; i < size; i++)
+               sprintf(str + (i * 2), "%02X", buf.data[i]);
+
+       err = textfile_put(filename, key, str);
+
+       free(buf.data);
+       free(str);
+
+       return err;
+}
+
+sdp_record_t *record_from_string(const gchar *str)
+{
+       sdp_record_t *rec;
+       int size, i, len;
+       uint8_t *pdata;
+       char tmp[3];
+
+       size = strlen(str)/2;
+       pdata = g_malloc0(size);
+
+       tmp[2] = 0;
+       for (i = 0; i < size; i++) {
+               memcpy(tmp, str + (i * 2), 2);
+               pdata[i] = (uint8_t) strtol(tmp, NULL, 16);
+       }
+
+       rec = sdp_extract_pdu(pdata, size, &len);
+       free(pdata);
+
+       return rec;
+}
+
+
+sdp_record_t *fetch_record(const gchar *src, const gchar *dst,
+                                               const uint32_t handle)
+{
+       char filename[PATH_MAX + 1], key[28], *str;
+       sdp_record_t *rec;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp");
+
+       snprintf(key, sizeof(key), "%17s#%08X", dst, handle);
+
+       str = textfile_get(filename, key);
+       if (!str)
+               return NULL;
+
+       rec = record_from_string(str);
+       free(str);
+
+       return rec;
+}
+
+int delete_record(const gchar *src, const gchar *dst, const uint32_t handle)
+{
+       char filename[PATH_MAX + 1], key[28];
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp");
+
+       snprintf(key, sizeof(key), "%17s#%08X", dst, handle);
+
+       return textfile_del(filename, key);
+}
+
+struct record_list {
+       sdp_list_t *recs;
+       const gchar *addr;
+};
+
+static void create_stored_records_from_keys(char *key, char *value,
+                                                       void *user_data)
+{
+       struct record_list *rec_list = user_data;
+       const gchar *addr = rec_list->addr;
+       sdp_record_t *rec;
+
+       if (strncmp(key, addr, 17))
+               return;
+
+       rec = record_from_string(value);
+
+       rec_list->recs = sdp_list_append(rec_list->recs, rec);
+}
+
+void delete_all_records(const bdaddr_t *src, const bdaddr_t *dst)
+{
+       sdp_list_t *records, *seq;
+       char srcaddr[18], dstaddr[18];
+
+       ba2str(src, srcaddr);
+       ba2str(dst, dstaddr);
+
+       records = read_records(src, dst);
+
+       for (seq = records; seq; seq = seq->next) {
+               sdp_record_t *rec = seq->data;
+               delete_record(srcaddr, dstaddr, rec->handle);
+       }
+
+       if (records)
+               sdp_list_free(records, (sdp_free_func_t) sdp_record_free);
+}
+
+sdp_list_t *read_records(const bdaddr_t *src, const bdaddr_t *dst)
+{
+       char filename[PATH_MAX + 1];
+       struct record_list rec_list;
+       char srcaddr[18], dstaddr[18];
+
+       ba2str(src, srcaddr);
+       ba2str(dst, dstaddr);
+
+       rec_list.addr = dstaddr;
+       rec_list.recs = NULL;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "sdp");
+       textfile_foreach(filename, create_stored_records_from_keys, &rec_list);
+
+       return rec_list.recs;
+}
+
+sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid)
+{
+       sdp_list_t *seq;
+
+       for (seq = recs; seq; seq = seq->next) {
+               sdp_record_t *rec = (sdp_record_t *) seq->data;
+               sdp_list_t *svcclass = NULL;
+               char *uuid_str;
+
+               if (sdp_get_service_classes(rec, &svcclass) < 0)
+                       continue;
+
+               /* Extract the uuid */
+               uuid_str = bt_uuid2string(svcclass->data);
+               if (!uuid_str)
+                       continue;
+
+               if (!strcasecmp(uuid_str, uuid)) {
+                       sdp_list_free(svcclass, free);
+                       free(uuid_str);
+                       return rec;
+               }
+
+               sdp_list_free(svcclass, free);
+               free(uuid_str);
+       }
+       return NULL;
+}
+
+int store_device_id(const gchar *src, const gchar *dst,
+                               const uint16_t source, const uint16_t vendor,
+                               const uint16_t product, const uint16_t version)
+{
+       char filename[PATH_MAX + 1], str[20];
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "did");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       snprintf(str, sizeof(str), "%04X %04X %04X %04X", source,
+                                               vendor, product, version);
+
+       return textfile_put(filename, dst, str);
+}
+
+static int read_device_id_from_did(const gchar *src, const gchar *dst,
+                                       uint16_t *source, uint16_t *vendor,
+                                       uint16_t *product, uint16_t *version)
+{
+       char filename[PATH_MAX + 1];
+       char *str, *vendor_str, *product_str, *version_str;
+
+       create_name(filename, PATH_MAX, STORAGEDIR, src, "did");
+
+       str = textfile_get(filename, dst);
+       if (!str)
+               return -ENOENT;
+
+       vendor_str = strchr(str, ' ');
+       if (!vendor_str) {
+               free(str);
+               return -ENOENT;
+       }
+       *(vendor_str++) = 0;
+
+       product_str = strchr(vendor_str, ' ');
+       if (!product_str) {
+               free(str);
+               return -ENOENT;
+       }
+       *(product_str++) = 0;
+
+       version_str = strchr(product_str, ' ');
+       if (!version_str) {
+               free(str);
+               return -ENOENT;
+       }
+       *(version_str++) = 0;
+
+       if (source)
+               *source = (uint16_t) strtol(str, NULL, 16);
+       if (vendor)
+               *vendor = (uint16_t) strtol(vendor_str, NULL, 16);
+       if (product)
+               *product = (uint16_t) strtol(product_str, NULL, 16);
+       if (version)
+               *version = (uint16_t) strtol(version_str, NULL, 16);
+
+       free(str);
+
+       return 0;
+}
+
+int read_device_id(const gchar *srcaddr, const gchar *dstaddr,
+                                       uint16_t *source, uint16_t *vendor,
+                                       uint16_t *product, uint16_t *version)
+{
+       uint16_t lsource, lvendor, lproduct, lversion;
+       sdp_list_t *recs;
+       sdp_record_t *rec;
+       bdaddr_t src, dst;
+       int err;
+
+       err = read_device_id_from_did(srcaddr, dstaddr, &lsource,
+                                               vendor, product, version);
+       if (!err) {
+               if (lsource == 0xffff)
+                       err = -ENOENT;
+
+               return err;
+       }
+
+       str2ba(srcaddr, &src);
+       str2ba(dstaddr, &dst);
+
+       recs = read_records(&src, &dst);
+       rec = find_record_in_list(recs, PNP_UUID);
+
+       if (rec) {
+               sdp_data_t *pdlist;
+
+               pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE);
+               lsource = pdlist ? pdlist->val.uint16 : 0x0000;
+
+               pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID);
+               lvendor = pdlist ? pdlist->val.uint16 : 0x0000;
+
+               pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID);
+               lproduct = pdlist ? pdlist->val.uint16 : 0x0000;
+
+               pdlist = sdp_data_get(rec, SDP_ATTR_VERSION);
+               lversion = pdlist ? pdlist->val.uint16 : 0x0000;
+
+               err = 0;
+       }
+
+       sdp_list_free(recs, (sdp_free_func_t)sdp_record_free);
+
+       if (err) {
+               /* FIXME: We should try EIR data if we have it, too */
+
+               /* If we don't have the data, we don't want to go through the
+                * above search every time. */
+               lsource = 0xffff;
+               lvendor = 0x0000;
+               lproduct = 0x0000;
+               lversion = 0x0000;
+       }
+
+       store_device_id(srcaddr, dstaddr, lsource, lvendor, lproduct, lversion);
+
+       if (err)
+               return err;
+
+       if (source)
+               *source = lsource;
+       if (vendor)
+               *vendor = lvendor;
+       if (product)
+               *product = lproduct;
+       if (version)
+               *version = lversion;
+
+       return 0;
+}
+
+int write_device_pairable(bdaddr_t *bdaddr, gboolean mode)
+{
+       char filename[PATH_MAX + 1];
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_put(filename, "pairable", mode ? "yes" : "no");
+}
+
+int read_device_pairable(bdaddr_t *bdaddr, gboolean *mode)
+{
+       char filename[PATH_MAX + 1], *str;
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       str = textfile_get(filename, "pairable");
+       if (!str)
+               return -ENOENT;
+
+       *mode = strcmp(str, "yes") == 0 ? TRUE : FALSE;
+
+       free(str);
+
+       return 0;
+}
+#ifdef __TIZEN_PATCH__
+// Storing limited property
+int write_device_limited(bdaddr_t *bdaddr, gboolean mode)
+{
+       char filename[PATH_MAX + 1];
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_put(filename, "limited", mode ? "yes" : "no");
+}
+
+int read_device_limited(bdaddr_t *bdaddr, gboolean *mode)
+{
+       char filename[PATH_MAX + 1], *str;
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       str = textfile_get(filename, "limited");
+       if (!str)
+               return -ENOENT;
+
+       *mode = strcmp(str, "yes") == 0 ? TRUE : FALSE;
+
+       free(str);
+
+       return 0;
+}
+
+int read_version_info(const bdaddr_t *local, const char *addr,
+                                       uint16_t *manufacturer, uint8_t *lmp_ver, uint16_t *lmp_subver,
+                                       uint8_t *features)
+{
+       char filename[PATH_MAX + 1], *str, feature_value[5];
+       int i;
+
+       create_filename(filename, PATH_MAX, local, "manufacturers");
+
+       str = textfile_caseget(filename, addr);
+
+       if (!str)
+               return -ENOENT;
+
+       if (sscanf(str, "%d %d %d", manufacturer, lmp_ver, lmp_subver) != 3) {
+               free(str);
+               return -ENOENT;
+       }
+
+       free(str);
+
+       create_filename(filename, PATH_MAX, local, "features");
+
+       str = textfile_caseget(filename, addr);
+
+       if (!str)
+               return -ENOENT;
+
+       if (strlen(str) != 16)
+       {
+               free(str);
+               return -ENOENT;
+       }
+
+       memset(feature_value, 0x00, sizeof(feature_value));
+
+       for (i = 0; i < 8; i++)
+       {
+               strncpy(feature_value, str + (i * 2), 4);
+               if (sscanf(feature_value, "%x", &features[i]) != 1) {
+                       free(str);
+                       return -ENOENT;
+               }
+       }
+
+       free(str);
+
+       return 0;
+}
+int read_aptx_preference(bdaddr_t *bdaddr, gboolean *aptx_preference)
+{
+       char filename[PATH_MAX + 1], *str;
+       int len;
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       str = textfile_get(filename, "Codec_preference");
+       if (!str)
+               return -ENOENT;
+
+       len = strlen(str);
+       if (len > 248)
+               str[248] = '\0';
+
+       if(!aptx_preference)
+               return -EINVAL;
+
+       if(!strncmp(str,"aptx",4))
+               *aptx_preference=TRUE;
+       else
+               *aptx_preference=FALSE;
+
+       free(str);
+
+       return 0;
+}
+
+int write_aptx_preference(bdaddr_t *bdaddr, gboolean aptx_preference)
+{
+       char filename[PATH_MAX + 1], str[249];
+       int i;
+
+       memset(str, 0, sizeof(str));
+       if(aptx_preference)
+               memcpy(str,"aptx",5);
+       else
+               memcpy(str,"sbc",4);
+
+       create_filename(filename, PATH_MAX, bdaddr, "config");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_put(filename, "Codec_preference", str);
+}
+
+#endif
+gboolean read_blocked(const bdaddr_t *local, const bdaddr_t *remote)
+{
+       char filename[PATH_MAX + 1], *str, addr[18];
+
+       create_filename(filename, PATH_MAX, local, "blocked");
+
+       ba2str(remote, addr);
+
+       str = textfile_caseget(filename, addr);
+       if (!str)
+               return FALSE;
+
+       free(str);
+
+       return TRUE;
+}
+
+int write_blocked(const bdaddr_t *local, const bdaddr_t *remote,
+                                                       gboolean blocked)
+{
+       char filename[PATH_MAX + 1], addr[18];
+
+       create_filename(filename, PATH_MAX, local, "blocked");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(remote, addr);
+
+       if (blocked == FALSE)
+               return textfile_casedel(filename, addr);
+
+       return textfile_caseput(filename, addr, "");
+}
+
+int write_device_services(const bdaddr_t *sba, const bdaddr_t *dba,
+                                                       const char *services)
+{
+       char filename[PATH_MAX + 1], addr[18];
+
+       create_filename(filename, PATH_MAX, sba, "primary");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dba, addr);
+
+       return textfile_put(filename, addr, services);
+}
+
+static void filter_keys(char *key, char *value, void *data)
+{
+       struct match *match = data;
+       const char *address = match->pattern;
+
+       /* Each key contains: MAC#handle*/
+       if (strncasecmp(key, address, 17) == 0)
+               match->keys = g_slist_append(match->keys, g_strdup(key));
+}
+
+int delete_device_service(const bdaddr_t *sba, const bdaddr_t *dba)
+{
+       GSList *l;
+       struct match match;
+       char filename[PATH_MAX + 1], address[18];
+       int err;
+
+       create_filename(filename, PATH_MAX, sba, "primary");
+
+       memset(address, 0, sizeof(address));
+       ba2str(dba, address);
+
+       err = textfile_del(filename, address);
+       if (err < 0)
+               return err;
+
+       /* Deleting all characteristics of a given address */
+       memset(&match, 0, sizeof(match));
+       match.pattern = address;
+
+       create_filename(filename, PATH_MAX, sba, "characteristic");
+       err = textfile_foreach(filename, filter_keys, &match);
+       if (err < 0)
+               return err;
+
+       for (l = match.keys; l; l = l->next) {
+               const char *key = l->data;
+               textfile_del(filename, key);
+       }
+
+       g_slist_foreach(match.keys, (GFunc) g_free, NULL);
+       g_slist_free(match.keys);
+
+       /* Deleting all attributes values of a given address */
+       memset(&match, 0, sizeof(match));
+       match.pattern = address;
+
+       create_filename(filename, PATH_MAX, sba, "attributes");
+       err = textfile_foreach(filename, filter_keys, &match);
+       if (err < 0)
+               return err;
+
+       for (l = match.keys; l; l = l->next) {
+               const char *key = l->data;
+               textfile_del(filename, key);
+       }
+
+       g_slist_foreach(match.keys, (GFunc) g_free, NULL);
+       g_slist_free(match.keys);
+
+       return 0;
+}
+
+char *read_device_services(const bdaddr_t *sba, const bdaddr_t *dba)
+{
+       char filename[PATH_MAX + 1], addr[18];
+
+       create_filename(filename, PATH_MAX, sba, "primary");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dba, addr);
+
+       return textfile_caseget(filename, addr);
+}
+
+int write_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba,
+                                       uint16_t handle, const char *chars)
+{
+       char filename[PATH_MAX + 1], addr[18], key[23];
+
+       create_filename(filename, PATH_MAX, sba, "characteristic");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dba, addr);
+
+       snprintf(key, sizeof(key), "%17s#%04X", addr, handle);
+
+       return textfile_put(filename, key, chars);
+}
+
+char *read_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba,
+                                                       uint16_t handle)
+{
+       char filename[PATH_MAX + 1], addr[18], key[23];
+
+       create_filename(filename, PATH_MAX, sba, "characteristic");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dba, addr);
+
+       snprintf(key, sizeof(key), "%17s#%04X", addr, handle);
+
+       return textfile_caseget(filename, key);
+}
+
+int write_device_attribute(const bdaddr_t *sba, const bdaddr_t *dba,
+                                       uint16_t handle, const char *chars)
+{
+       char filename[PATH_MAX + 1], addr[18], key[23];
+
+       create_filename(filename, PATH_MAX, sba, "attributes");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dba, addr);
+
+       snprintf(key, sizeof(key), "%17s#%04X", addr, handle);
+
+       return textfile_put(filename, key, chars);
+}
+
+int read_device_attributes(const bdaddr_t *sba, textfile_cb func, void *data)
+{
+       char filename[PATH_MAX + 1];
+
+       create_filename(filename, PATH_MAX, sba, "attributes");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       return textfile_foreach(filename, func, data);
+}
+
+int write_device_type(const bdaddr_t *sba, const bdaddr_t *dba,
+                                               device_type_t type)
+{
+       char filename[PATH_MAX + 1], addr[18], chars[3];
+
+       create_filename(filename, PATH_MAX, sba, "types");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dba, addr);
+
+       snprintf(chars, sizeof(chars), "%2.2X", type);
+
+       return textfile_put(filename, addr, chars);
+}
+
+device_type_t read_device_type(const bdaddr_t *sba, const bdaddr_t *dba)
+{
+       char filename[PATH_MAX + 1], addr[18], *chars;
+       device_type_t type;
+
+       create_filename(filename, PATH_MAX, sba, "types");
+
+       create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+       ba2str(dba, addr);
+
+       chars = textfile_caseget(filename, addr);
+       if (chars == NULL)
+               return DEVICE_TYPE_UNKNOWN;
+
+       type = strtol(chars, NULL, 16);
+
+       free(chars);
+
+       return type;
+}
diff --git a/src/storage.h b/src/storage.h
new file mode 100644 (file)
index 0000000..1cb0af2
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 "textfile.h"
+
+int read_device_alias(const char *src, const char *dst, char *alias, size_t size);
+int write_device_alias(const char *src, const char *dst, const char *alias);
+int write_discoverable_timeout(bdaddr_t *bdaddr, int timeout);
+int read_discoverable_timeout(const char *src, int *timeout);
+int write_pairable_timeout(bdaddr_t *bdaddr, int timeout);
+int read_pairable_timeout(const char *src, int *timeout);
+int write_device_mode(bdaddr_t *bdaddr, const char *mode);
+int read_device_mode(const char *src, char *mode, int length);
+int read_on_mode(const char *src, char *mode, int length);
+int write_local_name(bdaddr_t *bdaddr, const char *name);
+int read_local_name(bdaddr_t *bdaddr, char *name);
+int write_local_class(bdaddr_t *bdaddr, uint8_t *class);
+int read_local_class(bdaddr_t *bdaddr, uint8_t *class);
+int write_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class);
+int read_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t *class);
+int write_device_name(bdaddr_t *local, bdaddr_t *peer, char *name);
+int read_device_name(const char *src, const char *dst, char *name);
+int write_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data);
+int read_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data);
+int write_version_info(bdaddr_t *local, bdaddr_t *peer, uint16_t manufacturer, uint8_t lmp_ver, uint16_t lmp_subver);
+int write_features_info(bdaddr_t *local, bdaddr_t *peer, unsigned char *page1, unsigned char *page2);
+int read_remote_features(bdaddr_t *local, bdaddr_t *peer, unsigned char *page1, unsigned char *page2);
+int write_lastseen_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm);
+int write_lastused_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm);
+int write_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t type, int length);
+int read_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t *type);
+int read_pin_code(bdaddr_t *local, bdaddr_t *peer, char *pin);
+gboolean read_trust(const bdaddr_t *local, const char *addr, const char *service);
+int write_trust(const char *src, const char *addr, const char *service, gboolean trust);
+GSList *list_trusts(bdaddr_t *local, const char *service);
+int write_device_profiles(bdaddr_t *src, bdaddr_t *dst, const char *profiles);
+int delete_entry(bdaddr_t *src, const char *storage, const char *key);
+int store_record(const gchar *src, const gchar *dst, sdp_record_t *rec);
+sdp_record_t *record_from_string(const gchar *str);
+sdp_record_t *fetch_record(const gchar *src, const gchar *dst, const uint32_t handle);
+int delete_record(const gchar *src, const gchar *dst, const uint32_t handle);
+void delete_all_records(const bdaddr_t *src, const bdaddr_t *dst);
+sdp_list_t *read_records(const bdaddr_t *src, const bdaddr_t *dst);
+sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid);
+int store_device_id(const gchar *src, const gchar *dst,
+                               const uint16_t source, const uint16_t vendor,
+                               const uint16_t product, const uint16_t version);
+int read_device_id(const gchar *src, const gchar *dst,
+                                       uint16_t *source, uint16_t *vendor,
+                                       uint16_t *product, uint16_t *version);
+int write_device_pairable(bdaddr_t *local, gboolean mode);
+int read_device_pairable(bdaddr_t *local, gboolean *mode);
+gboolean read_blocked(const bdaddr_t *local, const bdaddr_t *remote);
+int write_blocked(const bdaddr_t *local, const bdaddr_t *remote,
+                                                       gboolean blocked);
+int write_device_services(const bdaddr_t *sba, const bdaddr_t *dba,
+                                                       const char *services);
+int delete_device_service(const bdaddr_t *sba, const bdaddr_t *dba);
+char *read_device_services(const bdaddr_t *sba, const bdaddr_t *dba);
+int write_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba,
+                                       uint16_t handle, const char *chars);
+char *read_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba,
+                                                       uint16_t handle);
+int write_device_attribute(const bdaddr_t *sba, const bdaddr_t *dba,
+                                        uint16_t handle, const char *chars);
+int read_device_attributes(const bdaddr_t *sba, textfile_cb func, void *data);
+int write_device_type(const bdaddr_t *sba, const bdaddr_t *dba,
+                                               device_type_t type);
+device_type_t read_device_type(const bdaddr_t *sba, const bdaddr_t *dba);
+
+#define PNP_UUID               "00001200-0000-1000-8000-00805f9b34fb"
+
+#ifdef __TIZEN_PATCH__
+// Storing limited property
+int write_device_limited(bdaddr_t *local, gboolean mode);
+int read_device_limited(bdaddr_t *local, gboolean *mode);
+int read_version_info(const bdaddr_t *local, const char *addr,
+                                       uint16_t *manufacturer, uint8_t *lmp_ver, uint16_t *lmp_subver,
+                                       uint8_t *features);
+#endif
diff --git a/src/textfile.c b/src/textfile.c
new file mode 100644 (file)
index 0000000..d115ff6
--- /dev/null
@@ -0,0 +1,492 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+
+#include "textfile.h"
+
+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 char *strnpbrk(const char *s, ssize_t len, const char *accept)
+{
+       const char *p = s;
+       const char *end;
+
+       end = s + len - 1;
+
+       while (p <= end && *p) {
+               const char *a = accept;
+
+               while (*a) {
+                       if (*p == *a)
+                               return (char *) p;
+                       a++;
+               }
+
+               p++;
+       }
+
+       return NULL;
+}
+
+static int write_key(const char *pathname, const char *key, const char *value, int icase)
+{
+       struct stat st;
+       char *map, *off, *end, *str;
+       off_t size, 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 = strnpbrk(off, size, "\r\n");
+       if (!end) {
+               err = EILSEQ;
+               goto unmap;
+       }
+
+       if (value && ((ssize_t) strlen(value) == end - off - len - 1) &&
+                       !strncmp(off + len + 1, value, end - off - len - 1))
+               goto unmap;
+
+       len = strspn(end, "\r\n");
+       end += len;
+
+       len = size - (end - map);
+       if (!len) {
+               munmap(map, size);
+               if (ftruncate(fd, base) < 0) {
+                       err = errno;
+                       goto unlock;
+               }
+               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 = strnpbrk(off, size - (map - 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, textfile_cb func, void *data)
+{
+       struct stat st;
+       char *map, *off, *end, *key, *value;
+       off_t size; size_t len;
+       int fd, err = 0;
+
+       fd = open(pathname, O_RDONLY);
+       if (fd < 0)
+               return -errno;
+
+       if (flock(fd, LOCK_SH) < 0) {
+               err = errno;
+               goto close;
+       }
+
+       if (fstat(fd, &st) < 0) {
+               err = errno;
+               goto unlock;
+       }
+
+       size = st.st_size;
+
+       map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+       if (!map || map == MAP_FAILED) {
+               err = errno;
+               goto unlock;
+       }
+
+       off = map;
+
+       while (size - (off - map) > 0) {
+               end = strnpbrk(off, size - (off - map), " ");
+               if (!end) {
+                       err = EILSEQ;
+                       break;
+               }
+
+               len = end - off;
+
+               key = malloc(len + 1);
+               if (!key) {
+                       err = errno;
+                       break;
+               }
+
+               memset(key, 0, len + 1);
+               memcpy(key, off, len);
+
+               off = end + 1;
+
+               if (size - (off - map) < 0) {
+                       err = EILSEQ;
+                       free(key);
+                       break;
+               }
+
+               end = strnpbrk(off, size - (off - map), "\r\n");
+               if (!end) {
+                       err = EILSEQ;
+                       free(key);
+                       break;
+               }
+
+               len = end - off;
+
+               value = malloc(len + 1);
+               if (!value) {
+                       err = errno;
+                       free(key);
+                       break;
+               }
+
+               memset(value, 0, len + 1);
+               memcpy(value, off, len);
+
+               func(key, value, data);
+
+               free(key);
+               free(value);
+
+               off = end + 1;
+       }
+
+       munmap(map, size);
+
+unlock:
+       flock(fd, LOCK_UN);
+
+close:
+       close(fd);
+       errno = err;
+
+       return 0;
+}
diff --git a/src/textfile.h b/src/textfile.h
new file mode 100644 (file)
index 0000000..dc5fc2b
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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);
+
+typedef void (*textfile_cb) (char *key, char *value, void *data);
+
+int textfile_foreach(const char *pathname, textfile_cb func, void *data);
+
+#endif /* __TEXTFILE_H */
diff --git a/src/uinput.h b/src/uinput.h
new file mode 100644 (file)
index 0000000..e83986b
--- /dev/null
@@ -0,0 +1,724 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <stdint.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+/* 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/test/agent.c b/test/agent.c
new file mode 100644 (file)
index 0000000..fed3266
--- /dev/null
@@ -0,0 +1,702 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <dbus/dbus.h>
+
+static char *passkey_value = NULL;
+static int passkey_delay = 0;
+static int do_reject = 0;
+
+static volatile sig_atomic_t __io_canceled = 0;
+static volatile sig_atomic_t __io_terminated = 0;
+
+static void sig_term(int sig)
+{
+       __io_canceled = 1;
+}
+
+static DBusHandlerResult agent_filter(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       const char *name, *old, *new;
+
+       if (!dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS,
+                                               "NameOwnerChanged"))
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &name,
+                                       DBUS_TYPE_STRING, &old,
+                                       DBUS_TYPE_STRING, &new,
+                                       DBUS_TYPE_INVALID)) {
+               fprintf(stderr, "Invalid arguments for NameOwnerChanged signal");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (!strcmp(name, "org.bluez") && *new == '\0') {
+               fprintf(stderr, "Agent has been terminated\n");
+               __io_terminated = 1;
+       }
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult request_pincode_message(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+       const char *path;
+
+       if (!passkey_value)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                                       DBUS_TYPE_INVALID)) {
+               fprintf(stderr, "Invalid arguments for RequestPinCode method");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (do_reject) {
+               reply = dbus_message_new_error(msg, "org.bluez.Error.Rejected", "");
+               goto send;
+       }
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply) {
+               fprintf(stderr, "Can't create reply message\n");
+               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+       }
+
+       printf("Pincode request for device %s\n", path);
+
+       if (passkey_delay) {
+               printf("Waiting for %d seconds\n", passkey_delay);
+               sleep(passkey_delay);
+       }
+
+       dbus_message_append_args(reply, DBUS_TYPE_STRING, &passkey_value,
+                                                       DBUS_TYPE_INVALID);
+
+send:
+       dbus_connection_send(conn, reply, NULL);
+
+       dbus_connection_flush(conn);
+
+       dbus_message_unref(reply);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult request_passkey_message(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+       const char *path;
+       unsigned int passkey;
+
+       if (!passkey_value)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                                       DBUS_TYPE_INVALID)) {
+               fprintf(stderr, "Invalid arguments for RequestPasskey method");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (do_reject) {
+               reply = dbus_message_new_error(msg, "org.bluez.Error.Rejected", "");
+               goto send;
+       }
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply) {
+               fprintf(stderr, "Can't create reply message\n");
+               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+       }
+
+       printf("Passkey request for device %s\n", path);
+
+       if (passkey_delay) {
+               printf("Waiting for %d seconds\n", passkey_delay);
+               sleep(passkey_delay);
+       }
+
+       passkey = strtoul(passkey_value, NULL, 10);
+
+       dbus_message_append_args(reply, DBUS_TYPE_UINT32, &passkey,
+                                                       DBUS_TYPE_INVALID);
+
+send:
+       dbus_connection_send(conn, reply, NULL);
+
+       dbus_connection_flush(conn);
+
+       dbus_message_unref(reply);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult request_confirmation_message(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+       const char *path;
+       unsigned int passkey;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                               DBUS_TYPE_UINT32, &passkey,
+                                                       DBUS_TYPE_INVALID)) {
+               fprintf(stderr, "Invalid arguments for RequestPasskey method");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (do_reject) {
+               reply = dbus_message_new_error(msg, "org.bluez.Error.Rejected", "");
+               goto send;
+       }
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply) {
+               fprintf(stderr, "Can't create reply message\n");
+               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+       }
+
+       printf("Confirmation request of %u for device %s\n", passkey, path);
+
+       if (passkey_delay) {
+               printf("Waiting for %d seconds\n", passkey_delay);
+               sleep(passkey_delay);
+       }
+
+send:
+       dbus_connection_send(conn, reply, NULL);
+
+       dbus_connection_flush(conn);
+
+       dbus_message_unref(reply);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult authorize_message(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+       const char *path, *uuid;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                               DBUS_TYPE_STRING, &uuid,
+                                                       DBUS_TYPE_INVALID)) {
+               fprintf(stderr, "Invalid arguments for Authorize method");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (do_reject) {
+               reply = dbus_message_new_error(msg, "org.bluez.Error.Rejected", "");
+               goto send;
+       }
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply) {
+               fprintf(stderr, "Can't create reply message\n");
+               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+       }
+
+       printf("Authorizing request for %s\n", path);
+
+send:
+       dbus_connection_send(conn, reply, NULL);
+
+       dbus_connection_flush(conn);
+
+       dbus_message_unref(reply);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult cancel_message(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) {
+               fprintf(stderr, "Invalid arguments for passkey Confirm method");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       printf("Request canceled\n");
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply) {
+               fprintf(stderr, "Can't create reply message\n");
+               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+       }
+
+       dbus_connection_send(conn, reply, NULL);
+
+       dbus_connection_flush(conn);
+
+       dbus_message_unref(reply);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult release_message(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       DBusMessage *reply;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) {
+               fprintf(stderr, "Invalid arguments for Release method");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (!__io_canceled)
+               fprintf(stderr, "Agent has been released\n");
+
+       __io_terminated = 1;
+
+       reply = dbus_message_new_method_return(msg);
+       if (!reply) {
+               fprintf(stderr, "Can't create reply message\n");
+               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+       }
+
+       dbus_connection_send(conn, reply, NULL);
+
+       dbus_connection_flush(conn);
+
+       dbus_message_unref(reply);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult agent_message(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       if (dbus_message_is_method_call(msg, "org.bluez.Agent",
+                                                       "RequestPinCode"))
+               return request_pincode_message(conn, msg, data);
+
+       if (dbus_message_is_method_call(msg, "org.bluez.Agent",
+                                                       "RequestPasskey"))
+               return request_passkey_message(conn, msg, data);
+
+       if (dbus_message_is_method_call(msg, "org.bluez.Agent",
+                                                       "RequestConfirmation"))
+               return request_confirmation_message(conn, msg, data);
+
+       if (dbus_message_is_method_call(msg, "org.bluez.Agent", "Authorize"))
+               return authorize_message(conn, msg, data);
+
+       if (dbus_message_is_method_call(msg, "org.bluez.Agent", "Cancel"))
+               return cancel_message(conn, msg, data);
+
+       if (dbus_message_is_method_call(msg, "org.bluez.Agent", "Release"))
+               return release_message(conn, msg, data);
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static const DBusObjectPathVTable agent_table = {
+       .message_function = agent_message,
+};
+
+static int register_agent(DBusConnection *conn, const char *adapter_path,
+                                               const char *agent_path,
+                                               const char *capabilities)
+{
+       DBusMessage *msg, *reply;
+       DBusError err;
+
+       msg = dbus_message_new_method_call("org.bluez", adapter_path,
+                                       "org.bluez.Adapter", "RegisterAgent");
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return -1;
+       }
+
+       dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &agent_path,
+                                       DBUS_TYPE_STRING, &capabilities,
+                                       DBUS_TYPE_INVALID);
+
+       dbus_error_init(&err);
+
+       reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
+
+       dbus_message_unref(msg);
+
+       if (!reply) {
+               fprintf(stderr, "Can't register agent\n");
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               return -1;
+       }
+
+       dbus_message_unref(reply);
+
+       dbus_connection_flush(conn);
+
+       return 0;
+}
+
+static int unregister_agent(DBusConnection *conn, const char *adapter_path,
+                                                       const char *agent_path)
+{
+       DBusMessage *msg, *reply;
+       DBusError err;
+
+       msg = dbus_message_new_method_call("org.bluez", adapter_path,
+                                       "org.bluez.Adapter", "UnregisterAgent");
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return -1;
+       }
+
+       dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &agent_path,
+                                                       DBUS_TYPE_INVALID);
+
+       dbus_error_init(&err);
+
+       reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
+
+       dbus_message_unref(msg);
+
+       if (!reply) {
+               fprintf(stderr, "Can't unregister agent\n");
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               return -1;
+       }
+
+       dbus_message_unref(reply);
+
+       dbus_connection_flush(conn);
+
+       dbus_connection_unregister_object_path(conn, agent_path);
+
+       return 0;
+}
+
+static int create_paired_device(DBusConnection *conn, const char *adapter_path,
+                                               const char *agent_path,
+                                               const char *capabilities,
+                                               const char *device)
+{
+       dbus_bool_t success;
+       DBusMessage *msg;
+
+       msg = dbus_message_new_method_call("org.bluez", adapter_path,
+                                               "org.bluez.Adapter",
+                                               "CreatePairedDevice");
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return -1;
+       }
+
+       dbus_message_append_args(msg, DBUS_TYPE_STRING, &device,
+                                       DBUS_TYPE_OBJECT_PATH, &agent_path,
+                                       DBUS_TYPE_STRING, &capabilities,
+                                       DBUS_TYPE_INVALID);
+
+       success = dbus_connection_send(conn, msg, NULL);
+
+       dbus_message_unref(msg);
+
+       if (!success) {
+               fprintf(stderr, "Not enough memory for message send\n");
+               return -1;
+       }
+
+       dbus_connection_flush(conn);
+
+       return 0;
+}
+
+static char *get_default_adapter_path(DBusConnection *conn)
+{
+       DBusMessage *msg, *reply;
+       DBusError err;
+       const char *reply_path;
+       char *path;
+
+       msg = dbus_message_new_method_call("org.bluez", "/",
+                                       "org.bluez.Manager", "DefaultAdapter");
+
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return NULL;
+       }
+
+       dbus_error_init(&err);
+
+       reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
+
+       dbus_message_unref(msg);
+
+       if (!reply) {
+               fprintf(stderr,
+                       "Can't get default adapter\n");
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               return NULL;
+       }
+
+       if (!dbus_message_get_args(reply, &err,
+                                       DBUS_TYPE_OBJECT_PATH, &reply_path,
+                                       DBUS_TYPE_INVALID)) {
+               fprintf(stderr,
+                       "Can't get reply arguments\n");
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               dbus_message_unref(reply);
+               return NULL;
+       }
+
+       path = strdup(reply_path);
+
+       dbus_message_unref(reply);
+
+       dbus_connection_flush(conn);
+
+       return path;
+}
+
+static char *get_adapter_path(DBusConnection *conn, const char *adapter)
+{
+       DBusMessage *msg, *reply;
+       DBusError err;
+       const char *reply_path;
+       char *path;
+
+       if (!adapter)
+               return get_default_adapter_path(conn);
+
+       msg = dbus_message_new_method_call("org.bluez", "/",
+                                       "org.bluez.Manager", "FindAdapter");
+
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return NULL;
+       }
+
+       dbus_message_append_args(msg, DBUS_TYPE_STRING, &adapter,
+                                       DBUS_TYPE_INVALID);
+
+       dbus_error_init(&err);
+
+       reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
+
+       dbus_message_unref(msg);
+
+       if (!reply) {
+               fprintf(stderr,
+                       "Can't find adapter %s\n", adapter);
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               return NULL;
+       }
+
+       if (!dbus_message_get_args(reply, &err,
+                                       DBUS_TYPE_OBJECT_PATH, &reply_path,
+                                       DBUS_TYPE_INVALID)) {
+               fprintf(stderr,
+                       "Can't get reply arguments\n");
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               dbus_message_unref(reply);
+               return NULL;
+       }
+
+       path = strdup(reply_path);
+
+       dbus_message_unref(reply);
+
+       dbus_connection_flush(conn);
+
+       return path;
+}
+
+static void usage(void)
+{
+       printf("Bluetooth agent ver %s\n\n", VERSION);
+
+       printf("Usage:\n"
+               "\tagent [--adapter adapter-path] [--path agent-path] <passkey> [<device>]\n"
+               "\n");
+}
+
+static struct option main_options[] = {
+       { "adapter",    1, 0, 'a' },
+       { "path",       1, 0, 'p' },
+       { "capabilites",1, 0, 'c' },
+       { "delay",      1, 0, 'd' },
+       { "reject",     0, 0, 'r' },
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       const char *capabilities = "DisplayYesNo";
+       struct sigaction sa;
+       DBusConnection *conn;
+       char match_string[128], default_path[128], *adapter_id = NULL;
+       char *adapter_path = NULL, *agent_path = NULL, *device = NULL;
+       int opt;
+
+       snprintf(default_path, sizeof(default_path),
+                                       "/org/bluez/agent_%d", getpid());
+
+       while ((opt = getopt_long(argc, argv, "+a:p:c:d:rh", main_options, NULL)) != EOF) {
+               switch(opt) {
+               case 'a':
+                       adapter_id = optarg;
+                       break;
+               case 'p':
+                       if (optarg[0] != '/') {
+                               fprintf(stderr, "Invalid path\n");
+                               exit(1);
+                       }
+                       agent_path = strdup(optarg);
+                       break;
+               case 'c':
+                       capabilities = optarg;
+                       break;
+               case 'd':
+                       passkey_delay = atoi(optarg);
+                       break;
+               case 'r':
+                       do_reject = 1;
+                       break;
+               case 'h':
+                       usage();
+                       exit(0);
+               default:
+                       exit(1);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       passkey_value = strdup(argv[0]);
+
+       if (argc > 1)
+               device = strdup(argv[1]);
+
+       if (!agent_path)
+               agent_path = strdup(default_path);
+
+       conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (!conn) {
+               fprintf(stderr, "Can't get on system bus");
+               exit(1);
+       }
+
+       adapter_path = get_adapter_path(conn, adapter_id);
+       if (!adapter_path)
+               exit(1);
+
+       if (!dbus_connection_register_object_path(conn, agent_path,
+                                                       &agent_table, NULL)) {
+               fprintf(stderr, "Can't register object path for agent\n");
+               exit(1);
+       }
+
+       if (device) {
+               if (create_paired_device(conn, adapter_path, agent_path,
+                                               capabilities, device) < 0) {
+                       dbus_connection_unref(conn);
+                       exit(1);
+               }
+       } else {
+               if (register_agent(conn, adapter_path, agent_path,
+                                                       capabilities) < 0) {
+                       dbus_connection_unref(conn);
+                       exit(1);
+               }
+       }
+
+       if (!dbus_connection_add_filter(conn, agent_filter, NULL, NULL))
+               fprintf(stderr, "Can't add signal filter");
+
+       snprintf(match_string, sizeof(match_string),
+                       "interface=%s,member=NameOwnerChanged,arg0=%s",
+                       DBUS_INTERFACE_DBUS, "org.bluez");
+
+       dbus_bus_add_match(conn, match_string, NULL);
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_flags   = SA_NOCLDSTOP;
+       sa.sa_handler = sig_term;
+       sigaction(SIGTERM, &sa, NULL);
+       sigaction(SIGINT,  &sa, NULL);
+
+       while (!__io_canceled && !__io_terminated) {
+               if (dbus_connection_read_write_dispatch(conn, 500) != TRUE)
+                       break;
+       }
+
+       if (!__io_terminated && !device)
+               unregister_agent(conn, adapter_path, agent_path);
+
+       free(adapter_path);
+       free(agent_path);
+
+       free(passkey_value);
+
+       dbus_connection_unref(conn);
+
+       return 0;
+}
diff --git a/test/apitest b/test/apitest
new file mode 100755 (executable)
index 0000000..b1c3f10
--- /dev/null
@@ -0,0 +1,448 @@
+#!/usr/bin/env python
+
+import dbus
+import dbus.decorators
+import dbus.glib
+import gobject
+import sys
+import getopt
+from signal import *
+
+mgr_cmds = [ "InterfaceVersion", "ListAdapters", "DefaultAdapter" ]
+mgr_signals = [ "AdapterAdded", "AdapterRemoved" ]
+
+dev_cmds = [ "GetAddress",
+             "GetVersion",
+             "GetRevision",
+             "GetManufacturer",
+             "GetCompany",
+             "GetMode",
+             "SetMode",
+             "GetDiscoverableTimeout",
+             "SetDiscoverableTimeout",
+             "IsConnectable",
+             "IsDiscoverable",
+             "IsConnected",
+             "ListConnections",
+             "GetMajorClass",
+             "ListAvailableMinorClasses",
+             "GetMinorClass",
+             "SetMinorClass",
+             "GetServiceClasses",
+             "GetName",
+             "SetName",
+             "GetRemoteVersion",
+             "GetRemoteRevision",
+             "GetRemoteManufacturer",
+             "GetRemoteCompany",
+             "GetRemoteMajorClass",
+             "GetRemoteMinorClass",
+             "GetRemoteServiceClasses",
+             "GetRemoteClass",
+             "GetRemoteName",
+             "GetRemoteAlias",
+             "SetRemoteAlias",
+             "ClearRemoteAlias",
+             "LastSeen",
+             "LastUsed",
+             "DisconnectRemoteDevice",
+             "CreateBonding",
+             "CancelBondingProcess",
+             "RemoveBonding",
+             "HasBonding",
+             "ListBondings",
+             "GetPinCodeLength",
+             "GetEncryptionKeySize",
+             "DiscoverDevices",
+             "DiscoverDevicesWithoutNameResolving",
+             "CancelDiscovery",
+             "ListRemoteDevices",
+             "ListRecentRemoteDevices" ]
+dev_signals = [ "ModeChanged",
+                "NameChanged",
+                "MinorClassChanged",
+                "DiscoveryStarted",
+                "DiscoveryCompleted",
+                "RemoteDeviceFound",
+                "RemoteNameUpdated",
+                "RemoteNameFailed",
+                "RemoteAliasChanged"
+                "RemoteAliasCleared",
+                "RemoteDeviceConnected",
+                "RemoteDeviceDisconnectRequested",
+                "RemoteDeviceDisconnected",
+                "BondingCreated",
+                "BondingRemoved" ]
+
+dev_signals_filter = [ "/org/bluez/hci0", "/org/bluez/hci1",
+                       "/org/bluez/hci2", "/org/bluez/hci3",
+                       "/org/bluez/hci4", "/org/bluez/hci5",
+                       "/org/bluez/hci6", "/org/bluez/hci7" ]
+
+class Tester:
+    exit_events = []
+    dev_path = None
+    need_dev = False
+    listen = False
+    at_interrupt = None
+
+    def __init__(self, argv):
+        self.name = argv[0]
+
+        self.parse_args(argv[1:])
+
+        try:
+            self.dbus_setup()
+        except dbus.DBusException, e:
+            print 'Failed to do D-Bus setup: %s' % e
+            sys.exit(1)
+
+    def parse_args(self, argv):
+        try:
+            opts, args = getopt.getopt(argv, "hli:")
+        except getopt.GetoptError:
+            self.usage()
+            sys.exit(1)
+
+        for o, a in opts:
+            if o == "-h":
+                self.usage()
+                sys.exit()
+            elif o == "-l":
+                self.listen = True
+            elif o == "-i":
+                if a[0] == '/':
+                    self.dev_path = a
+                else:
+                    self.dev_path = '/org/bluez/%s' % a
+
+        if not (args or self.listen):
+            self.usage()
+            sys.exit(1)
+
+        if args:
+            self.cmd = args[0]
+            self.cmd_args = args[1:]
+
+    def dbus_dev_setup(self):
+        if not self.dev_path:
+            try:
+                self.dbus_mgr_setup()
+                self.dev_path = self.manager.DefaultAdapter()
+            except dbus.DBusException, e:
+                print 'Failed to get default device: %s' % e
+                sys.exit(1)
+        try:
+            obj = self.bus.get_object('org.bluez', self.dev_path)
+            self.device = dbus.Interface(obj, 'org.bluez.Adapter')
+        except dbus.DBusException, e:
+            print 'Failed to setup device path: %s' % e
+            sys.exit(1)
+
+    def dbus_dev_sig_setup(self):
+        try:
+           for signal in dev_signals:
+                for path in dev_signals_filter:
+                    self.bus.add_signal_receiver(self.dev_signal_handler,
+                                             signal, 'org.bluez.Adapter',
+                                             'org.bluez', path,
+                                             message_keyword='dbus_message')
+        except dbus.DBusException, e:
+            print 'Failed to setup signal handler for device path: %s' % e
+            sys.exit(1)
+
+    def dbus_mgr_sig_setup(self):
+        try:
+            for signal in mgr_signals:
+                self.bus.add_signal_receiver(self.mgr_signal_handler,
+                                         signal,'org.bluez.Manager',
+                                         'org.bluez', '/org/bluez')
+        except dbus.DBusException, e:
+            print 'Failed to setup signal handler for manager path: %s' % e
+            sys.exit(1)
+
+    def dbus_mgr_setup(self):
+        self.manager_obj = self.bus.get_object('org.bluez', '/org/bluez')
+        self.manager = dbus.Interface(self.manager_obj, 'org.bluez.Manager')
+
+    def dbus_setup(self):
+        self.bus = dbus.SystemBus()
+
+    def usage(self):
+        print 'Usage: %s [-i <dev>] [-l] [-h] <cmd> [arg1..]' % self.name
+        print '  -i <dev>   Specify device (e.g. "hci0" or "/org/bluez/hci0")'
+        print '  -l         Listen for events (no command required)'
+        print '  -h         Show this help'
+        print 'Manager commands:'
+        for cmd in mgr_cmds:
+            print '\t%s' % cmd
+        print 'Adapter commands:'
+        for cmd in dev_cmds:
+            print '\t%s' % cmd
+
+    #@dbus.decorators.explicitly_pass_message
+    def dev_signal_handler(*args, **keywords):
+        dbus_message = keywords["dbus_message"]
+        print '%s - %s: ' % (dbus_message.get_member(), dbus_message.get_path()),
+        for arg in args[1:]:
+            print '%s   ' % arg,
+        print
+
+    #@dbus.decorators.explicitly_pass_message
+    def mgr_signal_handler(*args, **keywords):
+        dbus_message = keywords["dbus_message"]
+        print '%s: ' % dbus_message.get_member()
+        for arg in args[1:]:
+            print '%s   ' % arg,
+        print
+
+    def signal_cb(self, sig, frame):
+        print 'Caught signal, exiting'
+        if self.at_interrupt:
+            self.at_interrupt()
+        self.main_loop.quit()
+
+    def call_mgr_dbus_func(self):
+        if self.cmd == 'InterfaceVersion':
+            try:
+                print self.manager.InterfaceVersion()
+            except dbus.DBusException, e:
+                print 'Sending %s failed: %s' % (self.cmd, e)
+        if self.cmd == 'ListAdapters':
+            try:
+                devices = self.manager.ListAdapters()
+            except dbus.DBusException, e:
+                print 'Sending %s failed: %s' % (self.cmd, e)
+                sys.exit(1)
+            for device in devices:
+                print device
+        elif self.cmd == 'DefaultAdapter':
+            try:
+                print self.manager.DefaultAdapter()
+            except dbus.DBusException, e:
+                print 'Sending %s failed: %s' % (self.cmd, e)
+                sys.exit(1)
+
+    def call_dev_dbus_func(self):
+       try:
+           if self.cmd == 'GetAddress':
+               print self.device.GetAddress()
+           elif self.cmd == 'GetManufacturer':
+               print self.device.GetManufacturer()
+           elif self.cmd == 'GetVersion':
+               print self.device.GetVersion()
+           elif self.cmd == 'GetRevision':
+               print self.device.GetRevision()
+           elif self.cmd == 'GetCompany':
+               print self.device.GetCompany()
+           elif self.cmd == 'GetMode':
+               print self.device.GetMode()
+           elif self.cmd == 'SetMode':
+               if len(self.cmd_args) == 1:
+                   self.device.SetMode(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> SetMode scan_mode' % self.name
+           elif self.cmd == 'GetDiscoverableTimeout':
+               print '%u' % (self.device.GetDiscoverableTimeout())
+           elif self.cmd == 'SetDiscoverableTimeout':
+               if len(self.cmd_args) == 1:
+                   self.device.SetDiscoverableTimeout(dbus.UInt32(self.cmd_args[0]))
+               else:
+                   print 'Usage: %s -i <dev> SetDiscoverableTimeout timeout' % self.name
+           elif self.cmd == 'IsConnectable':
+               print self.device.IsConnectable()
+           elif self.cmd == 'IsDiscoverable':
+               print self.device.IsDiscoverable()
+           elif self.cmd == 'IsConnected':
+               if len(self.cmd_args) == 1:
+                   print self.device.IsConnected(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> IsConnected address' % self.name
+           elif self.cmd == 'ListConnections':
+               print self.device.ListConnections()
+           elif self.cmd == 'GetMajorClass':
+               print self.device.GetMajorClass()
+           elif self.cmd == 'ListAvailableMinorClasses':
+               print self.device.ListAvailableMinorClasses()
+           elif self.cmd == 'GetMinorClass':
+               print self.device.GetMinorClass()
+           elif self.cmd == 'SetMinorClass':
+               if len(self.cmd_args) == 1:
+                   self.device.SetMinorClass(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> SetMinorClass minor' % self.name
+           elif self.cmd == 'GetServiceClasses':
+               classes = self.device.GetServiceClasses()
+               for clas in classes: 
+                   print clas,
+           elif self.cmd == 'GetName':
+               print self.device.GetName()
+           elif self.cmd == 'SetName':
+               if len(self.cmd_args) == 1:
+                   self.device.SetName(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> SetName newname' % self.name
+           elif self.cmd == 'GetRemoteName':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteName(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteName address' % self.name
+           elif self.cmd == 'GetRemoteVersion':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteVersion(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteVersion address' % self.name
+           elif self.cmd == 'GetRemoteRevision':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteRevision(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteRevision address' % self.name
+           elif self.cmd == 'GetRemoteManufacturer':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteManufacturer(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteManufacturer address' % self.name
+           elif self.cmd == 'GetRemoteCompany':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteCompany(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteCompany address' % self.name
+           elif self.cmd == 'GetRemoteAlias':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteAlias(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteAlias address' % self.name
+           elif self.cmd == 'GetRemoteMajorClass':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteMajorClass(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteMajorClass address' % self.name
+           elif self.cmd == 'GetRemoteMinorClass':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteMinorClass(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteMinorClass address' % self.name
+           elif self.cmd == 'GetRemoteServiceClasses':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetRemoteServiceClasses(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetRemoteServiceClasses address' % self.name
+           elif self.cmd == 'SetRemoteAlias':
+               if len(self.cmd_args) == 2:
+                   self.device.SetRemoteAlias(self.cmd_args[0], self.cmd_args[1])
+               else:
+                   print 'Usage: %s -i <dev> SetRemoteAlias address alias' % self.name
+           elif self.cmd == 'ClearRemoteAlias':
+               if len(self.cmd_args) == 1:
+                   print self.device.ClearRemoteAlias(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> ClearRemoteAlias address' % self.name
+           elif self.cmd == 'LastSeen':
+               if len(self.cmd_args) == 1:
+                   print self.device.LastSeen(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> LastSeen address' % self.name
+           elif self.cmd == 'LastUsed':
+               if len(self.cmd_args) == 1:
+                   print self.device.LastUsed(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> LastUsed address' % self.name
+           elif self.cmd == 'DisconnectRemoteDevice':
+               if len(self.cmd_args) == 1:
+                   print self.device.LastUsed(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> DisconnectRemoteDevice address' % self.name
+           elif self.cmd == 'CreateBonding':
+               if len(self.cmd_args) == 1:
+                   print self.device.CreateBonding(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> CreateBonding address' % self.name
+           elif self.cmd == 'RemoveBonding':
+               if len(self.cmd_args) == 1:
+                   print self.device.RemoveBonding(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> RemoveBonding address' % self.name
+           elif self.cmd == 'CancelBondingProcess':
+               if len(self.cmd_args) == 1:
+                   print self.device.CancelBondingProcess(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> CancelBondingProcess address' % self.name
+           elif self.cmd == 'HasBonding':
+               if len(self.cmd_args) == 1:
+                   print self.device.HasBonding(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> HasBonding address' % self.name
+           elif self.cmd == 'ListBondings':
+               bondings = self.device.ListBondings()
+               for bond in bondings: 
+                   print bond,
+           elif self.cmd == 'GetPinCodeLength':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetPinCodeLength(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetPinCodeLength address' % self.name
+           elif self.cmd == 'GetEncryptionKeySize':
+               if len(self.cmd_args) == 1:
+                   print self.device.GetEncryptionKeySize(self.cmd_args[0])
+               else:
+                   print 'Usage: %s -i <dev> GetEncryptionKeySize address' % self.name
+           elif self.cmd == 'DiscoverDevices':
+               print self.device.DiscoverDevices()
+           elif self.cmd == 'DiscoverDevicesWithoutNameResolving':
+               print self.device.DiscoverDevicesWithoutNameResolving()
+           elif self.cmd == 'ListRemoteDevices':
+               devices = self.device.ListRemoteDevices()
+               for device in devices: 
+                   print device,
+           elif self.cmd == 'ListRecentRemoteDevices':
+               if len(self.cmd_args) == 1:
+                   devices = self.device.ListRecentRemoteDevices(self.cmd_args[0])
+                   for device in devices: 
+                       print device,
+               else:
+                   print 'Usage: %s -i <dev> ListRecentRemoteDevices date' % self.name
+           else:
+                # FIXME: remove at future version
+                print 'Script Error: Method %s not found. Maybe a mispelled word.' % (self.cmd_args)
+       except dbus.DBusException, e:
+           print '%s failed: %s' % (self.cmd, e)
+           sys.exit(1)
+
+    def run(self):
+        # Manager methods
+        if self.listen:
+            self.dbus_mgr_sig_setup()
+            self.dbus_dev_sig_setup()
+            print 'Listening for events...'
+
+        if self.cmd in mgr_cmds:
+            try:
+                self.dbus_mgr_setup()
+            except dbus.DBusException, e:
+                print 'Failed to setup manager interface: %s' % e
+                sys.exit(1)
+            self.call_mgr_dbus_func()
+        elif self.cmd in dev_cmds:
+            try:
+                self.dbus_dev_setup()
+            except dbus.DBusException, e:
+                print 'Failed to setup device interface: %s' % e
+                sys.exit(1)
+            self.call_dev_dbus_func()
+        elif not self.listen:
+            print 'Unknown command: %s' % self.cmd
+            self.usage()
+            sys.exit(1)
+
+        if self.listen:
+            signal(SIGINT, self.signal_cb)
+            signal(SIGTERM, self.signal_cb)
+            self.main_loop = gobject.MainLoop()
+            self.main_loop.run()
+
+if __name__ == '__main__':
+    gobject.threads_init()
+    dbus.glib.init_threads()
+
+    tester = Tester(sys.argv)
+    tester.run()
diff --git a/test/attest.c b/test/attest.c
new file mode 100644 (file)
index 0000000..12ba682
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+static int at_command(int fd, char *cmd, int to)
+{
+       fd_set rfds;
+       struct timeval timeout;
+       char buf[1024];
+       int sel, len, i, n;
+
+       len = write(fd, cmd, strlen(cmd));
+
+       for (i = 0; i < 100; i++) {
+
+               FD_ZERO(&rfds);
+               FD_SET(fd, &rfds);
+
+               timeout.tv_sec = 0;
+               timeout.tv_usec = to;
+
+               if ((sel = select(fd + 1, &rfds, NULL, NULL, &timeout)) > 0) {
+
+                       if (FD_ISSET(fd, &rfds)) {
+                               memset(buf, 0, sizeof(buf));
+                               len = read(fd, buf, sizeof(buf));
+                               for (n = 0; n < len; n++)
+                                       printf("%c", buf[n]);
+                               if (strstr(buf, "\r\nOK") != NULL)
+                                       break;
+                               if (strstr(buf, "\r\nERROR") != NULL)
+                                       break;
+                               if (strstr(buf, "\r\nCONNECT") != NULL)
+                                       break;
+                       }
+
+               }
+
+       }
+
+       return 0;
+}
+
+static int open_device(char *device)
+{
+       struct termios ti;
+       int fd;
+
+       fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
+       if (fd < 0) {
+               fprintf(stderr, "Can't open serial port: %s (%d)\n",
+                                                       strerror(errno), errno);
+               return -1;
+       }
+
+       tcflush(fd, TCIOFLUSH);
+
+       /* Switch tty to RAW mode */
+       cfmakeraw(&ti);
+       tcsetattr(fd, TCSANOW, &ti);
+
+       return fd;
+}
+
+static int open_socket(bdaddr_t *bdaddr, 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, BDADDR_ANY);
+
+       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, bdaddr);
+       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 usage(void)
+{
+       printf("Usage:\n\tattest <device> | <bdaddr> [channel]\n");
+}
+
+int main(int argc, char *argv[])
+{
+       int fd;
+
+       bdaddr_t bdaddr;
+       uint8_t channel;
+
+       switch (argc) {
+       case 2:
+               str2ba(argv[1], &bdaddr);
+               channel = 1;
+               break;
+       case 3:
+               str2ba(argv[1], &bdaddr);
+               channel = atoi(argv[2]);
+               break;
+       default:
+               usage();
+               exit(-1);
+       }
+
+       if (bacmp(BDADDR_ANY, &bdaddr)) {
+               printf("Connecting to %s on channel %d\n", argv[1], channel);
+               fd = open_socket(&bdaddr, channel);
+       } else {
+               printf("Opening device %s\n", argv[1]);
+               fd = open_device(argv[1]);
+       }
+
+       if (fd < 0)
+               exit(-2);
+
+       at_command(fd, "ATZ\r\n", 10000);
+       at_command(fd, "AT+CPBS=\"ME\"\r\n", 10000);
+       at_command(fd, "AT+CPBR=1,100\r\n", 100000);
+
+       close(fd);
+
+       return 0;
+}
diff --git a/test/avtest.c b/test/avtest.c
new file mode 100644 (file)
index 0000000..168326f
--- /dev/null
@@ -0,0 +1,869 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2007-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009-2010  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+
+#define AVDTP_PKT_TYPE_SINGLE          0x00
+#define AVDTP_PKT_TYPE_START           0x01
+#define AVDTP_PKT_TYPE_CONTINUE                0x02
+#define AVDTP_PKT_TYPE_END             0x03
+
+#define AVDTP_MSG_TYPE_COMMAND         0x00
+#define AVDTP_MSG_TYPE_GEN_REJECT      0x01
+#define AVDTP_MSG_TYPE_ACCEPT          0x02
+#define AVDTP_MSG_TYPE_REJECT          0x03
+
+#define 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_SEP_TYPE_SOURCE          0x00
+#define AVDTP_SEP_TYPE_SINK            0x01
+
+#define AVDTP_MEDIA_TYPE_AUDIO         0x00
+#define AVDTP_MEDIA_TYPE_VIDEO         0x01
+#define AVDTP_MEDIA_TYPE_MULTIMEDIA    0x02
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_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 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 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 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
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_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 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 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 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
+
+#else
+#error "Unknown byte order"
+#endif
+
+#define AVCTP_COMMAND          0
+#define AVCTP_RESPONSE         1
+
+#define AVCTP_PACKET_SINGLE    0
+
+static const unsigned char media_transport[] = {
+               0x01,   /* Media transport category */
+               0x00,
+               0x07,   /* Media codec category */
+               0x06,
+               0x00,   /* Media type audio */
+               0x00,   /* Codec SBC */
+               0x22,   /* 44.1 kHz, stereo */
+               0x15,   /* 16 blocks, 8 subbands */
+               0x02,
+               0x33,
+};
+
+static int media_sock = -1;
+
+static void dump_avctp_header(struct avctp_header *hdr)
+{
+       printf("TL %d PT %d CR %d IPID %d PID 0x%04x\n", hdr->transaction,
+                       hdr->packet_type, hdr->cr, hdr->ipid, ntohs(hdr->pid));
+}
+
+static void dump_avdtp_header(struct avdtp_header *hdr)
+{
+       printf("TL %d PT %d MT %d SI %d\n", hdr->transaction,
+                       hdr->packet_type, hdr->message_type, hdr->signal_id);
+}
+
+static void dump_buffer(const unsigned char *buf, int len)
+{
+       int i;
+
+       for (i = 0; i < len; i++)
+               printf("%02x ", buf[i]);
+       printf("\n");
+}
+
+static void process_avdtp(int srv_sk, int sk, unsigned char reject,
+                                                               int fragment)
+{
+       unsigned char buf[672];
+       ssize_t len;
+
+       while (1) {
+               struct avdtp_header *hdr = (void *) buf;
+
+               len = read(sk, buf, sizeof(buf));
+               if (len <= 0) {
+                       perror("Read failed");
+                       break;
+               }
+
+               dump_buffer(buf, len);
+               dump_avdtp_header(hdr);
+
+               if (hdr->packet_type != AVDTP_PKT_TYPE_SINGLE) {
+                       fprintf(stderr, "Only single packets are supported\n");
+                       break;
+               }
+
+               if (hdr->message_type != AVDTP_MSG_TYPE_COMMAND) {
+                       fprintf(stderr, "Ignoring non-command messages\n");
+                       continue;
+               }
+
+               switch (hdr->signal_id) {
+               case AVDTP_DISCOVER:
+                       if (reject == AVDTP_DISCOVER) {
+                               hdr->message_type = AVDTP_MSG_TYPE_REJECT;
+                               buf[2] = 0x29; /* Unsupported configuration */
+                               printf("Rejecting discover command\n");
+                               len = write(sk, buf, 3);
+                       } else {
+                               struct seid_info *sei = (void *) (buf + 2);
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                               buf[2] = 0x00;
+                               buf[3] = 0x00;
+                               sei->seid = 0x01;
+                               sei->type = AVDTP_SEP_TYPE_SINK;
+                               sei->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+                               printf("Accepting discover command\n");
+                               len = write(sk, buf, 4);
+                       }
+                       break;
+
+               case AVDTP_GET_CAPABILITIES:
+                       if (reject == AVDTP_GET_CAPABILITIES) {
+                               hdr->message_type = AVDTP_MSG_TYPE_REJECT;
+                               buf[2] = 0x29; /* Unsupported configuration */
+                               printf("Rejecting get capabilties command\n");
+                               len = write(sk, buf, 3);
+                       } else if (fragment) {
+                               struct avdtp_start_header *start = (void *) buf;
+
+                               printf("Sending fragmented reply to getcap\n");
+
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+
+                               /* Start packet */
+                               hdr->packet_type = AVDTP_PKT_TYPE_START;
+                               start->signal_id = AVDTP_GET_CAPABILITIES;
+                               start->no_of_packets = 3;
+                               memcpy(&buf[3], media_transport,
+                                               sizeof(media_transport));
+                               len = write(sk, buf,
+                                               3 + sizeof(media_transport));
+
+                               /* Continue packet */
+                               hdr->packet_type = AVDTP_PKT_TYPE_CONTINUE;
+                               memcpy(&buf[1], media_transport,
+                                               sizeof(media_transport));
+                               len = write(sk, buf,
+                                               1 + sizeof(media_transport));
+
+                               /* End packet */
+                               hdr->packet_type = AVDTP_PKT_TYPE_END;
+                               memcpy(&buf[1], media_transport,
+                                               sizeof(media_transport));
+                               len = write(sk, buf,
+                                               1 + sizeof(media_transport));
+                       } else {
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                               memcpy(&buf[2], media_transport,
+                                               sizeof(media_transport));
+                               printf("Accepting get capabilities command\n");
+                               len = write(sk, buf,
+                                               2 + sizeof(media_transport));
+                       }
+                       break;
+
+               case AVDTP_SET_CONFIGURATION:
+                       if (reject == AVDTP_SET_CONFIGURATION) {
+                               hdr->message_type = AVDTP_MSG_TYPE_REJECT;
+                               buf[2] = buf[4];
+                               buf[3] = 0x13; /* SEP In Use */
+                               printf("Rejecting set configuration command\n");
+                               len = write(sk, buf, 4);
+                       } else {
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                               printf("Accepting set configuration command\n");
+                               len = write(sk, buf, 2);
+                       }
+                       break;
+
+               case AVDTP_GET_CONFIGURATION:
+                       if (reject == AVDTP_GET_CONFIGURATION) {
+                               hdr->message_type = AVDTP_MSG_TYPE_REJECT;
+                               buf[2] = 0x12; /* Bad ACP SEID */
+                               printf("Rejecting get configuration command\n");
+                               len = write(sk, buf, 3);
+                       } else {
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                               printf("Accepting get configuration command\n");
+                               len = write(sk, buf, 2);
+                       }
+                       break;
+
+               case AVDTP_OPEN:
+                       if (reject == AVDTP_OPEN) {
+                               hdr->message_type = AVDTP_MSG_TYPE_REJECT;
+                               buf[2] = 0x31; /* Bad State */
+                               printf("Rejecting open command\n");
+                               len = write(sk, buf, 3);
+                       } else {
+                               struct sockaddr_l2 addr;
+                               socklen_t optlen;
+
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                               printf("Accepting open command\n");
+                               len = write(sk, buf, 2);
+
+                               memset(&addr, 0, sizeof(addr));
+                               optlen = sizeof(addr);
+
+                               media_sock = accept(srv_sk,
+                                               (struct sockaddr *) &addr,
+                                                               &optlen);
+                               if (media_sock < 0) {
+                                       perror("Accept failed");
+                                       break;
+                               }
+                       }
+                       break;
+
+               case AVDTP_START:
+                       if (reject == AVDTP_ABORT)
+                               printf("Ignoring start to cause abort");
+                       else if (reject == AVDTP_START) {
+                               hdr->message_type = AVDTP_MSG_TYPE_REJECT;
+                               buf[3] = 0x31; /* Bad State */
+                               printf("Rejecting start command\n");
+                               len = write(sk, buf, 4);
+                       } else {
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                               printf("Accepting start command\n");
+                               len = write(sk, buf, 2);
+                       }
+                       break;
+
+               case AVDTP_CLOSE:
+                       if (reject == AVDTP_CLOSE) {
+                               hdr->message_type = AVDTP_MSG_TYPE_REJECT;
+                               buf[2] = 0x31; /* Bad State */
+                               printf("Rejecting close command\n");
+                               len = write(sk, buf, 3);
+                       } else {
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                               printf("Accepting close command\n");
+                               len = write(sk, buf, 2);
+                               if (media_sock >= 0) {
+                                       close(media_sock);
+                                       media_sock = -1;
+                               }
+                       }
+                       break;
+
+               case AVDTP_SUSPEND:
+                       if (reject == AVDTP_SUSPEND) {
+                               hdr->message_type = AVDTP_MSG_TYPE_REJECT;
+                               buf[3] = 0x31; /* Bad State */
+                               printf("Rejecting suspend command\n");
+                               len = write(sk, buf, 4);
+                       } else {
+                               hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                               printf("Accepting suspend command\n");
+                               len = write(sk, buf, 2);
+                       }
+                       break;
+
+               case AVDTP_ABORT:
+                       hdr->message_type = AVDTP_MSG_TYPE_ACCEPT;
+                       printf("Accepting abort command\n");
+                       len = write(sk, buf, 2);
+                       if (media_sock >= 0) {
+                               close(media_sock);
+                               media_sock = -1;
+                       }
+                       break;
+
+               default:
+                       buf[1] = 0x00;
+                       printf("Unknown command\n");
+                       len = write(sk, buf, 2);
+                       break;
+               }
+       }
+}
+
+static void process_avctp(int sk, int reject)
+{
+       unsigned char buf[672];
+       ssize_t len;
+
+       while (1) {
+               struct avctp_header *hdr = (void *) buf;
+
+               len = read(sk, buf, sizeof(buf));
+               if (len <= 0) {
+                       perror("Read failed");
+                       break;
+               }
+
+               dump_buffer(buf, len);
+
+               if (len >= AVCTP_HEADER_LENGTH)
+                       dump_avctp_header(hdr);
+       }
+}
+
+static int set_minimum_mtu(int sk)
+{
+       struct l2cap_options l2o;
+       socklen_t optlen;
+
+       memset(&l2o, 0, sizeof(l2o));
+       optlen = sizeof(l2o);
+
+       if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &optlen) < 0) {
+               perror("getsockopt");
+               return -1;
+       }
+
+       l2o.imtu = 48;
+       l2o.omtu = 48;
+
+       if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) {
+               perror("setsockopt");
+               return -1;
+       }
+
+       return 0;
+}
+
+static void do_listen(const bdaddr_t *src, unsigned char reject, int fragment)
+{
+       struct sockaddr_l2 addr;
+       socklen_t optlen;
+       int sk, nsk;
+
+       sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+       if (sk < 0) {
+               perror("Can't create socket");
+               return;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, src);
+       addr.l2_psm = htobs(25);
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("Can't bind socket");
+               goto error;
+       }
+
+       if (fragment)
+               set_minimum_mtu(sk);
+
+       if (listen(sk, 10)) {
+               perror("Can't listen on the socket");
+               goto error;
+       }
+
+       while (1) {
+               memset(&addr, 0, sizeof(addr));
+               optlen = sizeof(addr);
+
+               nsk = accept(sk, (struct sockaddr *) &addr, &optlen);
+               if (nsk < 0) {
+                       perror("Accept failed");
+                       continue;
+               }
+
+               process_avdtp(sk, nsk, reject, fragment);
+
+               if (media_sock >= 0) {
+                       close(media_sock);
+                       media_sock = -1;
+               }
+
+               close(nsk);
+       }
+
+error:
+       close(sk);
+}
+
+static int do_connect(const bdaddr_t *src, const bdaddr_t *dst, int avctp,
+                                                               int fragment)
+{
+       struct sockaddr_l2 addr;
+       int sk, err;
+
+       sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+       if (sk < 0) {
+               perror("Can't create socket");
+               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) {
+               perror("Can't bind socket");
+               goto error;
+       }
+
+       if (fragment)
+               set_minimum_mtu(sk);
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, dst);
+       addr.l2_psm = htobs(avctp ? 23 : 25);
+
+       err = connect(sk, (struct sockaddr *) &addr, sizeof(addr));
+       if (err < 0) {
+               perror("Unable to connect");
+               goto error;
+       }
+
+       return sk;
+
+error:
+       close(sk);
+       return -1;
+}
+
+static void do_avdtp_send(int sk, const bdaddr_t *src, const bdaddr_t *dst,
+                               unsigned char cmd, int invalid, int preconf)
+{
+       unsigned char buf[672];
+       struct avdtp_header *hdr = (void *) buf;
+       ssize_t len;
+
+       memset(buf, 0, sizeof(buf));
+
+       switch (cmd) {
+       case AVDTP_DISCOVER:
+               if (invalid)
+                       hdr->message_type = 0x01;
+               else
+                       hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_DISCOVER;
+               len = write(sk, buf, 2);
+               break;
+
+       case AVDTP_GET_CAPABILITIES:
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_GET_CAPABILITIES;
+               buf[2] = 1 << 2; /* SEID 1 */
+               len = write(sk, buf, invalid ? 2 : 3);
+               break;
+
+       case AVDTP_SET_CONFIGURATION:
+               if (preconf)
+                       do_avdtp_send(sk, src, dst, cmd, 0, 0);
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_SET_CONFIGURATION;
+               buf[2] = 1 << 2; /* ACP SEID */
+               buf[3] = 1 << 2; /* INT SEID */
+               memcpy(&buf[4], media_transport, sizeof(media_transport));
+               if (invalid)
+                       buf[5] = 0x01; /* LOSC != 0 */
+               len = write(sk, buf, 4 + sizeof(media_transport));
+               break;
+
+       case AVDTP_GET_CONFIGURATION:
+               if (preconf)
+                       do_avdtp_send(sk, src, dst, AVDTP_SET_CONFIGURATION, 0, 0);
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_GET_CONFIGURATION;
+               if (invalid)
+                       buf[2] = 13 << 2; /* Invalid ACP SEID */
+               else
+                       buf[2] = 1 << 2; /* Valid ACP SEID */
+               len = write(sk, buf, 3);
+               break;
+
+       case AVDTP_OPEN:
+               if (preconf)
+                       do_avdtp_send(sk, src, dst, AVDTP_SET_CONFIGURATION, 0, 0);
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_OPEN;
+               buf[2] = 1 << 2; /* ACP SEID */
+               len = write(sk, buf, 3);
+               break;
+
+       case AVDTP_START:
+               if (preconf)
+                       do_avdtp_send(sk, src, dst, AVDTP_SET_CONFIGURATION, 0, 0);
+               if (!invalid)
+                       do_avdtp_send(sk, src, dst, AVDTP_OPEN, 0, 0);
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_START;
+               buf[2] = 1 << 2; /* ACP SEID */
+               len = write(sk, buf, 3);
+               break;
+
+       case AVDTP_CLOSE:
+               if (preconf) {
+                       do_avdtp_send(sk, src, dst, AVDTP_SET_CONFIGURATION, 0, 0);
+                       do_avdtp_send(sk, src, dst, AVDTP_OPEN, 0, 0);
+               }
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_CLOSE;
+               if (invalid)
+                       buf[2] = 13 << 2; /* Invalid ACP SEID */
+               else
+                       buf[2] = 1 << 2; /* Valid ACP SEID */
+               len = write(sk, buf, 3);
+               break;
+
+       case AVDTP_SUSPEND:
+               if (invalid)
+                       do_avdtp_send(sk, src, dst, AVDTP_OPEN, 0, preconf);
+               else
+                       do_avdtp_send(sk, src, dst, AVDTP_START, 0, preconf);
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_SUSPEND;
+               buf[2] = 1 << 2; /* ACP SEID */
+               len = write(sk, buf, 3);
+               break;
+
+       case AVDTP_ABORT:
+               do_avdtp_send(sk, src, dst, AVDTP_OPEN, 0, 1);
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = AVDTP_ABORT;
+               buf[2] = 1 << 2; /* ACP SEID */
+               len = write(sk, buf, 3);
+               break;
+
+       default:
+               hdr->message_type = AVDTP_MSG_TYPE_COMMAND;
+               hdr->packet_type = AVDTP_PKT_TYPE_SINGLE;
+               hdr->signal_id = cmd;
+               len = write(sk, buf, 2);
+               break;
+       }
+
+       do {
+               len = read(sk, buf, sizeof(buf));
+
+               dump_buffer(buf, len);
+               dump_avdtp_header(hdr);
+       } while (len < 2 || (hdr->message_type != AVDTP_MSG_TYPE_ACCEPT &&
+                               hdr->message_type != AVDTP_MSG_TYPE_REJECT &&
+                               hdr->message_type != AVDTP_MSG_TYPE_GEN_REJECT));
+
+       if (cmd == AVDTP_OPEN && len >= 2 &&
+                               hdr->message_type == AVDTP_MSG_TYPE_ACCEPT)
+               media_sock = do_connect(src, dst, 0, 0);
+}
+
+static void do_avctp_send(int sk, int invalid)
+{
+       unsigned char buf[672];
+       struct avctp_header *hdr = (void *) buf;
+       unsigned char play_pressed[] = { 0x00, 0x48, 0x7c, 0x44, 0x00 };
+       ssize_t len;
+
+       memset(buf, 0, sizeof(buf));
+
+       hdr->packet_type = AVCTP_PACKET_SINGLE;
+       hdr->cr = AVCTP_COMMAND;
+       if (invalid)
+               hdr->pid = 0xffff;
+       else
+               hdr->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+       memcpy(&buf[AVCTP_HEADER_LENGTH], play_pressed, sizeof(play_pressed));
+
+       len = write(sk, buf, AVCTP_HEADER_LENGTH + sizeof(play_pressed));
+
+       len = read(sk, buf, sizeof(buf));
+
+       dump_buffer(buf, len);
+       if (len >= AVCTP_HEADER_LENGTH)
+               dump_avctp_header(hdr);
+}
+
+static void usage()
+{
+       printf("avtest - Audio/Video testing ver %s\n", VERSION);
+       printf("Usage:\n"
+               "\tavtest [options] [remote address]\n");
+       printf("Options:\n"
+               "\t--device <hcidev>    HCI device\n"
+               "\t--reject <command>   Reject command\n"
+               "\t--send <command>     Send command\n"
+               "\t--preconf            Configure stream before actual command\n"
+               "\t--wait <N>           Wait N seconds before exiting\n"
+               "\t--fragment           Use minimum MTU and fragmented messages\n"
+               "\t--invalid <command>  Send invalid command\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "device",     1, 0, 'i' },
+       { "reject",     1, 0, 'r' },
+       { "send",       1, 0, 's' },
+       { "invalid",    1, 0, 'f' },
+       { "preconf",    0, 0, 'c' },
+       { "fragment",   0, 0, 'F' },
+       { "avctp",      0, 0, 'C' },
+       { "wait",       1, 0, 'w' },
+       { 0, 0, 0, 0 }
+};
+
+static unsigned char parse_cmd(const char *arg)
+{
+       if (!strncmp(arg, "discov", 6))
+               return AVDTP_DISCOVER;
+       else if (!strncmp(arg, "capa", 4))
+               return AVDTP_GET_CAPABILITIES;
+       else if (!strncmp(arg, "getcapa", 7))
+               return AVDTP_GET_CAPABILITIES;
+       else if (!strncmp(arg, "setconf", 7))
+               return AVDTP_SET_CONFIGURATION;
+       else if (!strncmp(arg, "getconf", 7))
+               return AVDTP_GET_CONFIGURATION;
+       else if (!strncmp(arg, "open", 4))
+               return AVDTP_OPEN;
+       else if (!strncmp(arg, "start", 5))
+               return AVDTP_START;
+       else if (!strncmp(arg, "close", 5))
+               return AVDTP_CLOSE;
+       else if (!strncmp(arg, "suspend", 7))
+               return AVDTP_SUSPEND;
+       else if (!strncmp(arg, "abort", 7))
+               return AVDTP_ABORT;
+       else
+               return atoi(arg);
+}
+
+enum {
+       MODE_NONE, MODE_REJECT, MODE_SEND,
+};
+
+int main(int argc, char *argv[])
+{
+       unsigned char cmd = 0x00;
+       bdaddr_t src, dst;
+       int opt, mode = MODE_NONE, sk, invalid = 0, preconf = 0, fragment = 0;
+       int avctp = 0, wait_before_exit = 0;
+
+       bacpy(&src, BDADDR_ANY);
+       bacpy(&dst, BDADDR_ANY);
+
+       while ((opt = getopt_long(argc, argv, "+i:r:s:f:hcFCw:",
+                                               main_options, NULL)) != EOF) {
+               switch (opt) {
+               case 'i':
+                       if (!strncmp(optarg, "hci", 3))
+                               hci_devba(atoi(optarg + 3), &src);
+                       else
+                               str2ba(optarg, &src);
+                       break;
+
+               case 'r':
+                       mode = MODE_REJECT;
+                       cmd = parse_cmd(optarg);
+                       break;
+
+               case 'f':
+                       invalid = 1;
+                       /* Intentionally missing break */
+
+               case 's':
+                       mode = MODE_SEND;
+                       cmd = parse_cmd(optarg);
+                       break;
+
+               case 'c':
+                       preconf = 1;
+                       break;
+
+               case 'F':
+                       fragment = 1;
+                       break;
+
+               case 'C':
+                       avctp = 1;
+                       break;
+
+               case 'w':
+                       wait_before_exit = atoi(optarg);
+                       break;
+
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       if (argv[optind])
+               str2ba(argv[optind], &dst);
+
+       if (avctp) {
+               avctp = mode;
+               mode = MODE_SEND;
+       }
+
+       switch (mode) {
+       case MODE_REJECT:
+               do_listen(&src, cmd, fragment);
+               break;
+       case MODE_SEND:
+               sk = do_connect(&src, &dst, avctp, fragment);
+               if (sk < 0)
+                       exit(1);
+               if (avctp) {
+                       if (avctp == MODE_SEND)
+                               do_avctp_send(sk, invalid);
+                       else
+                               process_avctp(sk, cmd);
+               } else
+                       do_avdtp_send(sk, &src, &dst, cmd, invalid, preconf);
+               if (wait_before_exit) {
+                       printf("Waiting %d seconds before exiting\n", wait_before_exit);
+                       sleep(wait_before_exit);
+               }
+               if (media_sock >= 0)
+                       close(media_sock);
+               close(sk);
+               break;
+       default:
+               fprintf(stderr, "No operating mode specified!\n");
+               exit(1);
+       }
+
+       return 0;
+}
diff --git a/test/bdaddr.8 b/test/bdaddr.8
new file mode 100644 (file)
index 0000000..88345f8
--- /dev/null
@@ -0,0 +1,68 @@
+.TH BDADDR 8 "Sep 27 2005" BlueZ "Linux System Administration"
+.SH NAME
+bdaddr \- Utility for changing the Bluetooth device address
+.SH SYNOPSIS
+.B bdaddr
+.br
+.B bdaddr -h
+.br
+.B bdaddr [-i <dev>] [-r] [-t] [new bdaddr]
+
+.SH DESCRIPTION
+.LP
+.B
+bdaddr
+is used to query or set the local Bluetooth device address (BD_ADDR). If run
+with no arguments,
+.B
+bdaddr
+prints the chip manufacturer's name, and the current BD_ADDR. If the IEEE OUI
+index file "oui.txt" is installed on the system, the BD_ADDR owner will be
+displayed. If the optional [new bdaddr] argument is given, the device will be
+reprogrammed with that address. This can either be permanent or temporary, as
+specified by the -t flag. In both cases, the device must be reset before the
+new address will become active. This can be done with a 'soft' reset by
+specifying the -r flag, or a 'hard' reset by removing and replugging the
+device. A 'hard' reset will cause the address to revert to the current
+non-volatile value.
+.PP
+.B
+bdaddr
+uses manufacturer specific commands to set the address, and is therefore
+device specific. For this reason, not all devices are supported, and not all
+options are supported on all devices.
+Current supported manufacturers are:
+.B Ericsson, Cambridge Silicon Radio (CSR), Texas Instruments (TI), Zeevo
+and
+.B ST Microelectronics (ST)
+
+.SH OPTIONS
+.TP
+.BI -h
+Gives a list of possible commands.
+.TP
+.BI -i\ <dev>
+Specify a particular device to operate on. If not specified, default is the
+first available device.
+.TP
+.BI -r
+Reset device and make new BD_ADDR active.
+.B
+CSR
+devices only.
+.TP
+.BI -t
+Temporary change. Do not write to non-volatile memory.
+.B
+CSR
+devices only.
+.SH FILES
+.TP
+.I
+/usr/share/misc/oui.txt
+IEEE Organizationally Unique Identifier master file.
+Manually update from: http://standards.ieee.org/regauth/oui/oui.txt
+.SH AUTHORS
+Written by Marcel Holtmann <marcel@holtmann.org>,
+man page by Adam Laurie <adam@algroup.co.uk>
+.PP
diff --git a/test/bdaddr.c b/test/bdaddr.c
new file mode 100644 (file)
index 0000000..683b3b9
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "oui.h"
+
+static int transient = 0;
+
+static int generic_reset_device(int dd)
+{
+       bdaddr_t bdaddr;
+       int err;
+
+       err = hci_send_cmd(dd, 0x03, 0x0003, 0, NULL);
+       if (err < 0)
+               return err;
+
+       return hci_read_bd_addr(dd, &bdaddr, 10000);
+}
+
+#define OCF_ERICSSON_WRITE_BD_ADDR     0x000d
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) ericsson_write_bd_addr_cp;
+#define ERICSSON_WRITE_BD_ADDR_CP_SIZE 6
+
+static int ericsson_write_bd_addr(int dd, bdaddr_t *bdaddr)
+{
+       struct hci_request rq;
+       ericsson_write_bd_addr_cp cp;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = OCF_ERICSSON_WRITE_BD_ADDR;
+       rq.cparam = &cp;
+       rq.clen   = ERICSSON_WRITE_BD_ADDR_CP_SIZE;
+       rq.rparam = NULL;
+       rq.rlen   = 0;
+
+       if (hci_send_req(dd, &rq, 1000) < 0)
+               return -1;
+
+       return 0;
+}
+
+#define OCF_ERICSSON_STORE_IN_FLASH    0x0022
+typedef struct {
+       uint8_t         user_id;
+       uint8_t         flash_length;
+       uint8_t         flash_data[253];
+} __attribute__ ((packed)) ericsson_store_in_flash_cp;
+#define ERICSSON_STORE_IN_FLASH_CP_SIZE 255
+
+static int ericsson_store_in_flash(int dd, uint8_t user_id, uint8_t flash_length, uint8_t *flash_data)
+{
+       struct hci_request rq;
+       ericsson_store_in_flash_cp cp;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.user_id = user_id;
+       cp.flash_length = flash_length;
+       if (flash_length > 0)
+               memcpy(cp.flash_data, flash_data, flash_length);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = OCF_ERICSSON_STORE_IN_FLASH;
+       rq.cparam = &cp;
+       rq.clen   = ERICSSON_STORE_IN_FLASH_CP_SIZE;
+       rq.rparam = NULL;
+       rq.rlen   = 0;
+
+       if (hci_send_req(dd, &rq, 1000) < 0)
+               return -1;
+
+       return 0;
+}
+
+static int csr_write_bd_addr(int dd, bdaddr_t *bdaddr)
+{
+       unsigned char cmd[] = { 0x02, 0x00, 0x0c, 0x00, 0x11, 0x47, 0x03, 0x70,
+                               0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
+                               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       if (transient)
+               cmd[14] = 0x08;
+
+       cmd[16] = bdaddr->b[2];
+       cmd[17] = 0x00;
+       cmd[18] = bdaddr->b[0];
+       cmd[19] = bdaddr->b[1];
+       cmd[20] = bdaddr->b[3];
+       cmd[21] = 0x00;
+       cmd[22] = bdaddr->b[4];
+       cmd[23] = bdaddr->b[5];
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+static int csr_reset_device(int dd)
+{
+       unsigned char cmd[] = { 0x02, 0x00, 0x09, 0x00,
+                               0x00, 0x00, 0x01, 0x40, 0x00, 0x00,
+                               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       if (transient)
+               cmd[6] = 0x02;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       return 0;
+}
+
+#define OCF_TI_WRITE_BD_ADDR           0x0006
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) ti_write_bd_addr_cp;
+#define TI_WRITE_BD_ADDR_CP_SIZE 6
+
+static int ti_write_bd_addr(int dd, bdaddr_t *bdaddr)
+{
+       struct hci_request rq;
+       ti_write_bd_addr_cp cp;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = OCF_TI_WRITE_BD_ADDR;
+       rq.cparam = &cp;
+       rq.clen   = TI_WRITE_BD_ADDR_CP_SIZE;
+       rq.rparam = NULL;
+       rq.rlen   = 0;
+
+       if (hci_send_req(dd, &rq, 1000) < 0)
+               return -1;
+
+       return 0;
+}
+
+#define OCF_BCM_WRITE_BD_ADDR          0x0001
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) bcm_write_bd_addr_cp;
+#define BCM_WRITE_BD_ADDR_CP_SIZE 6
+
+static int bcm_write_bd_addr(int dd, bdaddr_t *bdaddr)
+{
+       struct hci_request rq;
+       bcm_write_bd_addr_cp cp;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = OCF_BCM_WRITE_BD_ADDR;
+       rq.cparam = &cp;
+       rq.clen   = BCM_WRITE_BD_ADDR_CP_SIZE;
+       rq.rparam = NULL;
+       rq.rlen   = 0;
+
+       if (hci_send_req(dd, &rq, 1000) < 0)
+               return -1;
+
+       return 0;
+}
+
+#define OCF_ZEEVO_WRITE_BD_ADDR                0x0001
+typedef struct {
+       bdaddr_t        bdaddr;
+} __attribute__ ((packed)) zeevo_write_bd_addr_cp;
+#define ZEEVO_WRITE_BD_ADDR_CP_SIZE 6
+
+static int zeevo_write_bd_addr(int dd, bdaddr_t *bdaddr)
+{
+       struct hci_request rq;
+       zeevo_write_bd_addr_cp cp;
+
+       memset(&cp, 0, sizeof(cp));
+       bacpy(&cp.bdaddr, bdaddr);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = OCF_ZEEVO_WRITE_BD_ADDR;
+       rq.cparam = &cp;
+       rq.clen   = ZEEVO_WRITE_BD_ADDR_CP_SIZE;
+       rq.rparam = NULL;
+       rq.rlen   = 0;
+
+       if (hci_send_req(dd, &rq, 1000) < 0)
+               return -1;
+
+       return 0;
+}
+
+static int st_write_bd_addr(int dd, bdaddr_t *bdaddr)
+{
+       return ericsson_store_in_flash(dd, 0xfe, 6, (uint8_t *) bdaddr);
+}
+
+static struct {
+       uint16_t compid;
+       int (*write_bd_addr)(int dd, bdaddr_t *bdaddr);
+       int (*reset_device)(int dd);
+} vendor[] = {
+       { 0,            ericsson_write_bd_addr, NULL                    },
+       { 10,           csr_write_bd_addr,      csr_reset_device        },
+       { 13,           ti_write_bd_addr,       NULL                    },
+       { 15,           bcm_write_bd_addr,      generic_reset_device    },
+       { 18,           zeevo_write_bd_addr,    NULL                    },
+       { 48,           st_write_bd_addr,       generic_reset_device    },
+       { 57,           ericsson_write_bd_addr, generic_reset_device    },
+       { 65535,        NULL,                   NULL                    },
+};
+
+static void usage(void)
+{
+       printf("bdaddr - Utility for changing the Bluetooth device address\n\n");
+       printf("Usage:\n"
+               "\tbdaddr [-i <dev>] [-r] [-t] [new bdaddr]\n");
+}
+
+static struct option main_options[] = {
+       { "device",     1, 0, 'i' },
+       { "reset",      0, 0, 'r' },
+       { "transient",  0, 0, 't' },
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       struct hci_dev_info di;
+       struct hci_version ver;
+       bdaddr_t bdaddr;
+       char addr[18], oui[9], *comp;
+       int i, dd, opt, dev = 0, reset = 0;
+
+       bacpy(&bdaddr, BDADDR_ANY);
+
+       while ((opt=getopt_long(argc, argv, "+i:rth", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'i':
+                       dev = hci_devid(optarg);
+                       if (dev < 0) {
+                               perror("Invalid device");
+                               exit(1);
+                       }
+                       break;
+
+               case 'r':
+                       reset = 1;
+                       break;
+
+               case 't':
+                       transient = 1;
+                       break;
+
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       dd = hci_open_dev(dev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (hci_devinfo(dev, &di) < 0) {
+               fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               exit(1);
+       }
+
+       if (hci_read_local_version(dd, &ver, 1000) < 0) {
+               fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               exit(1);
+       }
+
+       if (!bacmp(&di.bdaddr, BDADDR_ANY)) {
+               if (hci_read_bd_addr(dd, &bdaddr, 1000) < 0) {
+                       fprintf(stderr, "Can't read address for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+                       hci_close_dev(dd);
+                       exit(1);
+               }
+       } else
+               bacpy(&bdaddr, &di.bdaddr);
+
+       printf("Manufacturer:   %s (%d)\n",
+                       bt_compidtostr(ver.manufacturer), ver.manufacturer);
+
+       ba2oui(&bdaddr, oui);
+       comp = ouitocomp(oui);
+
+       ba2str(&bdaddr, addr);
+       printf("Device address: %s", addr);
+
+       if (comp) {
+               printf(" (%s)\n", comp);
+               free(comp);
+       } else
+               printf("\n");
+
+       if (argc < 1) {
+               hci_close_dev(dd);
+               exit(0);
+       }
+
+       str2ba(argv[0], &bdaddr);
+       if (!bacmp(&bdaddr, BDADDR_ANY)) {
+               hci_close_dev(dd);
+               exit(0);
+       }
+
+       for (i = 0; vendor[i].compid != 65535; i++)
+               if (ver.manufacturer == vendor[i].compid) {
+                       ba2oui(&bdaddr, oui);
+                       comp = ouitocomp(oui);
+
+                       ba2str(&bdaddr, addr);
+                       printf("New BD address: %s", addr);
+
+                       if (comp) {
+                               printf(" (%s)\n\n", comp);
+                               free(comp);
+                       } else
+                               printf("\n\n");
+
+
+                       if (vendor[i].write_bd_addr(dd, &bdaddr) < 0) {
+                               fprintf(stderr, "Can't write new address\n");
+                               hci_close_dev(dd);
+                               exit(1);
+                       }
+
+                       printf("Address changed - ");
+
+                       if (reset && vendor[i].reset_device) {
+                               if (vendor[i].reset_device(dd) < 0) {
+                                       printf("Reset device manually\n");
+                               } else {
+                                       ioctl(dd, HCIDEVRESET, dev);
+                                       printf("Device reset successully\n");
+                               }
+                       } else {
+                               printf("Reset device now\n");
+                       }
+
+                       //ioctl(dd, HCIDEVRESET, dev);
+                       //ioctl(dd, HCIDEVDOWN, dev);
+                       //ioctl(dd, HCIDEVUP, dev);
+
+                       hci_close_dev(dd);
+                       exit(0);
+               }
+
+       hci_close_dev(dd);
+
+       printf("\n");
+       fprintf(stderr, "Unsupported manufacturer\n");
+
+       exit(1);
+}
diff --git a/test/btiotest.c b/test/btiotest.c
new file mode 100644 (file)
index 0000000..c02a25a
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009-2010  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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+
+#include <glib.h>
+
+#include "btio.h"
+
+#define DEFAULT_ACCEPT_TIMEOUT 2
+
+struct io_data {
+       guint ref;
+       GIOChannel *io;
+       BtIOType type;
+       gint reject;
+       gint disconn;
+       gint accept;
+};
+
+static void io_data_unref(struct io_data *data)
+{
+       data->ref--;
+
+       if (data->ref)
+               return;
+
+       if (data->io)
+               g_io_channel_unref(data->io);
+
+       g_free(data);
+}
+
+static struct io_data *io_data_ref(struct io_data *data)
+{
+       data->ref++;
+       return data;
+}
+
+static struct io_data *io_data_new(GIOChannel *io, BtIOType type, gint reject,
+                                               gint disconn, gint accept)
+{
+       struct io_data *data;
+
+       data = g_new0(struct io_data, 1);
+       data->io = io;
+       data->type = type;
+       data->reject = reject;
+       data->disconn = disconn;
+       data->accept = accept;
+
+       return io_data_ref(data);
+}
+
+static gboolean io_watch(GIOChannel *io, GIOCondition cond, gpointer user_data)
+{
+       printf("Disconnected\n");
+       return FALSE;
+}
+
+static gboolean disconn_timeout(gpointer user_data)
+{
+       struct io_data *data = user_data;
+
+       printf("Disconnecting\n");
+
+       g_io_channel_shutdown(data->io, TRUE, NULL);
+
+       return FALSE;
+}
+
+static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+       struct io_data *data = user_data;
+       GIOCondition cond;
+       char addr[18];
+       uint16_t handle;
+       uint8_t cls[3];
+
+       if (err) {
+               printf("Connecting failed: %s\n", err->message);
+               return;
+       }
+
+       if (!bt_io_get(io, data->type, &err,
+                       BT_IO_OPT_DEST, addr,
+                       BT_IO_OPT_HANDLE, &handle,
+                       BT_IO_OPT_CLASS, cls,
+                       BT_IO_OPT_INVALID)) {
+               printf("Unable to get destination address: %s\n",
+                                                               err->message);
+               g_clear_error(&err);
+               strcpy(addr, "(unknown)");
+       }
+
+       printf("Successfully connected to %s. handle=%u, class=%02x%02x%02x\n",
+                       addr, handle, cls[0], cls[1], cls[2]);
+
+       if (data->type == BT_IO_L2CAP || data->type == BT_IO_SCO) {
+               uint16_t omtu, imtu;
+
+               if (!bt_io_get(io, data->type, &err,
+                                       BT_IO_OPT_OMTU, &omtu,
+                                       BT_IO_OPT_IMTU, &imtu,
+                                       BT_IO_OPT_INVALID)) {
+                       printf("Unable to get L2CAP MTU sizes: %s\n",
+                                                               err->message);
+                       g_clear_error(&err);
+               } else
+                       printf("imtu=%u, omtu=%u\n", imtu, omtu);
+       }
+
+       if (data->disconn == 0) {
+               g_io_channel_shutdown(io, TRUE, NULL);
+               printf("Disconnected\n");
+               return;
+       }
+
+       if (data->io == NULL)
+               data->io = g_io_channel_ref(io);
+
+       if (data->disconn > 0) {
+               io_data_ref(data);
+               g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, data->disconn,
+                                       disconn_timeout, data,
+                                       (GDestroyNotify) io_data_unref);
+       }
+
+
+       io_data_ref(data);
+       cond = G_IO_NVAL | G_IO_HUP | G_IO_ERR;
+       g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, io_watch, data,
+                                       (GDestroyNotify) io_data_unref);
+}
+
+static gboolean confirm_timeout(gpointer user_data)
+{
+       struct io_data *data = user_data;
+
+       if (data->reject >= 0) {
+               printf("Rejecting connection\n");
+               g_io_channel_shutdown(data->io, TRUE, NULL);
+               return FALSE;
+       }
+
+       printf("Accepting connection\n");
+
+       io_data_ref(data);
+
+       if (!bt_io_accept(data->io, connect_cb, data,
+                               (GDestroyNotify) io_data_unref, NULL)) {
+               printf("bt_io_accept() failed\n");
+               io_data_unref(data);
+       }
+
+       return FALSE;
+}
+
+static void confirm_cb(GIOChannel *io, gpointer user_data)
+{
+       char addr[18];
+       struct io_data *data = user_data;
+       GError *err = NULL;
+
+       if (!bt_io_get(io, data->type, &err, BT_IO_OPT_DEST, addr,
+                                                       BT_IO_OPT_INVALID)) {
+               printf("bt_io_get(OPT_DEST): %s\n", err->message);
+               g_clear_error(&err);
+       } else
+               printf("Got confirmation request for %s\n", addr);
+
+       if (data->accept < 0 && data->reject < 0)
+               return;
+
+       if (data->reject == 0) {
+               printf("Rejecting connection\n");
+               g_io_channel_shutdown(io, TRUE, NULL);
+               return;
+       }
+
+       data->io = g_io_channel_ref(io);
+       io_data_ref(data);
+
+       if (data->accept == 0) {
+               if (!bt_io_accept(io, connect_cb, data,
+                                       (GDestroyNotify) io_data_unref,
+                                       &err)) {
+                       printf("bt_io_accept() failed: %s\n", err->message);
+                       g_clear_error(&err);
+                       io_data_unref(data);
+                       return;
+               }
+       } else {
+               gint seconds = (data->reject > 0) ?
+                                               data->reject : data->accept;
+               g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, seconds,
+                                       confirm_timeout, data,
+                                       (GDestroyNotify) io_data_unref);
+       }
+}
+
+static void l2cap_connect(const char *src, const char *dst, uint16_t psm,
+                                               gint disconn, gint sec)
+{
+       struct io_data *data;
+       GError *err = NULL;
+
+       printf("Connecting to %s L2CAP PSM %u\n", dst, psm);
+
+       data = io_data_new(NULL, BT_IO_L2CAP, -1, disconn, -1);
+
+       if (src)
+               data->io = bt_io_connect(BT_IO_L2CAP, connect_cb, data,
+                                               (GDestroyNotify) io_data_unref,
+                                               &err,
+                                               BT_IO_OPT_SOURCE, src,
+                                               BT_IO_OPT_DEST, dst,
+                                               BT_IO_OPT_PSM, psm,
+                                               BT_IO_OPT_SEC_LEVEL, sec,
+                                               BT_IO_OPT_INVALID);
+       else
+               data->io = bt_io_connect(BT_IO_L2CAP, connect_cb, data,
+                                               (GDestroyNotify) io_data_unref,
+                                               &err,
+                                               BT_IO_OPT_DEST, dst,
+                                               BT_IO_OPT_PSM, psm,
+                                               BT_IO_OPT_SEC_LEVEL, sec,
+                                               BT_IO_OPT_INVALID);
+
+       if (!data->io) {
+               printf("Connecting to %s failed: %s\n", dst, err->message);
+               g_error_free(err);
+               exit(EXIT_FAILURE);
+       }
+}
+
+static void l2cap_listen(const char *src, uint16_t psm, gint defer,
+                               gint reject, gint disconn, gint accept,
+                               gint sec, gboolean master)
+{
+       struct io_data *data;
+       BtIOConnect conn;
+       BtIOConfirm cfm;
+       GIOChannel *l2_srv;
+       GError *err = NULL;
+
+       if (defer) {
+               conn = NULL;
+               cfm = confirm_cb;
+       } else {
+               conn = connect_cb;
+               cfm = NULL;
+       }
+
+       printf("Listening on L2CAP PSM %u\n", psm);
+
+       data = io_data_new(NULL, BT_IO_L2CAP, reject, disconn, accept);
+
+       if (src)
+               l2_srv = bt_io_listen(BT_IO_L2CAP, conn, cfm,
+                                       data, (GDestroyNotify) io_data_unref,
+                                       &err,
+                                       BT_IO_OPT_SOURCE, src,
+                                       BT_IO_OPT_PSM, psm,
+                                       BT_IO_OPT_SEC_LEVEL, sec,
+                                       BT_IO_OPT_MASTER, master,
+                                       BT_IO_OPT_INVALID);
+       else
+               l2_srv = bt_io_listen(BT_IO_L2CAP, conn, cfm,
+                                       data, (GDestroyNotify) io_data_unref,
+                                       &err,
+                                       BT_IO_OPT_PSM, psm,
+                                       BT_IO_OPT_SEC_LEVEL, sec,
+                                       BT_IO_OPT_MASTER, master,
+                                       BT_IO_OPT_INVALID);
+
+       if (!l2_srv) {
+               printf("Listening failed: %s\n", err->message);
+               g_error_free(err);
+               exit(EXIT_FAILURE);
+       }
+
+       g_io_channel_unref(l2_srv);
+}
+
+static void rfcomm_connect(const char *src, const char *dst, uint8_t ch,
+                                               gint disconn, gint sec)
+{
+       struct io_data *data;
+       GError *err = NULL;
+
+       printf("Connecting to %s RFCOMM channel %u\n", dst, ch);
+
+       data = io_data_new(NULL, BT_IO_RFCOMM, -1, disconn, -1);
+
+       if (src)
+               data->io = bt_io_connect(BT_IO_RFCOMM, connect_cb, data,
+                                               (GDestroyNotify) io_data_unref,
+                                               &err,
+                                               BT_IO_OPT_SOURCE, src,
+                                               BT_IO_OPT_DEST, dst,
+                                               BT_IO_OPT_CHANNEL, ch,
+                                               BT_IO_OPT_SEC_LEVEL, sec,
+                                               BT_IO_OPT_INVALID);
+       else
+               data->io = bt_io_connect(BT_IO_RFCOMM, connect_cb, data,
+                                               (GDestroyNotify) io_data_unref,
+                                               &err,
+                                               BT_IO_OPT_DEST, dst,
+                                               BT_IO_OPT_CHANNEL, ch,
+                                               BT_IO_OPT_SEC_LEVEL, sec,
+                                               BT_IO_OPT_INVALID);
+
+       if (!data->io) {
+               printf("Connecting to %s failed: %s\n", dst, err->message);
+               g_error_free(err);
+               exit(EXIT_FAILURE);
+       }
+}
+
+static void rfcomm_listen(const char *src, uint8_t ch, gboolean defer,
+                               gint reject, gint disconn, gint accept,
+                               gint sec, gboolean master)
+{
+       struct io_data *data;
+       BtIOConnect conn;
+       BtIOConfirm cfm;
+       GIOChannel *rc_srv;
+       GError *err = NULL;
+
+       if (defer) {
+               conn = NULL;
+               cfm = confirm_cb;
+       } else {
+               conn = connect_cb;
+               cfm = NULL;
+       }
+
+       data = io_data_new(NULL, BT_IO_RFCOMM, reject, disconn, accept);
+
+       if (src)
+               rc_srv = bt_io_listen(BT_IO_RFCOMM, conn, cfm,
+                                       data, (GDestroyNotify) io_data_unref,
+                                       &err,
+                                       BT_IO_OPT_SOURCE, src,
+                                       BT_IO_OPT_CHANNEL, ch,
+                                       BT_IO_OPT_SEC_LEVEL, sec,
+                                       BT_IO_OPT_MASTER, master,
+                                       BT_IO_OPT_INVALID);
+       else
+               rc_srv = bt_io_listen(BT_IO_RFCOMM, conn, cfm,
+                                       data, (GDestroyNotify) io_data_unref,
+                                       &err,
+                                       BT_IO_OPT_CHANNEL, ch,
+                                       BT_IO_OPT_SEC_LEVEL, sec,
+                                       BT_IO_OPT_MASTER, master,
+                                       BT_IO_OPT_INVALID);
+
+       if (!rc_srv) {
+               printf("Listening failed: %s\n", err->message);
+               g_error_free(err);
+               exit(EXIT_FAILURE);
+       }
+
+       bt_io_get(rc_srv, BT_IO_RFCOMM, &err,
+                       BT_IO_OPT_CHANNEL, &ch,
+                       BT_IO_OPT_INVALID);
+
+       printf("Listening on RFCOMM channel %u\n", ch);
+
+       g_io_channel_unref(rc_srv);
+}
+
+static void sco_connect(const char *src, const char *dst, gint disconn)
+{
+       struct io_data *data;
+       GError *err = NULL;
+
+       printf("Connecting SCO to %s\n", dst);
+
+       data = io_data_new(NULL, BT_IO_SCO, -1, disconn, -1);
+
+       if (src)
+               data->io = bt_io_connect(BT_IO_SCO, connect_cb, data,
+                                               (GDestroyNotify) io_data_unref,
+                                               &err,
+                                               BT_IO_OPT_SOURCE, src,
+                                               BT_IO_OPT_DEST, dst,
+                                               BT_IO_OPT_INVALID);
+       else
+               data->io = bt_io_connect(BT_IO_SCO, connect_cb, data,
+                                               (GDestroyNotify) io_data_unref,
+                                               &err,
+                                               BT_IO_OPT_DEST, dst,
+                                               BT_IO_OPT_INVALID);
+
+       if (!data->io) {
+               printf("Connecting to %s failed: %s\n", dst, err->message);
+               g_error_free(err);
+               exit(EXIT_FAILURE);
+       }
+}
+
+static void sco_listen(const char *src, gint disconn)
+{
+       struct io_data *data;
+       GIOChannel *sco_srv;
+       GError *err = NULL;
+
+       printf("Listening for SCO connections\n");
+
+       data = io_data_new(NULL, BT_IO_SCO, -1, disconn, -1);
+
+       if (src)
+               sco_srv = bt_io_listen(BT_IO_SCO, connect_cb, NULL,
+                                       data, (GDestroyNotify) io_data_unref,
+                                       &err,
+                                       BT_IO_OPT_SOURCE, src,
+                                       BT_IO_OPT_INVALID);
+       else
+               sco_srv = bt_io_listen(BT_IO_SCO, connect_cb, NULL,
+                                       data, (GDestroyNotify) io_data_unref,
+                                       &err, BT_IO_OPT_INVALID);
+
+       if (!sco_srv) {
+               printf("Listening failed: %s\n", err->message);
+               g_error_free(err);
+               exit(EXIT_FAILURE);
+       }
+
+       g_io_channel_unref(sco_srv);
+}
+
+static gint opt_channel = -1;
+static gint opt_psm = 0;
+static gboolean opt_sco = FALSE;
+static gboolean opt_defer = FALSE;
+static char *opt_dev = NULL;
+static gint opt_reject = -1;
+static gint opt_disconn = -1;
+static gint opt_accept = DEFAULT_ACCEPT_TIMEOUT;
+static gint opt_sec = 0;
+static gboolean opt_master = FALSE;
+
+static GMainLoop *main_loop;
+
+static GOptionEntry options[] = {
+       { "channel", 'c', 0, G_OPTION_ARG_INT, &opt_channel,
+                               "RFCOMM channel" },
+       { "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm,
+                               "L2CAP PSM" },
+       { "sco", 's', 0, G_OPTION_ARG_NONE, &opt_sco,
+                               "Use SCO" },
+       { "defer", 'd', 0, G_OPTION_ARG_NONE, &opt_defer,
+                               "Use DEFER_SETUP for incoming connections" },
+       { "sec-level", 'S', 0, G_OPTION_ARG_INT, &opt_sec,
+                               "Security level" },
+       { "dev", 'i', 0, G_OPTION_ARG_STRING, &opt_dev,
+                               "Which HCI device to use" },
+       { "reject", 'r', 0, G_OPTION_ARG_INT, &opt_reject,
+                               "Reject connection after N seconds" },
+       { "disconnect", 'D', 0, G_OPTION_ARG_INT, &opt_disconn,
+                               "Disconnect connection after N seconds" },
+       { "accept", 'a', 0, G_OPTION_ARG_INT, &opt_accept,
+                               "Accept connection after N seconds" },
+       { "master", 'm', 0, G_OPTION_ARG_NONE, &opt_master,
+                               "Master role switch (incoming connections)" },
+       { NULL },
+};
+
+static void sig_term(int sig)
+{
+       g_main_loop_quit(main_loop);
+}
+
+int main(int argc, char *argv[])
+{
+       GOptionContext *context;
+
+       context = g_option_context_new(NULL);
+       g_option_context_add_main_entries(context, options, NULL);
+
+       if (!g_option_context_parse(context, &argc, &argv, NULL))
+               exit(EXIT_FAILURE);
+
+       g_option_context_free(context);
+
+       printf("accept=%d, reject=%d, discon=%d, defer=%d, sec=%d\n",
+               opt_accept, opt_reject, opt_disconn, opt_defer, opt_sec);
+
+       if (opt_psm) {
+               if (argc > 1)
+                       l2cap_connect(opt_dev, argv[1], opt_psm,
+                                                       opt_disconn, opt_sec);
+               else
+                       l2cap_listen(opt_dev, opt_psm, opt_defer, opt_reject,
+                                       opt_disconn, opt_accept, opt_sec,
+                                       opt_master);
+       }
+
+       if (opt_channel != -1) {
+               if (argc > 1)
+                       rfcomm_connect(opt_dev, argv[1], opt_channel,
+                                                       opt_disconn, opt_sec);
+               else
+                       rfcomm_listen(opt_dev, opt_channel, opt_defer,
+                                       opt_reject, opt_disconn, opt_accept,
+                                       opt_sec, opt_master);
+       }
+
+       if (opt_sco) {
+               if (argc > 1)
+                       sco_connect(opt_dev, argv[1], opt_disconn);
+               else
+                       sco_listen(opt_dev, opt_disconn);
+       }
+
+       signal(SIGTERM, sig_term);
+       signal(SIGINT, sig_term);
+
+       main_loop = g_main_loop_new(NULL, FALSE);
+
+       g_main_loop_run(main_loop);
+
+       g_main_loop_unref(main_loop);
+
+       printf("Exiting\n");
+
+       exit(EXIT_SUCCESS);
+}
diff --git a/test/dbusdef.py b/test/dbusdef.py
new file mode 100644 (file)
index 0000000..5af6153
--- /dev/null
@@ -0,0 +1,16 @@
+import dbus
+
+bus = dbus.SystemBus()
+
+
+dummy = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.Introspectable')
+
+#print dummy.Introspect()
+
+
+manager = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.bluez.Manager')
+
+try:
+       adapter = dbus.Interface(bus.get_object('org.bluez', manager.DefaultAdapter()), 'org.bluez.Adapter')
+except:
+       pass
diff --git a/test/gaptest.c b/test/gaptest.c
new file mode 100644 (file)
index 0000000..3e9f534
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2007-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <dbus/dbus.h>
+
+#define BLUEZ_SERVICE  "org.bluez"
+
+#define MANAGER_PATH   "/"
+#define MANAGER_INTF   BLUEZ_SERVICE ".Manager"
+#define ADAPTER_INTF   BLUEZ_SERVICE ".Adapter"
+
+static char *get_adapter(DBusConnection *conn)
+{
+       DBusMessage *message, *reply;
+       DBusError error;
+       const char *path;
+       char *result = NULL;
+
+       message = dbus_message_new_method_call(BLUEZ_SERVICE, MANAGER_PATH,
+                                       MANAGER_INTF, "DefaultAdapter");
+       if (!message)
+               return NULL;
+
+       dbus_error_init(&error);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, &error);
+
+       dbus_message_unref(message);
+
+       if (!reply) {
+               if (dbus_error_is_set(&error) == TRUE) {
+                       fprintf(stderr, "%s\n", error.message);
+                       dbus_error_free(&error);
+               } else
+                       fprintf(stderr, "Failed to set property\n");
+               return NULL;
+       }
+
+       if (dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               goto done;
+
+       printf("Using default adapter %s\n", path);
+
+       result = strdup(path);
+
+done:
+       dbus_message_unref(reply);
+
+       return result;
+}
+
+static char *find_device(DBusConnection *conn, const char *adapter,
+                                                       const char *address)
+{
+       DBusMessage *message, *reply;
+       DBusError error;
+       const char *path;
+       char *result = NULL;
+
+       message = dbus_message_new_method_call(BLUEZ_SERVICE, adapter,
+                                       ADAPTER_INTF, "FindDevice");
+       if (!message)
+               return NULL;
+
+       dbus_message_append_args(message, DBUS_TYPE_STRING, &address,
+                                                       DBUS_TYPE_INVALID);
+
+       dbus_error_init(&error);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, &error);
+
+       dbus_message_unref(message);
+
+       if (!reply) {
+               if (dbus_error_is_set(&error) == TRUE) {
+                       fprintf(stderr, "%s\n", error.message);
+                       dbus_error_free(&error);
+               } else
+                       fprintf(stderr, "Failed to set property\n");
+               return NULL;
+       }
+
+       if (dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+                                               DBUS_TYPE_INVALID) == FALSE)
+               goto done;
+
+       printf("Using device %s for address %s\n", path, address);
+
+       result = strdup(path);
+
+done:
+       dbus_message_unref(reply);
+
+       return result;
+}
+
+static int remove_device(DBusConnection *conn, const char *adapter,
+                                                       const char *device)
+{
+       DBusMessage *message, *reply;
+       DBusError error;
+
+       message = dbus_message_new_method_call(BLUEZ_SERVICE, adapter,
+                                       ADAPTER_INTF, "RemoveDevice");
+       if (!message)
+               return -ENOMEM;
+
+       dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &device,
+                                                       DBUS_TYPE_INVALID);
+
+       dbus_error_init(&error);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, &error);
+
+       dbus_message_unref(message);
+
+       if (!reply) {
+               if (dbus_error_is_set(&error) == TRUE) {
+                       fprintf(stderr, "%s\n", error.message);
+                       dbus_error_free(&error);
+               } else
+                       fprintf(stderr, "Failed to set property\n");
+               return -EIO;
+       }
+
+       dbus_message_unref(reply);
+
+       printf("Removed device %s\n", device);
+
+       return 0;
+}
+
+static int set_property(DBusConnection *conn, const char *adapter,
+                                       const char *key, int type, void *val)
+{
+       DBusMessage *message, *reply;
+       DBusMessageIter array, value;
+       DBusError error;
+       const char *signature;
+
+       message = dbus_message_new_method_call(BLUEZ_SERVICE, adapter,
+                                               ADAPTER_INTF, "SetProperty");
+       if (!message)
+               return -ENOMEM;
+
+       switch (type) {
+       case DBUS_TYPE_BOOLEAN:
+               signature = DBUS_TYPE_BOOLEAN_AS_STRING;
+               break;
+       case DBUS_TYPE_UINT32:
+               signature = DBUS_TYPE_UINT32_AS_STRING;
+               break;
+       default:
+               return -EILSEQ;
+       }
+
+       dbus_message_iter_init_append(message, &array);
+
+       dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &key);
+
+       dbus_message_iter_open_container(&array, DBUS_TYPE_VARIANT,
+                                                       signature, &value);
+       dbus_message_iter_append_basic(&value, type, val);
+       dbus_message_iter_close_container(&array, &value);
+
+       dbus_error_init(&error);
+
+       reply = dbus_connection_send_with_reply_and_block(conn,
+                                                       message, -1, &error);
+
+       dbus_message_unref(message);
+
+       if (!reply) {
+               if (dbus_error_is_set(&error) == TRUE) {
+                       fprintf(stderr, "%s\n", error.message);
+                       dbus_error_free(&error);
+               } else
+                       fprintf(stderr, "Failed to set property\n");
+               return -EIO;
+       }
+
+       dbus_message_unref(reply);
+
+       printf("Set property %s for %s\n", key, adapter);
+
+       return 0;
+}
+
+static void usage(void)
+{
+       printf("gaptest - GAP testing\n"
+               "Usage:\n");
+       printf("\tgaptest [options]\n");
+       printf("Options:\n"
+               "\t-T <timeout>        Set timeout\n"
+               "\t-P <powered>        Set powered\n"
+               "\t-D <discoverable>   Set discoverable\n"
+               "\t-B <pairable>       Set pairable\n"
+               "\t-C <address>        Create device\n"
+               "\t-R <address>        Remove device\n");
+}
+
+int main(int argc, char *argv[])
+{
+       DBusConnection *conn;
+       char *adapter, *device;
+       const char *create = NULL, *remove = NULL;
+       int opt, timeout = -1, powered = -1, discoverable = -1, pairable = -1;
+
+       while ((opt = getopt(argc, argv, "T:P:D:B:C:R:h")) != EOF) {
+               switch (opt) {
+               case 'T':
+                       timeout = atoi(optarg);
+                       break;
+               case 'P':
+                       powered = atoi(optarg);
+                       break;
+               case 'D':
+                       discoverable = atoi(optarg);
+                       break;
+               case 'B':
+                       pairable = atoi(optarg);
+                       break;
+               case 'C':
+                       create = optarg;
+                       break;
+               case 'R':
+                       remove = optarg;
+                       break;
+               case 'h':
+                       usage();
+                       exit(0);
+               default:
+                       usage();
+                       exit(1);
+               }
+       }
+
+       conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+       if (!conn) {
+               fprintf(stderr, "Can't get on system bus\n");
+               exit(1);
+       }
+
+       adapter = get_adapter(conn);
+       if (!adapter) {
+               fprintf(stderr, "Can't get default adapter\n");
+               exit(1);
+       }
+
+       if (powered >= 0) {
+               set_property(conn, adapter, "Powered",
+                                       DBUS_TYPE_BOOLEAN, &powered);
+       }
+
+       if (discoverable >= 0) {
+               set_property(conn, adapter, "Discoverable",
+                                       DBUS_TYPE_BOOLEAN, &discoverable);
+
+               if (timeout >= 0)
+                       set_property(conn, adapter, "DiscoverableTimeout",
+                                               DBUS_TYPE_UINT32, &timeout);
+       }
+
+       if (pairable >= 0) {
+               set_property(conn, adapter, "Pairable",
+                                       DBUS_TYPE_BOOLEAN, &pairable);
+
+               if (timeout >= 0)
+                       set_property(conn, adapter, "PairableTimeout",
+                                               DBUS_TYPE_UINT32, &timeout);
+       }
+
+       if (create) {
+               device = find_device(conn, adapter, create);
+               if (!device) {
+                       fprintf(stderr, "Can't find device\n");
+                       exit(1);
+               }
+
+               free(device);
+       }
+
+       if (remove) {
+               device = find_device(conn, adapter, remove);
+               if (!device) {
+                       fprintf(stderr, "Can't find device\n");
+                       exit(1);
+               }
+
+               remove_device(conn, adapter, device);
+
+               free(device);
+       }
+
+       free(adapter);
+
+       dbus_connection_unref(conn);
+
+       return 0;
+}
diff --git a/test/hciemu.1 b/test/hciemu.1
new file mode 100644 (file)
index 0000000..cecaeb7
--- /dev/null
@@ -0,0 +1,31 @@
+.TH HCIEMU 1 "Jul 6 2009" BlueZ ""
+.SH NAME
+hciemu \- HCI emulator
+.SH SYNOPSIS
+.B hciemu
+[\fIoptions\fR] \fIlocal_address\fR
+
+.SH DESCRIPTION
+.LP
+.B
+hciemu
+is used to emulate an HCI via \fBhci_vhci\fR kernel module
+
+.SH OPTIONS
+.TP
+.BI -d\  device
+use specified \fIdevice\fR
+.TP
+.BI -b\  bdaddr
+emulate \fIbdaddr\fR
+.TP
+.BI -s\  file
+create snoop file \fIfile\fR
+.TP
+.B -n
+do not detach
+
+.SH AUTHORS
+Written by Marcel Holtmann <marcel@holtmann.org> and Maxim Krasnyansky
+<maxk@qualcomm.com>, man page by Filippo Giunchedi <filippo@debian.org>
+.PP
diff --git a/test/hciemu.c b/test/hciemu.c
new file mode 100644 (file)
index 0000000..9950372
--- /dev/null
@@ -0,0 +1,1343 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2002  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/resource.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include <netdb.h>
+
+#include <glib.h>
+
+#define GHCI_DEV               "/dev/ghci"
+
+#define VHCI_DEV               "/dev/vhci"
+#define VHCI_UDEV              "/dev/hci_vhci"
+
+#define VHCI_MAX_CONN          12
+
+#define VHCI_ACL_MTU           192
+#define VHCI_ACL_MAX_PKT       8
+
+struct vhci_device {
+       uint8_t         features[8];
+       uint8_t         name[248];
+       uint8_t         dev_class[3];
+       uint8_t         inq_mode;
+       uint8_t         eir_fec;
+       uint8_t         eir_data[240];
+       uint16_t        acl_cnt;
+       bdaddr_t        bdaddr;
+       int             fd;
+       int             dd;
+       GIOChannel      *scan;
+};
+
+struct vhci_conn {
+       bdaddr_t        dest;
+       uint16_t        handle;
+       GIOChannel      *chan;
+};
+
+struct vhci_link_info {
+       bdaddr_t        bdaddr;
+       uint8_t         dev_class[3];
+       uint8_t         link_type;
+       uint8_t         role;
+} __attribute__ ((packed));
+
+static struct vhci_device vdev;
+static struct vhci_conn *vconn[VHCI_MAX_CONN];
+
+struct btsnoop_hdr {
+       uint8_t         id[8];          /* Identification Pattern */
+       uint32_t        version;        /* Version Number = 1 */
+       uint32_t        type;           /* Datalink Type */
+} __attribute__ ((packed));
+#define BTSNOOP_HDR_SIZE (sizeof(struct btsnoop_hdr))
+
+struct btsnoop_pkt {
+       uint32_t        size;           /* Original Length */
+       uint32_t        len;            /* Included Length */
+       uint32_t        flags;          /* Packet Flags */
+       uint32_t        drops;          /* Cumulative Drops */
+       uint64_t        ts;             /* Timestamp microseconds */
+       uint8_t         data[0];        /* Packet Data */
+} __attribute__ ((packed));
+#define BTSNOOP_PKT_SIZE (sizeof(struct btsnoop_pkt))
+
+static uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00 };
+
+static GMainLoop *event_loop;
+
+static volatile sig_atomic_t __io_canceled;
+
+static inline void io_init(void)
+{
+       __io_canceled = 0;
+}
+
+static inline void io_cancel(void)
+{
+       __io_canceled = 1;
+}
+
+static void sig_term(int sig)
+{
+       io_cancel();
+       g_main_loop_quit(event_loop);
+}
+
+static gboolean io_acl_data(GIOChannel *chan, GIOCondition cond, gpointer data);
+static gboolean io_conn_ind(GIOChannel *chan, GIOCondition cond, gpointer data);
+static gboolean io_hci_data(GIOChannel *chan, GIOCondition cond, gpointer data);
+
+static inline int read_n(int fd, void *buf, int len)
+{
+       register int w, t = 0;
+
+       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, void *buf, int len)
+{
+       register int w, t = 0;
+
+       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;
+}
+
+static int create_snoop(char *file)
+{
+       struct btsnoop_hdr hdr;
+       int fd, len;
+
+       fd = open(file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+       if (fd < 0)
+               return fd;
+
+       memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id));
+       hdr.version = htonl(1);
+       hdr.type = htonl(1002);
+
+       len = write(fd, &hdr, BTSNOOP_HDR_SIZE);
+       if (len < 0) {
+               close(fd);
+               return -EIO;
+       }
+
+       if (len != BTSNOOP_HDR_SIZE) {
+               close(fd);
+               return -1;
+       }
+
+       return fd;
+}
+
+static int write_snoop(int fd, int type, int incoming, unsigned char *buf, int len)
+{
+       struct btsnoop_pkt pkt;
+       struct timeval tv;
+       uint32_t size = len;
+       uint64_t ts;
+       int err;
+
+       if (fd < 0)
+               return -1;
+
+       memset(&tv, 0, sizeof(tv));
+       gettimeofday(&tv, NULL);
+       ts = (tv.tv_sec - 946684800ll) * 1000000ll + tv.tv_usec;
+
+       pkt.size = htonl(size);
+       pkt.len  = pkt.size;
+       pkt.flags = ntohl(incoming & 0x01);
+       pkt.drops = htonl(0);
+       pkt.ts = hton64(ts + 0x00E03AB44A676000ll);
+
+       if (type == HCI_COMMAND_PKT || type == HCI_EVENT_PKT)
+               pkt.flags |= ntohl(0x02);
+
+       err = write(fd, &pkt, BTSNOOP_PKT_SIZE);
+       err = write(fd, buf, size);
+
+       return 0;
+}
+
+static struct vhci_conn *conn_get_by_bdaddr(bdaddr_t *ba)
+{
+       register int i;
+
+       for (i = 0; i < VHCI_MAX_CONN; i++)
+               if (!bacmp(&vconn[i]->dest, ba))
+                       return vconn[i];
+
+       return NULL;
+}
+
+static void command_status(uint16_t ogf, uint16_t ocf, uint8_t status)
+{
+       uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf;
+       evt_cmd_status *cs;
+       hci_event_hdr *he;
+
+       /* Packet type */
+       *ptr++ = HCI_EVENT_PKT;
+
+       /* Event header */
+       he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE;
+
+       he->evt  = EVT_CMD_STATUS;
+       he->plen = EVT_CMD_STATUS_SIZE;
+
+       cs = (void *) ptr; ptr += EVT_CMD_STATUS_SIZE;
+
+       cs->status = status;
+       cs->ncmd   = 1;
+       cs->opcode = htobs(cmd_opcode_pack(ogf, ocf));
+
+       write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf);
+
+       if (write(vdev.fd, buf, ptr - buf) < 0)
+               syslog(LOG_ERR, "Can't send event: %s(%d)",
+                                               strerror(errno), errno);
+}
+
+static void command_complete(uint16_t ogf, uint16_t ocf, int plen, void *data)
+{
+       uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf;
+       evt_cmd_complete *cc;
+       hci_event_hdr *he;
+
+       /* Packet type */
+       *ptr++ = HCI_EVENT_PKT;
+
+       /* Event header */
+       he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE;
+
+       he->evt  = EVT_CMD_COMPLETE;
+       he->plen = EVT_CMD_COMPLETE_SIZE + plen;
+
+       cc = (void *) ptr; ptr += EVT_CMD_COMPLETE_SIZE;
+
+       cc->ncmd = 1;
+       cc->opcode = htobs(cmd_opcode_pack(ogf, ocf));
+
+       if (plen) {
+               memcpy(ptr, data, plen);
+               ptr += plen;
+       }
+
+       write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf);
+
+       if (write(vdev.fd, buf, ptr - buf) < 0)
+               syslog(LOG_ERR, "Can't send event: %s(%d)",
+                                               strerror(errno), errno);
+}
+
+static void connect_request(struct vhci_conn *conn)
+{
+       uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf;
+       evt_conn_request *cr;
+       hci_event_hdr *he;
+
+       /* Packet type */
+       *ptr++ = HCI_EVENT_PKT;
+
+       /* Event header */
+       he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE;
+
+       he->evt  = EVT_CONN_REQUEST;
+       he->plen = EVT_CONN_REQUEST_SIZE;
+
+       cr = (void *) ptr; ptr += EVT_CONN_REQUEST_SIZE;
+
+       bacpy(&cr->bdaddr, &conn->dest);
+       memset(&cr->dev_class, 0, sizeof(cr->dev_class));
+       cr->link_type = ACL_LINK;
+
+       write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf);
+
+       if (write(vdev.fd, buf, ptr - buf) < 0)
+               syslog(LOG_ERR, "Can't send event: %s (%d)",
+                                               strerror(errno), errno);
+}
+
+static void connect_complete(struct vhci_conn *conn)
+{
+       uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf;
+       evt_conn_complete *cc;
+       hci_event_hdr *he;
+
+       /* Packet type */
+       *ptr++ = HCI_EVENT_PKT;
+
+       /* Event header */
+       he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE;
+
+       he->evt  = EVT_CONN_COMPLETE;
+       he->plen = EVT_CONN_COMPLETE_SIZE;
+
+       cc = (void *) ptr; ptr += EVT_CONN_COMPLETE_SIZE;
+
+       bacpy(&cc->bdaddr, &conn->dest);
+       cc->status = 0x00;
+       cc->handle = htobs(conn->handle);
+       cc->link_type = ACL_LINK;
+       cc->encr_mode = 0x00;
+
+       write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf);
+
+       if (write(vdev.fd, buf, ptr - buf) < 0)
+               syslog(LOG_ERR, "Can't send event: %s (%d)",
+                                               strerror(errno), errno);
+}
+
+static void disconn_complete(struct vhci_conn *conn)
+{
+       uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf;
+       evt_disconn_complete *dc;
+       hci_event_hdr *he;
+
+       /* Packet type */
+       *ptr++ = HCI_EVENT_PKT;
+
+       /* Event header */
+       he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE;
+
+       he->evt  = EVT_DISCONN_COMPLETE;
+       he->plen = EVT_DISCONN_COMPLETE_SIZE;
+
+       dc = (void *) ptr; ptr += EVT_DISCONN_COMPLETE_SIZE;
+
+       dc->status = 0x00;
+       dc->handle = htobs(conn->handle);
+       dc->reason = 0x00;
+
+       write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf);
+
+       if (write(vdev.fd, buf, ptr - buf) < 0)
+               syslog(LOG_ERR, "Can't send event: %s (%d)",
+                                               strerror(errno), errno);
+
+       vdev.acl_cnt = 0;
+}
+
+static void num_completed_pkts(struct vhci_conn *conn)
+{
+       uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf;
+       evt_num_comp_pkts *np;
+       hci_event_hdr *he;
+
+       /* Packet type */
+       *ptr++ = HCI_EVENT_PKT;
+
+       /* Event header */
+       he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE;
+
+       he->evt  = EVT_NUM_COMP_PKTS;
+       he->plen = EVT_NUM_COMP_PKTS_SIZE;
+
+       np = (void *) ptr; ptr += EVT_NUM_COMP_PKTS_SIZE;
+       np->num_hndl = 1;
+
+       *((uint16_t *) ptr) = htobs(conn->handle); ptr += 2;
+       *((uint16_t *) ptr) = htobs(vdev.acl_cnt); ptr += 2;
+
+       write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf);
+
+       if (write(vdev.fd, buf, ptr - buf) < 0)
+               syslog(LOG_ERR, "Can't send event: %s (%d)",
+                                               strerror(errno), errno);
+}
+
+static int scan_enable(uint8_t *data)
+{
+       struct sockaddr_in sa;
+       GIOChannel *sk_io;
+       bdaddr_t ba;
+       int sk, opt;
+
+       if (!(*data & SCAN_PAGE)) {
+               if (vdev.scan) {
+                       g_io_channel_shutdown(vdev.scan, TRUE, NULL);
+                       vdev.scan = NULL;
+               }
+               return 0;
+       }
+
+       if (vdev.scan)
+               return 0;
+
+       if ((sk = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+               syslog(LOG_ERR, "Can't create socket: %s (%d)",
+                                               strerror(errno), errno);
+               return 1;
+       }
+
+       opt = 1;
+       setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+       baswap(&ba, &vdev.bdaddr);
+       sa.sin_family = AF_INET;
+       memcpy(&sa.sin_addr.s_addr, &ba, sizeof(sa.sin_addr.s_addr));
+       sa.sin_port = *(uint16_t *) &ba.b[4];
+       if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) {
+               syslog(LOG_ERR, "Can't bind socket: %s (%d)",
+                                               strerror(errno), errno);
+               goto failed;
+       }
+
+       if (listen(sk, 10)) {
+               syslog(LOG_ERR, "Can't listen on socket: %s (%d)",
+                                               strerror(errno), errno);
+               goto failed;
+       }
+
+       sk_io = g_io_channel_unix_new(sk);
+       g_io_add_watch(sk_io, G_IO_IN | G_IO_NVAL, io_conn_ind, NULL);
+       vdev.scan = sk_io;
+       return 0;
+
+failed:
+       close(sk);
+       return 1;
+}
+
+static void accept_connection(uint8_t *data)
+{
+       accept_conn_req_cp *cp = (void *) data;
+       struct vhci_conn *conn;
+
+       if (!(conn = conn_get_by_bdaddr(&cp->bdaddr)))
+               return;
+
+       connect_complete(conn);
+
+       g_io_add_watch(conn->chan, G_IO_IN | G_IO_NVAL | G_IO_HUP,
+                       io_acl_data, (gpointer) conn);
+}
+
+static void close_connection(struct vhci_conn *conn)
+{
+       char addr[18];
+
+       ba2str(&conn->dest, addr);
+       syslog(LOG_INFO, "Closing connection %s handle %d",
+                                       addr, conn->handle);
+
+       g_io_channel_shutdown(conn->chan, TRUE, NULL);
+       g_io_channel_unref(conn->chan);
+
+       vconn[conn->handle - 1] = NULL;
+       disconn_complete(conn);
+       free(conn);
+}
+
+static void disconnect(uint8_t *data)
+{
+       disconnect_cp *cp = (void *) data;
+       struct vhci_conn *conn;
+       uint16_t handle;
+
+       handle = btohs(cp->handle);
+
+       if (handle > VHCI_MAX_CONN)
+               return;
+
+       if (!(conn = vconn[handle-1]))
+               return;
+
+       close_connection(conn);
+}
+
+static void create_connection(uint8_t *data)
+{
+       create_conn_cp *cp = (void *) data;
+       struct vhci_link_info info;
+       struct vhci_conn *conn;
+       struct sockaddr_in sa;
+       int h, sk, opt;
+       bdaddr_t ba;
+
+       for (h = 0; h < VHCI_MAX_CONN; h++)
+               if (!vconn[h])
+                       goto do_connect;
+
+       syslog(LOG_ERR, "Too many connections");
+       return;
+
+do_connect:
+       if ((sk = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+               syslog(LOG_ERR, "Can't create socket: %s (%d)",
+                                               strerror(errno), errno);
+               return;
+       }
+
+       opt = 1;
+       setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+       baswap(&ba, &vdev.bdaddr);
+       sa.sin_family = AF_INET;
+       sa.sin_addr.s_addr = INADDR_ANY;        // *(uint32_t *) &ba;
+       sa.sin_port = 0;                        // *(uint16_t *) &ba.b[4];
+       if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) {
+               syslog(LOG_ERR, "Can't bind socket: %s (%d)",
+                                               strerror(errno), errno);
+               close(sk);
+               return;
+       }
+
+       baswap(&ba, &cp->bdaddr);
+       sa.sin_family = AF_INET;
+       memcpy(&sa.sin_addr.s_addr, &ba, sizeof(sa.sin_addr.s_addr));
+       sa.sin_port = *(uint16_t *) &ba.b[4];
+       if (connect(sk, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
+               syslog(LOG_ERR, "Can't connect: %s (%d)",
+                                               strerror(errno), errno);
+               close(sk);
+               return;
+       }
+
+       /* Send info */
+       memset(&info, 0, sizeof(info));
+       bacpy(&info.bdaddr, &vdev.bdaddr);
+       info.link_type = ACL_LINK;
+       info.role = 1;
+       write_n(sk, (void *) &info, sizeof(info));
+
+       if (!(conn = malloc(sizeof(*conn)))) {
+               syslog(LOG_ERR, "Can't alloc new connection: %s (%d)",
+                                               strerror(errno), errno);
+               close(sk);
+               return;
+       }
+
+       memcpy((uint8_t *) &ba, (uint8_t *) &sa.sin_addr, 4);
+       memcpy((uint8_t *) &ba.b[4], (uint8_t *) &sa.sin_port, 2);
+       baswap(&conn->dest, &ba);
+
+       vconn[h] = conn;
+       conn->handle = h + 1;
+       conn->chan = g_io_channel_unix_new(sk);
+
+       connect_complete(conn);
+       g_io_add_watch(conn->chan, G_IO_IN | G_IO_NVAL | G_IO_HUP,
+                               io_acl_data, (gpointer) conn);
+       return;
+}
+
+static void hci_link_control(uint16_t ocf, int plen, uint8_t *data)
+{
+       uint8_t status;
+
+       const uint16_t ogf = OGF_LINK_CTL;
+
+       switch (ocf) {
+       case OCF_CREATE_CONN:
+               command_status(ogf, ocf, 0x00);
+               create_connection(data);
+               break;
+
+       case OCF_ACCEPT_CONN_REQ:
+               command_status(ogf, ocf, 0x00);
+               accept_connection(data);
+               break;
+
+       case OCF_DISCONNECT:
+               command_status(ogf, ocf, 0x00);
+               disconnect(data);
+               break;
+
+       default:
+               status = 0x01;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+       }
+}
+
+static void hci_link_policy(uint16_t ocf, int plen, uint8_t *data)
+{
+       uint8_t status;
+
+       const uint16_t ogf = OGF_INFO_PARAM;
+
+       switch (ocf) {
+       default:
+               status = 0x01;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+       }
+}
+
+static void hci_host_control(uint16_t ocf, int plen, uint8_t *data)
+{
+       read_local_name_rp ln;
+       read_class_of_dev_rp cd;
+       read_inquiry_mode_rp im;
+       read_ext_inquiry_response_rp ir;
+       uint8_t status;
+
+       const uint16_t ogf = OGF_HOST_CTL;
+
+       switch (ocf) {
+       case OCF_RESET:
+               status = 0x00;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_SET_EVENT_FLT:
+               status = 0x00;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_CHANGE_LOCAL_NAME:
+               status = 0x00;
+               memcpy(vdev.name, data, sizeof(vdev.name));
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_READ_LOCAL_NAME:
+               ln.status = 0x00;
+               memcpy(ln.name, vdev.name, sizeof(ln.name));
+               command_complete(ogf, ocf, sizeof(ln), &ln);
+               break;
+
+       case OCF_WRITE_CONN_ACCEPT_TIMEOUT:
+       case OCF_WRITE_PAGE_TIMEOUT:
+               status = 0x00;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_WRITE_SCAN_ENABLE:
+               status = scan_enable(data);
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_WRITE_AUTH_ENABLE:
+               status = 0x00;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_WRITE_ENCRYPT_MODE:
+               status = 0x00;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_READ_CLASS_OF_DEV:
+               cd.status = 0x00;
+               memcpy(cd.dev_class, vdev.dev_class, 3);
+               command_complete(ogf, ocf, sizeof(cd), &cd);
+               break;
+
+       case OCF_WRITE_CLASS_OF_DEV:
+               status = 0x00;
+               memcpy(vdev.dev_class, data, 3);
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_READ_INQUIRY_MODE:
+               im.status = 0x00;
+               im.mode = vdev.inq_mode;
+               command_complete(ogf, ocf, sizeof(im), &im);
+               break;
+
+       case OCF_WRITE_INQUIRY_MODE:
+               status = 0x00;
+               vdev.inq_mode = data[0];
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       case OCF_READ_EXT_INQUIRY_RESPONSE:
+               ir.status = 0x00;
+               ir.fec = vdev.eir_fec;
+               memcpy(ir.data, vdev.eir_data, 240);
+               command_complete(ogf, ocf, sizeof(ir), &ir);
+               break;
+
+       case OCF_WRITE_EXT_INQUIRY_RESPONSE:
+               status = 0x00;
+               vdev.eir_fec = data[0];
+               memcpy(vdev.eir_data, data + 1, 240);
+               command_complete(ogf, ocf, 1, &status);
+               break;
+
+       default:
+               status = 0x01;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+       }
+}
+
+static void hci_info_param(uint16_t ocf, int plen, uint8_t *data)
+{
+       read_local_version_rp lv;
+       read_local_features_rp lf;
+       read_local_ext_features_rp ef;
+       read_buffer_size_rp bs;
+       read_bd_addr_rp ba;
+       uint8_t status;
+
+       const uint16_t ogf = OGF_INFO_PARAM;
+
+       switch (ocf) {
+       case OCF_READ_LOCAL_VERSION:
+               lv.status = 0x00;
+               lv.hci_ver = 0x03;
+               lv.hci_rev = htobs(0x0000);
+               lv.lmp_ver = 0x03;
+               lv.manufacturer = htobs(29);
+               lv.lmp_subver = htobs(0x0000);
+               command_complete(ogf, ocf, sizeof(lv), &lv);
+               break;
+
+       case OCF_READ_LOCAL_FEATURES:
+               lf.status = 0x00;
+               memcpy(lf.features, vdev.features, 8);
+               command_complete(ogf, ocf, sizeof(lf), &lf);
+               break;
+
+       case OCF_READ_LOCAL_EXT_FEATURES:
+               ef.status = 0x00;
+               if (*data == 0) {
+                       ef.page_num = 0;
+                       ef.max_page_num = 0;
+                       memcpy(ef.features, vdev.features, 8);
+               } else {
+                       ef.page_num = *data;
+                       ef.max_page_num = 0;
+                       memset(ef.features, 0, 8);
+               }
+               command_complete(ogf, ocf, sizeof(ef), &ef);
+               break;
+
+       case OCF_READ_BUFFER_SIZE:
+               bs.status = 0x00;
+               bs.acl_mtu = htobs(VHCI_ACL_MTU);
+               bs.sco_mtu = 0;
+               bs.acl_max_pkt = htobs(VHCI_ACL_MAX_PKT);
+               bs.sco_max_pkt = htobs(0);
+               command_complete(ogf, ocf, sizeof(bs), &bs);
+               break;
+
+       case OCF_READ_BD_ADDR:
+               ba.status = 0x00;
+               bacpy(&ba.bdaddr, &vdev.bdaddr);
+               command_complete(ogf, ocf, sizeof(ba), &ba);
+               break;
+
+       default:
+               status = 0x01;
+               command_complete(ogf, ocf, 1, &status);
+               break;
+       }
+}
+
+static void hci_command(uint8_t *data)
+{
+       hci_command_hdr *ch;
+       uint8_t *ptr = data;
+       uint16_t ogf, ocf;
+
+       ch = (hci_command_hdr *) ptr;
+       ptr += HCI_COMMAND_HDR_SIZE;
+
+       ch->opcode = btohs(ch->opcode);
+       ogf = cmd_opcode_ogf(ch->opcode);
+       ocf = cmd_opcode_ocf(ch->opcode);
+
+       switch (ogf) {
+       case OGF_LINK_CTL:
+               hci_link_control(ocf, ch->plen, ptr);
+               break;
+
+       case OGF_LINK_POLICY:
+               hci_link_policy(ocf, ch->plen, ptr);
+               break;
+
+       case OGF_HOST_CTL:
+               hci_host_control(ocf, ch->plen, ptr);
+               break;
+
+       case OGF_INFO_PARAM:
+               hci_info_param(ocf, ch->plen, ptr);
+               break;
+       }
+}
+
+static void hci_acl_data(uint8_t *data)
+{
+       hci_acl_hdr *ah = (void *) data;
+       struct vhci_conn *conn;
+       uint16_t handle;
+       int fd;
+
+       handle = acl_handle(btohs(ah->handle));
+
+       if (handle > VHCI_MAX_CONN || !(conn = vconn[handle - 1])) {
+               syslog(LOG_ERR, "Bad connection handle %d", handle);
+               return;
+       }
+
+       fd = g_io_channel_unix_get_fd(conn->chan);
+       if (write_n(fd, data, btohs(ah->dlen) + HCI_ACL_HDR_SIZE) < 0) {
+               close_connection(conn);
+               return;
+       }
+
+       if (++vdev.acl_cnt > VHCI_ACL_MAX_PKT - 1) {
+               /* Send num of complete packets event */
+               num_completed_pkts(conn);
+               vdev.acl_cnt = 0;
+       }
+}
+
+static gboolean io_acl_data(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       struct vhci_conn *conn = (struct vhci_conn *) data;
+       unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr;
+       hci_acl_hdr *ah;
+       uint16_t flags;
+       int fd, err, len;
+
+       if (cond & G_IO_NVAL) {
+               g_io_channel_unref(chan);
+               return FALSE;
+       }
+
+       if (cond & G_IO_HUP) {
+               close_connection(conn);
+               return FALSE;
+       }
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       ptr = buf + 1;
+       if (read_n(fd, ptr, HCI_ACL_HDR_SIZE) <= 0) {
+               close_connection(conn);
+               return FALSE;
+       }
+
+       ah = (void *) ptr;
+       ptr += HCI_ACL_HDR_SIZE;
+
+       len = btohs(ah->dlen);
+       if (read_n(fd, ptr, len) <= 0) {
+               close_connection(conn);
+               return FALSE;
+       }
+
+       buf[0] = HCI_ACLDATA_PKT;
+
+       flags = acl_flags(btohs(ah->handle));
+       ah->handle = htobs(acl_handle_pack(conn->handle, flags));
+       len += HCI_ACL_HDR_SIZE + 1;
+
+       write_snoop(vdev.dd, HCI_ACLDATA_PKT, 1, buf, len);
+
+       err = write(vdev.fd, buf, len);
+
+       return TRUE;
+}
+
+static gboolean io_conn_ind(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       struct vhci_link_info info;
+       struct vhci_conn *conn;
+       struct sockaddr_in sa;
+       socklen_t len;
+       int sk, nsk, h;
+
+       if (cond & G_IO_NVAL)
+               return FALSE;
+
+       sk = g_io_channel_unix_get_fd(chan);
+
+       len = sizeof(sa);
+       if ((nsk = accept(sk, (struct sockaddr *) &sa, &len)) < 0)
+               return TRUE;
+
+       if (read_n(nsk, &info, sizeof(info)) < 0) {
+               syslog(LOG_ERR, "Can't read link info");
+               return TRUE;
+       }
+
+       if (!(conn = malloc(sizeof(*conn)))) {
+               syslog(LOG_ERR, "Can't alloc new connection");
+               close(nsk);
+               return TRUE;
+       }
+
+       bacpy(&conn->dest, &info.bdaddr);
+
+       for (h = 0; h < VHCI_MAX_CONN; h++)
+               if (!vconn[h])
+                       goto accepted;
+
+       syslog(LOG_ERR, "Too many connections");
+       free(conn);
+       close(nsk);
+       return TRUE;
+
+accepted:
+       vconn[h] = conn;
+       conn->handle = h + 1;
+       conn->chan = g_io_channel_unix_new(nsk);
+       connect_request(conn);
+
+       return TRUE;
+}
+
+static gboolean io_hci_data(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+       unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr;
+       int type;
+       ssize_t len;
+       int fd;
+
+       ptr = buf;
+
+       fd = g_io_channel_unix_get_fd(chan);
+
+       len = read(fd, buf, sizeof(buf));
+       if (len < 0) {
+               if (errno == EAGAIN)
+                       return TRUE;
+
+               syslog(LOG_ERR, "Read failed: %s (%d)", strerror(errno), errno);
+               g_io_channel_unref(chan);
+               g_main_loop_quit(event_loop);
+               return FALSE;
+       }
+
+       type = *ptr++;
+
+       write_snoop(vdev.dd, type, 0, buf, len);
+
+       switch (type) {
+       case HCI_COMMAND_PKT:
+               hci_command(ptr);
+               break;
+
+       case HCI_ACLDATA_PKT:
+               hci_acl_data(ptr);
+               break;
+
+       default:
+               syslog(LOG_ERR, "Unknown packet type 0x%2.2x", type);
+               break;
+       }
+
+       return TRUE;
+}
+
+static int getbdaddrbyname(char *str, bdaddr_t *ba)
+{
+       int i, n, len;
+
+       len = strlen(str);
+
+       /* Check address format */
+       for (i = 0, n = 0; i < len; i++)
+               if (str[i] == ':')
+                       n++;
+
+       if (n == 5) {
+               /* BD address */
+               str2ba(str, ba);
+               return 0;
+       }
+
+       if (n == 1) {
+               /* IP address + port */
+               struct hostent *hent;
+               bdaddr_t b;
+               char *ptr;
+
+               ptr = strchr(str, ':');
+               *ptr++ = 0;
+
+               if (!(hent = gethostbyname(str))) {
+                       fprintf(stderr, "Can't resolve %s\n", str);
+                       return -2;
+               }
+
+               memcpy(&b, hent->h_addr, 4);
+               *(uint16_t *) (&b.b[4]) = htons(atoi(ptr));
+               baswap(ba, &b);
+
+               return 0;
+       }
+
+       fprintf(stderr, "Invalid address format\n");
+
+       return -1;
+}
+
+static void rewrite_bdaddr(unsigned char *buf, int len, bdaddr_t *bdaddr)
+{
+       hci_event_hdr *eh;
+       unsigned char *ptr = buf;
+       int type;
+
+       if (!bdaddr)
+               return;
+
+       if (!bacmp(bdaddr, BDADDR_ANY))
+               return;
+
+       type = *ptr++;
+
+       switch (type) {
+       case HCI_EVENT_PKT:
+               eh = (hci_event_hdr *) ptr;
+               ptr += HCI_EVENT_HDR_SIZE;
+
+               if (eh->evt == EVT_CMD_COMPLETE) {
+                       evt_cmd_complete *cc = (void *) ptr;
+
+                       ptr += EVT_CMD_COMPLETE_SIZE;
+
+                       if (cc->opcode == htobs(cmd_opcode_pack(OGF_INFO_PARAM,
+                                               OCF_READ_BD_ADDR))) {
+                               bacpy((bdaddr_t *) (ptr + 1), bdaddr);
+                       }
+               }
+               break;
+       }
+}
+
+static int run_proxy(int fd, int dev, bdaddr_t *bdaddr)
+{
+       unsigned char buf[HCI_MAX_FRAME_SIZE + 1];
+       struct hci_dev_info di;
+       struct hci_filter flt;
+       struct pollfd p[2];
+       int dd, err, len, need_raw;
+
+       dd = hci_open_dev(dev);
+       if (dd < 0) {
+               syslog(LOG_ERR, "Can't open device hci%d: %s (%d)",
+                                               dev, strerror(errno), errno);
+               return 1;
+       }
+
+       if (hci_devinfo(dev, &di) < 0) {
+               syslog(LOG_ERR, "Can't get device info for hci%d: %s (%d)",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               return 1;
+       }
+
+       need_raw = !hci_test_bit(HCI_RAW, &di.flags);
+
+       hci_filter_clear(&flt);
+       hci_filter_all_ptypes(&flt);
+       hci_filter_all_events(&flt);
+
+       if (setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
+               syslog(LOG_ERR, "Can't set filter for hci%d: %s (%d)",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               return 1;
+       }
+
+       if (need_raw) {
+               if (ioctl(dd, HCISETRAW, 1) < 0) {
+                       syslog(LOG_ERR, "Can't set raw mode on hci%d: %s (%d)",
+                                               dev, strerror(errno), errno);
+                       hci_close_dev(dd);
+                       return 1;
+               }
+       }
+
+       p[0].fd = fd;
+       p[0].events = POLLIN;
+       p[1].fd = dd;
+       p[1].events = POLLIN;
+
+       while (!__io_canceled) {
+               p[0].revents = 0;
+               p[1].revents = 0;
+               err = poll(p, 2, 500);
+               if (err < 0)
+                       break;
+               if (!err)
+                       continue;
+
+               if (p[0].revents & POLLIN) {
+                       len = read(fd, buf, sizeof(buf));
+                       if (len > 0) {
+                               rewrite_bdaddr(buf, len, bdaddr);
+                               err = write(dd, buf, len);
+                       }
+               }
+
+               if (p[1].revents & POLLIN) {
+                       len = read(dd, buf, sizeof(buf));
+                       if (len > 0) {
+                               rewrite_bdaddr(buf, len, bdaddr);
+                               err = write(fd, buf, len);
+                       }
+               }
+       }
+
+       if (need_raw) {
+               if (ioctl(dd, HCISETRAW, 0) < 0)
+                       syslog(LOG_ERR, "Can't clear raw mode on hci%d: %s (%d)",
+                                               dev, strerror(errno), errno);
+       }
+
+       hci_close_dev(dd);
+
+       syslog(LOG_INFO, "Exit");
+
+       return 0;
+}
+
+static void usage(void)
+{
+       printf("hciemu - HCI emulator ver %s\n", VERSION);
+       printf("Usage: \n");
+       printf("\thciemu [options] local_address\n"
+               "Options:\n"
+               "\t[-d device] use specified device\n"
+               "\t[-b bdaddr] emulate specified address\n"
+               "\t[-s file] create snoop file\n"
+               "\t[-n] do not detach\n"
+               "\t[-h] help, you are looking at it\n");
+}
+
+static struct option main_options[] = {
+       { "device",     1, 0, 'd' },
+       { "bdaddr",     1, 0, 'b' },
+       { "snoop",      1, 0, 's' },
+       { "nodetach",   0, 0, 'n' },
+       { "help",       0, 0, 'h' },
+       { 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       struct sigaction sa;
+       GIOChannel *dev_io;
+       char *device = NULL, *snoop = NULL;
+       bdaddr_t bdaddr;
+       int fd, dd, opt, detach = 1, dev = -1;
+
+       bacpy(&bdaddr, BDADDR_ANY);
+
+       while ((opt=getopt_long(argc, argv, "d:b:s:nh", main_options, NULL)) != EOF) {
+               switch(opt) {
+               case 'd':
+                       device = strdup(optarg);
+                       break;
+
+               case 'b':
+                       str2ba(optarg, &bdaddr);
+                       break;
+
+               case 's':
+                       snoop = strdup(optarg);
+                       break;
+
+               case 'n':
+                       detach = 0;
+                       break;
+
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       if (strlen(argv[0]) > 3 && !strncasecmp(argv[0], "hci", 3)) {
+               dev = hci_devid(argv[0]);
+               if (dev < 0) {
+                       perror("Invalid device");
+                       exit(1);
+               }
+       } else {
+               if (getbdaddrbyname(argv[0], &vdev.bdaddr) < 0)
+                       exit(1);
+       }
+
+       if (detach) {
+               if (daemon(0, 0)) {
+                       perror("Can't start daemon");
+                       exit(1);
+               }
+       }
+
+       /* Start logging to syslog and stderr */
+       openlog("hciemu", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
+       syslog(LOG_INFO, "HCI emulation daemon ver %s started", VERSION);
+
+       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);
+
+       io_init();
+
+       if (!device && dev >= 0)
+               device = strdup(GHCI_DEV);
+
+       /* Open and create virtual HCI device */
+       if (device) {
+               fd = open(device, O_RDWR);
+               if (fd < 0) {
+                       syslog(LOG_ERR, "Can't open device %s: %s (%d)",
+                                               device, strerror(errno), errno);
+                       free(device);
+                       exit(1);
+               }
+               free(device);
+       } else {
+               fd = open(VHCI_DEV, O_RDWR);
+               if (fd < 0) {
+                       fd = open(VHCI_UDEV, O_RDWR);
+                       if (fd < 0) {
+                               syslog(LOG_ERR, "Can't open device %s: %s (%d)",
+                                               VHCI_DEV, strerror(errno), errno);
+                               exit(1);
+                       }
+               }
+       }
+
+       /* Create snoop file */
+       if (snoop) {
+               dd = create_snoop(snoop);
+               if (dd < 0)
+                       syslog(LOG_ERR, "Can't create snoop file %s: %s (%d)",
+                                               snoop, strerror(errno), errno);
+               free(snoop);
+       } else
+               dd = -1;
+
+       /* Create event loop */
+       event_loop = g_main_loop_new(NULL, FALSE);
+
+       if (dev >= 0)
+               return run_proxy(fd, dev, &bdaddr);
+
+       /* Device settings */
+       vdev.features[0] = 0xff;
+       vdev.features[1] = 0xff;
+       vdev.features[2] = 0x8f;
+       vdev.features[3] = 0xfe;
+       vdev.features[4] = 0x9b;
+       vdev.features[5] = 0xf9;
+       vdev.features[6] = 0x01;
+       vdev.features[7] = 0x80;
+
+       memset(vdev.name, 0, sizeof(vdev.name));
+       strncpy((char *) vdev.name, "BlueZ (Virtual HCI)",
+                                                       sizeof(vdev.name) - 1);
+
+       vdev.dev_class[0] = 0x00;
+       vdev.dev_class[1] = 0x00;
+       vdev.dev_class[2] = 0x00;
+
+       vdev.inq_mode = 0x00;
+       vdev.eir_fec = 0x00;
+       memset(vdev.eir_data, 0, sizeof(vdev.eir_data));
+
+       vdev.fd = fd;
+       vdev.dd = dd;
+
+       dev_io = g_io_channel_unix_new(fd);
+       g_io_add_watch(dev_io, G_IO_IN, io_hci_data, NULL);
+
+       setpriority(PRIO_PROCESS, 0, -19);
+
+       /* Start event processor */
+       g_main_loop_run(event_loop);
+
+       close(fd);
+
+       if (dd >= 0)
+               close(dd);
+
+       syslog(LOG_INFO, "Exit");
+
+       return 0;
+}
diff --git a/test/hsmicro b/test/hsmicro
new file mode 100755 (executable)
index 0000000..c254226
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+SOX=`which sox`
+HSTEST=`which hstest`
+
+if [ -z "$HSTEST" ]
+then
+       HSTEST="./hstest"
+fi
+
+if [ -z "$1" ]
+then
+       echo -e "Usage:\n\thsmicro <bdaddr> [channel]"
+       exit
+fi
+
+BDADDR=$1
+CHANNEL=$2
+
+$HSTEST record - $BDADDR $CHANNEL | $SOX -t raw -r 8000 -c 1 -s -w - -t ossdsp -r 44100 -c 2 -s -w /dev/dsp polyphase vol 5.0 2> /dev/null
diff --git a/test/hsplay b/test/hsplay
new file mode 100755 (executable)
index 0000000..8cecbff
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+MPG123=`which mpg123`
+SOX=`which sox`
+HSTEST=`which hstest`
+
+if [ -z "$HSTEST" ]
+then
+       HSTEST="./hstest"
+fi
+
+if [ -z "$1" ] || [ -z "$2" ]
+then
+       echo -e "Usage:\n\thsplay <file> <bdaddr> [channel]"
+       exit
+fi
+
+FILE=$1
+BDADDR=$2
+CHANNEL=$3
+
+$MPG123 -q -s "$FILE" | $SOX -t raw -r 44100 -c 2 -s -w - -t raw -r 8000 -c 1 -s -w - | $HSTEST play - $BDADDR $CHANNEL
diff --git a/test/hstest.c b/test/hstest.c
new file mode 100644 (file)
index 0000000..08f2257
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <termios.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/rfcomm.h>
+
+static volatile int terminate = 0;
+
+static void sig_term(int sig) {
+       terminate = 1;
+}
+
+static int rfcomm_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t channel)
+{
+       struct sockaddr_rc addr;
+       int s;
+
+       if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
+               return -1;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, src);
+       addr.rc_channel = 0;
+       if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+               close(s);
+               return -1;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, dst);
+       addr.rc_channel = channel;
+       if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ){
+               close(s);
+               return -1;
+       }
+
+       return s;
+}
+
+static int sco_connect(bdaddr_t *src, bdaddr_t *dst, uint16_t *handle, uint16_t *mtu)
+{
+       struct sockaddr_sco addr;
+       struct sco_conninfo conn;
+       struct sco_options opts;
+       socklen_t size;
+       int s;
+
+       if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
+               return -1;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, src);
+
+       if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+               close(s);
+               return -1;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, dst);
+
+       if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ){
+               close(s);
+               return -1;
+       }
+
+       memset(&conn, 0, sizeof(conn));
+       size = sizeof(conn);
+
+       if (getsockopt(s, SOL_SCO, SCO_CONNINFO, &conn, &size) < 0) {
+               close(s);
+               return -1;
+       }
+
+       memset(&opts, 0, sizeof(opts));
+       size = sizeof(opts);
+
+       if (getsockopt(s, SOL_SCO, SCO_OPTIONS, &opts, &size) < 0) {
+               close(s);
+               return -1;
+       }
+
+       if (handle)
+               *handle = conn.hci_handle;
+
+       if (mtu)
+               *mtu = opts.mtu;
+
+       return s;
+}
+
+static void usage(void)
+{
+       printf("Usage:\n"
+               "\thstest play   <file> <bdaddr> [channel]\n"
+               "\thstest record <file> <bdaddr> [channel]\n");
+}
+
+#define PLAY   1
+#define RECORD 2
+
+int main(int argc, char *argv[])
+{
+       struct sigaction sa;
+
+       fd_set rfds;
+       struct timeval timeout;
+       unsigned char buf[2048], *p;
+       int maxfd, sel, rlen, wlen;
+
+       bdaddr_t local;
+       bdaddr_t bdaddr;
+       uint8_t channel;
+
+       char *filename;
+       mode_t filemode;
+       int err, mode = 0;
+       int dd, rd, sd, fd;
+       uint16_t sco_handle, sco_mtu, vs;
+
+       switch (argc) {
+       case 4:
+               str2ba(argv[3], &bdaddr);
+               channel = 6;
+               break;
+       case 5:
+               str2ba(argv[3], &bdaddr);
+               channel = atoi(argv[4]);
+               break;
+       default:
+               usage();
+               exit(-1);
+       }
+
+       if (strncmp(argv[1], "play", 4) == 0) {
+               mode = PLAY;
+               filemode = O_RDONLY;
+       } else if (strncmp(argv[1], "rec", 3) == 0) {
+               mode = RECORD;
+               filemode = O_WRONLY | O_CREAT | O_TRUNC;
+       } else {
+               usage();
+               exit(-1);
+       }
+
+       filename = argv[2];
+
+       hci_devba(0, &local);
+       dd = hci_open_dev(0);
+       hci_read_voice_setting(dd, &vs, 1000);
+       vs = htobs(vs);
+       fprintf(stderr, "Voice setting: 0x%04x\n", vs);
+       close(dd);
+       if (vs != 0x0060) {
+               fprintf(stderr, "The voice setting must be 0x0060\n");
+               return -1;
+       }
+
+       if (strcmp(filename, "-") == 0) {
+               switch (mode) {
+               case PLAY:
+                       fd = 0;
+                       break;
+               case RECORD:
+                       fd = 1;
+                       break;
+               default:
+                       return -1;
+               }
+       } else {
+               if ((fd = open(filename, filemode)) < 0) {
+                       perror("Can't open input/output file");
+                       return -1;
+               }
+       }
+
+       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_IGN;
+       sigaction(SIGCHLD, &sa, NULL);
+       sigaction(SIGPIPE, &sa, NULL);
+
+       if ((rd = rfcomm_connect(&local, &bdaddr, channel)) < 0) {
+               perror("Can't connect RFCOMM channel");
+               return -1;
+       }
+
+       fprintf(stderr, "RFCOMM channel connected\n");
+
+       if ((sd = sco_connect(&local, &bdaddr, &sco_handle, &sco_mtu)) < 0) {
+               perror("Can't connect SCO audio channel");
+               close(rd);
+               return -1;
+       }
+
+       fprintf(stderr, "SCO audio channel connected (handle %d, mtu %d)\n", sco_handle, sco_mtu);
+
+       if (mode == RECORD)
+               err = write(rd, "RING\r\n", 6);
+
+       maxfd = (rd > sd) ? rd : sd;
+
+       while (!terminate) {
+
+               FD_ZERO(&rfds);
+               FD_SET(rd, &rfds);
+               FD_SET(sd, &rfds);
+
+               timeout.tv_sec = 0;
+               timeout.tv_usec = 10000;
+
+               if ((sel = select(maxfd + 1, &rfds, NULL, NULL, &timeout)) > 0) {
+
+                       if (FD_ISSET(rd, &rfds)) {
+                               memset(buf, 0, sizeof(buf));
+                               rlen = read(rd, buf, sizeof(buf));
+                               if (rlen > 0) {
+                                       fprintf(stderr, "%s\n", buf);
+                                       wlen = write(rd, "OK\r\n", 4);
+                               }
+                       }
+
+                       if (FD_ISSET(sd, &rfds)) {
+                               memset(buf, 0, sizeof(buf));
+                               rlen = read(sd, buf, sizeof(buf));
+                               if (rlen > 0)
+                                       switch (mode) {
+                                       case PLAY:
+                                               rlen = read(fd, buf, rlen);
+
+                                               wlen = 0;
+                                               p = buf;
+                                               while (rlen > sco_mtu) {
+                                                       wlen += write(sd, p, sco_mtu);
+                                                       rlen -= sco_mtu;
+                                                       p += sco_mtu;
+                                               }
+                                               wlen += write(sd, p, rlen);
+                                               break;
+                                       case RECORD:
+                                               wlen = write(fd, buf, rlen);
+                                               break;
+                                       default:
+                                               break;
+                                       }
+                       }
+
+               }
+
+       }
+
+       close(sd);
+       sleep(5);
+       close(rd);
+
+       close(fd);
+
+       return 0;
+}
diff --git a/test/ipctest.c b/test/ipctest.c
new file mode 100644 (file)
index 0000000..9fdfac4
--- /dev/null
@@ -0,0 +1,1129 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include <glib.h>
+
+#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");
+
+               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]");
+               }
+
+               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/test/l2test.c b/test/l2test.c
new file mode 100644 (file)
index 0000000..3ac332f
--- /dev/null
@@ -0,0 +1,1379 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+
+#define NIBBLE_TO_ASCII(c)  ((c) < 0x0a ? (c) + 0x30 : (c) + 0x57)
+
+/* Test modes */
+enum {
+       SEND,
+       RECV,
+       RECONNECT,
+       MULTY,
+       DUMP,
+       CONNECT,
+       CRECV,
+       LSEND,
+       SENDDUMP,
+       LSENDDUMP,
+       LSENDRECV,
+       CSENDRECV,
+       INFOREQ,
+       PAIRING,
+};
+
+static unsigned char *buf;
+
+/* Default mtu */
+static int imtu = 672;
+static int omtu = 0;
+
+/* Default FCS option */
+static int fcs = 0x01;
+
+/* Default Transmission Window */
+static int txwin_size = 63;
+
+/* Default Max Transmission */
+static int max_transmit = 3;
+
+/* Default data size */
+static long data_size = -1;
+static long buffer_size = 2048;
+
+/* Default addr and psm and cid */
+static bdaddr_t bdaddr;
+static unsigned short psm = 0x1011;
+static unsigned short cid = 0;
+
+/* Default number of frames to send (-1 = infinite) */
+static int num_frames = -1;
+
+/* Default number of consecutive frames before the delay */
+static int count = 1;
+
+/* Default delay after sending count number of frames */
+static unsigned long delay = 0;
+
+static char *filename = NULL;
+
+static int rfcmode = 0;
+static int master = 0;
+static int auth = 0;
+static int encrypt = 0;
+static int secure = 0;
+static int socktype = SOCK_SEQPACKET;
+static int linger = 0;
+static int reliable = 0;
+static int timestamp = 0;
+static int defer_setup = 0;
+
+static float tv2fl(struct timeval tv)
+{
+       return (float)tv.tv_sec + (float)(tv.tv_usec/1000000.0);
+}
+
+static char *ltoh(unsigned long c, char* s)
+{
+       int c1;
+
+       c1     = (c >> 28) & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       c1     = (c >> 24) & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       c1     = (c >> 20) & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       c1     = (c >> 16) & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       c1     = (c >> 12) & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       c1     = (c >>  8) & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       c1     = (c >>  4) & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       c1     = c & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       *s     = 0;
+       return s;
+}
+
+static char *ctoh(char c, char* s)
+{
+       char c1;
+
+       c1     = (c >> 4) & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       c1     = c & 0x0f;
+       *(s++) = NIBBLE_TO_ASCII (c1);
+       *s     = 0;
+       return s;
+}
+
+static void hexdump(unsigned char *s, unsigned long l)
+{
+       char bfr[80];
+       char *pb;
+       unsigned long i, n = 0;
+
+       if (l == 0)
+               return;
+
+       while (n < l) {
+               pb = bfr;
+               pb = ltoh (n, pb);
+               *(pb++) = ':';
+               *(pb++) = ' ';
+               for (i = 0; i < 16; i++) {
+                       if (n + i >= l) {
+                               *(pb++) = ' ';
+                               *(pb++) = ' ';
+                       } else
+                               pb = ctoh (*(s + i), pb);
+                       *(pb++) = ' ';
+               }
+               *(pb++) = ' ';
+               for (i = 0; i < 16; i++) {
+                       if (n + i >= l)
+                               break;
+                       else
+                               *(pb++) = (isprint (*(s + i)) ? *(s + i) : '.');
+               }
+               *pb = 0;
+               n += 16;
+               s += 16;
+               puts(bfr);
+       }
+}
+
+static int do_connect(char *svr)
+{
+       struct sockaddr_l2 addr;
+       struct l2cap_options opts;
+       struct l2cap_conninfo conn;
+       socklen_t optlen;
+       int sk, opt;
+
+       /* Create socket */
+       sk = socket(PF_BLUETOOTH, socktype, BTPROTO_L2CAP);
+       if (sk < 0) {
+               syslog(LOG_ERR, "Can't create socket: %s (%d)",
+                                                       strerror(errno), errno);
+               return -1;
+       }
+
+       /* Bind to local address */
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, &bdaddr);
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "Can't bind socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Get default options */
+       memset(&opts, 0, sizeof(opts));
+       optlen = sizeof(opts);
+
+       if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) {
+               syslog(LOG_ERR, "Can't get default L2CAP options: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Set new options */
+       opts.omtu = omtu;
+       opts.imtu = imtu;
+       opts.mode = rfcmode;
+
+       opts.fcs = fcs;
+       opts.txwin_size = txwin_size;
+       opts.max_tx = max_transmit;
+
+       if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) {
+               syslog(LOG_ERR, "Can't set L2CAP options: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+#if 0
+       /* Enable SO_TIMESTAMP */
+       if (timestamp) {
+               int t = 1;
+
+               if (setsockopt(sk, SOL_SOCKET, SO_TIMESTAMP, &t, sizeof(t)) < 0) {
+                       syslog(LOG_ERR, "Can't enable SO_TIMESTAMP: %s (%d)",
+                                                       strerror(errno), errno);
+                       goto error;
+               }
+       }
+#endif
+
+       /* Enable SO_LINGER */
+       if (linger) {
+               struct linger l = { .l_onoff = 1, .l_linger = linger };
+
+               if (setsockopt(sk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) {
+                       syslog(LOG_ERR, "Can't enable SO_LINGER: %s (%d)",
+                                                       strerror(errno), errno);
+                       goto error;
+               }
+       }
+
+       /* Set link mode */
+       opt = 0;
+       if (reliable)
+               opt |= L2CAP_LM_RELIABLE;
+       if (master)
+               opt |= L2CAP_LM_MASTER;
+       if (auth)
+               opt |= L2CAP_LM_AUTH;
+       if (encrypt)
+               opt |= L2CAP_LM_ENCRYPT;
+       if (secure)
+               opt |= L2CAP_LM_SECURE;
+
+       if (setsockopt(sk, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) {
+               syslog(LOG_ERR, "Can't set L2CAP link mode: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Connect to remote device */
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       str2ba(svr, &addr.l2_bdaddr);
+       if (cid)
+               addr.l2_cid = htobs(cid);
+       else if (psm)
+               addr.l2_psm = htobs(psm);
+       else
+               goto error;
+
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0 ) {
+               syslog(LOG_ERR, "Can't connect: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Get current options */
+       memset(&opts, 0, sizeof(opts));
+       optlen = sizeof(opts);
+
+       if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) {
+               syslog(LOG_ERR, "Can't get L2CAP options: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Get connection information */
+       memset(&conn, 0, sizeof(conn));
+       optlen = sizeof(conn);
+
+       if (getsockopt(sk, SOL_L2CAP, L2CAP_CONNINFO, &conn, &optlen) < 0) {
+               syslog(LOG_ERR, "Can't get L2CAP connection information: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       syslog(LOG_INFO, "Connected [imtu %d, omtu %d, flush_to %d, "
+                               "mode %d, handle %d, class 0x%02x%02x%02x]",
+               opts.imtu, opts.omtu, opts.flush_to, opts.mode, conn.hci_handle,
+               conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]);
+
+       omtu = (opts.omtu > buffer_size) ? buffer_size : opts.omtu;
+       imtu = (opts.imtu > buffer_size) ? buffer_size : opts.imtu;
+
+       return sk;
+
+error:
+       close(sk);
+       return -1;
+}
+
+static void do_listen(void (*handler)(int sk))
+{
+       struct sockaddr_l2 addr;
+       struct l2cap_options opts;
+       struct l2cap_conninfo conn;
+       socklen_t optlen;
+       int sk, nsk, opt;
+       char ba[18];
+
+       /* Create socket */
+       sk = socket(PF_BLUETOOTH, socktype, BTPROTO_L2CAP);
+       if (sk < 0) {
+               syslog(LOG_ERR, "Can't create socket: %s (%d)",
+                                                       strerror(errno), errno);
+               exit(1);
+       }
+
+       /* Bind to local address */
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, &bdaddr);
+       if (cid)
+               addr.l2_cid = htobs(cid);
+       else if (psm)
+               addr.l2_psm = htobs(psm);
+       else
+               goto error;
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "Can't bind socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Set link mode */
+       opt = 0;
+       if (reliable)
+               opt |= L2CAP_LM_RELIABLE;
+       if (master)
+               opt |= L2CAP_LM_MASTER;
+       if (auth)
+               opt |= L2CAP_LM_AUTH;
+       if (encrypt)
+               opt |= L2CAP_LM_ENCRYPT;
+       if (secure)
+               opt |= L2CAP_LM_SECURE;
+
+       if (opt && setsockopt(sk, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) {
+               syslog(LOG_ERR, "Can't set L2CAP link mode: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Get default options */
+       memset(&opts, 0, sizeof(opts));
+       optlen = sizeof(opts);
+
+       if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) {
+               syslog(LOG_ERR, "Can't get default L2CAP options: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Set new options */
+       opts.omtu = omtu;
+       opts.imtu = imtu;
+       if (rfcmode > 0)
+               opts.mode = rfcmode;
+
+       opts.fcs = fcs;
+       opts.txwin_size = txwin_size;
+       opts.max_tx = max_transmit;
+
+       if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) {
+               syslog(LOG_ERR, "Can't set L2CAP options: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       if (socktype == SOCK_DGRAM) {
+               handler(sk);
+               return;
+       }
+
+       /* Enable deferred setup */
+       opt = defer_setup;
+
+       if (opt && setsockopt(sk, SOL_BLUETOOTH, BT_DEFER_SETUP,
+                                               &opt, sizeof(opt)) < 0) {
+               syslog(LOG_ERR, "Can't enable deferred setup : %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Listen for connections */
+       if (listen(sk, 10)) {
+               syslog(LOG_ERR, "Can not listen on the socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Check for socket address */
+       memset(&addr, 0, sizeof(addr));
+       optlen = sizeof(addr);
+
+       if (getsockname(sk, (struct sockaddr *) &addr, &optlen) < 0) {
+               syslog(LOG_ERR, "Can't get socket name: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       psm = btohs(addr.l2_psm);
+       cid = btohs(addr.l2_cid);
+
+       syslog(LOG_INFO, "Waiting for connection on psm %d ...", psm);
+
+       while (1) {
+               memset(&addr, 0, sizeof(addr));
+               optlen = sizeof(addr);
+
+               nsk = accept(sk, (struct sockaddr *) &addr, &optlen);
+               if (nsk < 0) {
+                       syslog(LOG_ERR, "Accept failed: %s (%d)",
+                                                       strerror(errno), errno);
+                       goto error;
+               }
+               if (fork()) {
+                       /* Parent */
+                       close(nsk);
+                       continue;
+               }
+               /* Child */
+               close(sk);
+
+               /* Get current options */
+               memset(&opts, 0, sizeof(opts));
+               optlen = sizeof(opts);
+
+               if (getsockopt(nsk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) {
+                       syslog(LOG_ERR, "Can't get L2CAP options: %s (%d)",
+                                                       strerror(errno), errno);
+                       if (!defer_setup) {
+                               close(nsk);
+                               goto error;
+                       }
+               }
+
+               /* Get connection information */
+               memset(&conn, 0, sizeof(conn));
+               optlen = sizeof(conn);
+
+               if (getsockopt(nsk, SOL_L2CAP, L2CAP_CONNINFO, &conn, &optlen) < 0) {
+                       syslog(LOG_ERR, "Can't get L2CAP connection information: %s (%d)",
+                                                       strerror(errno), errno);
+                       if (!defer_setup) {
+                               close(nsk);
+                               goto error;
+                       }
+               }
+
+               ba2str(&addr.l2_bdaddr, ba);
+               syslog(LOG_INFO, "Connect from %s [imtu %d, omtu %d, flush_to %d, "
+                                       "mode %d, handle %d, class 0x%02x%02x%02x]",
+                       ba, opts.imtu, opts.omtu, opts.flush_to, opts.mode, conn.hci_handle,
+                       conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]);
+
+               omtu = (opts.omtu > buffer_size) ? buffer_size : opts.omtu;
+               imtu = (opts.imtu > buffer_size) ? buffer_size : opts.imtu;
+
+#if 0
+               /* Enable SO_TIMESTAMP */
+               if (timestamp) {
+                       int t = 1;
+
+                       if (setsockopt(nsk, SOL_SOCKET, SO_TIMESTAMP, &t, sizeof(t)) < 0) {
+                               syslog(LOG_ERR, "Can't enable SO_TIMESTAMP: %s (%d)",
+                                                       strerror(errno), errno);
+                               goto error;
+                       }
+               }
+#endif
+
+               /* Enable SO_LINGER */
+               if (linger) {
+                       struct linger l = { .l_onoff = 1, .l_linger = linger };
+
+                       if (setsockopt(nsk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) {
+                               syslog(LOG_ERR, "Can't enable SO_LINGER: %s (%d)",
+                                                       strerror(errno), errno);
+                               close(nsk);
+                               goto error;
+                       }
+               }
+
+               /* Handle deferred setup */
+               if (defer_setup) {
+                       syslog(LOG_INFO, "Waiting for %d seconds",
+                                                       abs(defer_setup) - 1);
+                       sleep(abs(defer_setup) - 1);
+
+                       if (defer_setup < 0) {
+                               close(nsk);
+                               goto error;
+                       }
+               }
+
+               handler(nsk);
+
+               syslog(LOG_INFO, "Disconnect: %m");
+               exit(0);
+       }
+
+       return;
+
+error:
+       close(sk);
+       exit(1);
+}
+
+static void dump_mode(int sk)
+{
+       socklen_t optlen;
+       int opt, len;
+
+       if (data_size < 0)
+               data_size = imtu;
+
+       if (defer_setup) {
+               len = read(sk, buf, sizeof(buf));
+               if (len < 0)
+                       syslog(LOG_ERR, "Initial read error: %s (%d)",
+                                               strerror(errno), errno);
+               else
+                       syslog(LOG_INFO, "Initial bytes %d", len);
+       }
+
+       syslog(LOG_INFO, "Receiving ...");
+       while (1) {
+               fd_set rset;
+
+               FD_ZERO(&rset);
+               FD_SET(sk, &rset);
+
+               if (select(sk + 1, &rset, NULL, NULL, NULL) < 0)
+                       return;
+
+               if (!FD_ISSET(sk, &rset))
+                       continue;
+
+               len = read(sk, buf, data_size);
+               if (len <= 0) {
+                       if (len < 0) {
+                               if (reliable && (errno == ECOMM)) {
+                                       syslog(LOG_INFO, "L2CAP Error ECOMM - clearing error and continuing.");
+                                       optlen = sizeof(opt);
+                                       if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &opt, &optlen) < 0) {
+                                               syslog(LOG_ERR, "Couldn't getsockopt(SO_ERROR): %s (%d)",
+                                                       strerror(errno), errno);
+                                               return;
+                                       }
+                                       continue;
+                               } else {
+                                       syslog(LOG_ERR, "Read error: %s(%d)",
+                                                       strerror(errno), errno);
+                               }
+                       }
+                       return;
+               }
+
+               syslog(LOG_INFO, "Recevied %d bytes", len);
+               hexdump(buf, len);
+       }
+}
+
+static void recv_mode(int sk)
+{
+       struct timeval tv_beg, tv_end, tv_diff;
+       struct pollfd p;
+       char ts[30];
+       long total;
+       uint32_t seq;
+       socklen_t optlen;
+       int opt, len;
+
+       if (data_size < 0)
+               data_size = imtu;
+
+       if (defer_setup) {
+               len = read(sk, buf, sizeof(buf));
+               if (len < 0)
+                       syslog(LOG_ERR, "Initial read error: %s (%d)",
+                                               strerror(errno), errno);
+               else
+                       syslog(LOG_INFO, "Initial bytes %d", len);
+       }
+
+       syslog(LOG_INFO, "Receiving ...");
+
+       memset(ts, 0, sizeof(ts));
+
+       p.fd = sk;
+       p.events = POLLIN | POLLERR | POLLHUP;
+
+       seq = 0;
+       while (1) {
+               gettimeofday(&tv_beg, NULL);
+               total = 0;
+               while (total < data_size) {
+                       uint32_t sq;
+                       uint16_t l;
+                       int i;
+
+                       p.revents = 0;
+                       if (poll(&p, 1, -1) <= 0)
+                               return;
+
+                       if (p.revents & (POLLERR | POLLHUP))
+                               return;
+
+                       len = recv(sk, buf, data_size, 0);
+                       if (len < 0) {
+                               if (reliable && (errno == ECOMM)) {
+                                       syslog(LOG_INFO, "L2CAP Error ECOMM - clearing error and continuing.\n");
+                                       optlen = sizeof(opt);
+                                       if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &opt, &optlen) < 0) {
+                                               syslog(LOG_ERR, "Couldn't getsockopt(SO_ERROR): %s (%d)",
+                                                       strerror(errno), errno);
+                                               return;
+                                       }
+                                       continue;
+                               } else {
+                                       syslog(LOG_ERR, "Read failed: %s (%d)",
+                                               strerror(errno), errno);
+                               }
+                       }
+
+                       if (len < 6)
+                               break;
+
+                       if (timestamp) {
+                               struct timeval tv;
+
+                               if (ioctl(sk, SIOCGSTAMP, &tv) < 0) {
+                                       timestamp = 0;
+                                       memset(ts, 0, sizeof(ts));
+                               } else {
+                                       sprintf(ts, "[%ld.%ld] ",
+                                                       tv.tv_sec, tv.tv_usec);
+                               }
+                       }
+
+                       /* Check sequence */
+                       sq = btohl(*(uint32_t *) buf);
+                       if (seq != sq) {
+                               syslog(LOG_INFO, "seq missmatch: %d -> %d", seq, sq);
+                               seq = sq;
+                       }
+                       seq++;
+
+                       /* Check length */
+                       l = btohs(*(uint16_t *) (buf + 4));
+                       if (len != l) {
+                               syslog(LOG_INFO, "size missmatch: %d -> %d", len, l);
+                               continue;
+                       }
+
+                       /* Verify data */
+                       for (i = 6; i < len; i++) {
+                               if (buf[i] != 0x7f)
+                                       syslog(LOG_INFO, "data missmatch: byte %d 0x%2.2x", i, buf[i]);
+                       }
+
+                       total += len;
+               }
+               gettimeofday(&tv_end, NULL);
+
+               timersub(&tv_end, &tv_beg, &tv_diff);
+
+               syslog(LOG_INFO,"%s%ld bytes in %.2f sec, %.2f kB/s", ts, total,
+                       tv2fl(tv_diff), (float)(total / tv2fl(tv_diff) ) / 1024.0);
+       }
+}
+
+static void do_send(int sk)
+{
+       uint32_t seq;
+       int i, fd, len, buflen, size, sent;
+
+       syslog(LOG_INFO, "Sending ...");
+
+       if (data_size < 0)
+               data_size = omtu;
+
+       if (filename) {
+               fd = open(filename, O_RDONLY);
+               if (fd < 0) {
+                       syslog(LOG_ERR, "Open failed: %s (%d)",
+                                                       strerror(errno), errno);
+                       exit(1);
+               }
+
+               sent = 0;
+               size = read(fd, buf, data_size);
+               while (size > 0) {
+                       buflen = (size > omtu) ? omtu : size;
+
+                       len = send(sk, buf + sent, buflen, 0);
+
+                       sent += len;
+                       size -= len;
+               }
+               return;
+       } else {
+               for (i = 6; i < data_size; i++)
+                       buf[i] = 0x7f;
+       }
+
+       seq = 0;
+       while ((num_frames == -1) || (num_frames-- > 0)) {
+               *(uint32_t *) buf = htobl(seq);
+               *(uint16_t *) (buf + 4) = htobs(data_size);
+               seq++;
+
+               sent = 0;
+               size = data_size;
+               while (size > 0) {
+                       buflen = (size > omtu) ? omtu : size;
+
+                       len = send(sk, buf, buflen, 0);
+                       if (len < 0 || len != buflen) {
+                               syslog(LOG_ERR, "Send failed: %s (%d)",
+                                                       strerror(errno), errno);
+                               exit(1);
+                       }
+
+                       sent += len;
+                       size -= len;
+               }
+
+               if (num_frames && delay && count && !(seq % count))
+                       usleep(delay);
+       }
+}
+
+static void send_mode(int sk)
+{
+       do_send(sk);
+
+       syslog(LOG_INFO, "Closing channel ...");
+       if (shutdown(sk, SHUT_RDWR) < 0)
+               syslog(LOG_INFO, "Close failed: %m");
+       else
+               syslog(LOG_INFO, "Done");
+}
+
+static void senddump_mode(int sk)
+{
+       do_send(sk);
+
+       dump_mode(sk);
+}
+
+static void send_and_recv_mode(int sk)
+{
+       int flags;
+
+       if ((flags = fcntl(sk, F_GETFL, 0)) < 0)
+               flags = 0;
+       fcntl(sk, F_SETFL, flags | O_NONBLOCK);
+
+       /* fork for duplex channel */
+       if (fork())
+               send_mode(sk);
+       else
+               recv_mode(sk);
+       return;
+}
+
+static void reconnect_mode(char *svr)
+{
+       while (1) {
+               int sk = do_connect(svr);
+               close(sk);
+       }
+}
+
+static void connect_mode(char *svr)
+{
+       struct pollfd p;
+       int sk;
+
+       if ((sk = do_connect(svr)) < 0)
+               exit(1);
+
+       p.fd = sk;
+       p.events = POLLERR | POLLHUP;
+
+       while (1) {
+               p.revents = 0;
+               if (poll(&p, 1, 500))
+                       break;
+       }
+
+       syslog(LOG_INFO, "Disconnected");
+
+       close(sk);
+}
+
+static void multi_connect_mode(int argc, char *argv[])
+{
+       int i, n, sk;
+
+       while (1) {
+               for (n = 0; n < argc; n++) {
+                       for (i = 0; i < count; i++) {
+                               if (fork())
+                                       continue;
+
+                               /* Child */
+                               sk = do_connect(argv[n]);
+                               usleep(500);
+                               close(sk);
+                               exit(0);
+                       }
+               }
+               sleep(4);
+       }
+}
+
+static void info_request(char *svr)
+{
+       unsigned char buf[48];
+       l2cap_cmd_hdr *cmd = (l2cap_cmd_hdr *) buf;
+       l2cap_info_req *req = (l2cap_info_req *) (buf + L2CAP_CMD_HDR_SIZE);
+       l2cap_info_rsp *rsp = (l2cap_info_rsp *) (buf + L2CAP_CMD_HDR_SIZE);
+       uint16_t mtu;
+       uint32_t channels, mask = 0x0000;
+       struct sockaddr_l2 addr;
+       int sk, err;
+
+       sk = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
+       if (sk < 0) {
+               perror("Can't create socket");
+               return;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, &bdaddr);
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("Can't bind socket");
+               goto failed;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       str2ba(svr, &addr.l2_bdaddr);
+
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0 ) {
+               perror("Can't connect socket");
+               goto failed;
+       }
+
+       memset(buf, 0, sizeof(buf));
+       cmd->code  = L2CAP_INFO_REQ;
+       cmd->ident = 141;
+       cmd->len   = htobs(2);
+       req->type  = htobs(0x0001);
+
+       if (send(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_REQ_SIZE, 0) < 0) {
+               perror("Can't send info request");
+               goto failed;
+       }
+
+       err = recv(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_RSP_SIZE + 2, 0);
+       if (err < 0) {
+               perror("Can't receive info response");
+               goto failed;
+       }
+
+       switch (btohs(rsp->result)) {
+       case 0x0000:
+               memcpy(&mtu, rsp->data, sizeof(mtu));
+               printf("Connectionless MTU size is %d\n", btohs(mtu));
+               break;
+       case 0x0001:
+               printf("Connectionless MTU is not supported\n");
+               break;
+       }
+
+       memset(buf, 0, sizeof(buf));
+       cmd->code  = L2CAP_INFO_REQ;
+       cmd->ident = 142;
+       cmd->len   = htobs(2);
+       req->type  = htobs(0x0002);
+
+       if (send(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_REQ_SIZE, 0) < 0) {
+               perror("Can't send info request");
+               goto failed;
+       }
+
+       err = recv(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_RSP_SIZE + 4, 0);
+       if (err < 0) {
+               perror("Can't receive info response");
+               goto failed;
+       }
+
+       switch (btohs(rsp->result)) {
+       case 0x0000:
+               memcpy(&mask, rsp->data, sizeof(mask));
+               printf("Extended feature mask is 0x%04x\n", btohl(mask));
+               if (mask & 0x01)
+                       printf("  Flow control mode\n");
+               if (mask & 0x02)
+                       printf("  Retransmission mode\n");
+               if (mask & 0x04)
+                       printf("  Bi-directional QoS\n");
+               if (mask & 0x08)
+                       printf("  Enhanced Retransmission mode\n");
+               if (mask & 0x10)
+                       printf("  Streaming mode\n");
+               if (mask & 0x20)
+                       printf("  FCS Option\n");
+               if (mask & 0x40)
+                       printf("  Extended Flow Specification\n");
+               if (mask & 0x80)
+                       printf("  Fixed Channels\n");
+               if (mask & 0x0100)
+                       printf("  Extended Window Size\n");
+               if (mask & 0x0200)
+                       printf("  Unicast Connectionless Data Reception\n");
+               break;
+       case 0x0001:
+               printf("Extended feature mask is not supported\n");
+               break;
+       }
+
+       if (!(mask & 0x80))
+               goto failed;
+
+       memset(buf, 0, sizeof(buf));
+       cmd->code  = L2CAP_INFO_REQ;
+       cmd->ident = 143;
+       cmd->len   = htobs(2);
+       req->type  = htobs(0x0003);
+
+       if (send(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_REQ_SIZE, 0) < 0) {
+               perror("Can't send info request");
+               goto failed;
+       }
+
+       err = recv(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_RSP_SIZE + 8, 0);
+       if (err < 0) {
+               perror("Can't receive info response");
+               goto failed;
+       }
+
+       switch (btohs(rsp->result)) {
+       case 0x0000:
+               memcpy(&channels, rsp->data, sizeof(channels));
+               printf("Fixed channels list is 0x%04x\n", btohl(channels));
+               break;
+       case 0x0001:
+               printf("Fixed channels list is not supported\n");
+               break;
+       }
+
+failed:
+       close(sk);
+}
+
+static void do_pairing(char *svr)
+{
+       struct sockaddr_l2 addr;
+       int sk, opt;
+
+       sk = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
+       if (sk < 0) {
+               perror("Can't create socket");
+               return;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, &bdaddr);
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("Can't bind socket");
+               goto failed;
+       }
+
+       if (secure)
+               opt = L2CAP_LM_SECURE;
+       else
+               opt = L2CAP_LM_ENCRYPT;
+
+       if (setsockopt(sk, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) {
+               perror("Can't set link mode");
+               goto failed;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       str2ba(svr, &addr.l2_bdaddr);
+
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0 ) {
+               perror("Can't connect socket");
+               goto failed;
+       }
+
+       printf("Pairing successful\n");
+
+failed:
+       close(sk);
+}
+
+static void usage(void)
+{
+       printf("l2test - L2CAP testing\n"
+               "Usage:\n");
+       printf("\tl2test <mode> [options] [bdaddr]\n");
+       printf("Modes:\n"
+               "\t-r listen and receive\n"
+               "\t-w listen and send\n"
+               "\t-d listen and dump incoming data\n"
+               "\t-x listen, then send, then dump incoming data\n"
+               "\t-t listen, then send and receive at the same time\n"
+               "\t-q connect, then send and receive at the same time\n"
+               "\t-s connect and send\n"
+               "\t-u connect and receive\n"
+               "\t-n connect and be silent\n"
+               "\t-y connect, then send, then dump incoming data\n"
+               "\t-c connect, disconnect, connect, ...\n"
+               "\t-m multiple connects\n"
+               "\t-p trigger dedicated bonding\n"
+               "\t-z information request\n");
+
+       printf("Options:\n"
+               "\t[-b bytes] [-i device] [-P psm] [-J cid]\n"
+               "\t[-I imtu] [-O omtu]\n"
+               "\t[-L seconds] enable SO_LINGER\n"
+               "\t[-W seconds] enable deferred setup\n"
+               "\t[-B filename] use data packets from file\n"
+               "\t[-N num] send num frames (default = infinite)\n"
+               "\t[-C num] send num frames before delay (default = 1)\n"
+               "\t[-D milliseconds] delay after sending num frames (default = 0)\n"
+               "\t[-X mode] select retransmission/flow-control mode\n"
+               "\t[-F fcs] use CRC16 check (default = 1)\n"
+               "\t[-Q num] Max Transmit value (default = 3)\n"
+               "\t[-Z size] Transmission Window size (default = 63)\n"
+               "\t[-R] reliable mode\n"
+               "\t[-G] use connectionless channel (datagram)\n"
+               "\t[-U] use sock stream\n"
+               "\t[-A] request authentication\n"
+               "\t[-E] request encryption\n"
+               "\t[-S] secure connection\n"
+               "\t[-M] become master\n"
+               "\t[-T] enable timestamps\n");
+}
+
+int main(int argc, char *argv[])
+{
+       struct sigaction sa;
+       int opt, sk, mode = RECV, need_addr = 0;
+
+       bacpy(&bdaddr, BDADDR_ANY);
+
+       while ((opt=getopt(argc,argv,"rdscuwmntqxyzpb:i:P:I:O:J:B:N:L:W:C:D:X:F:Q:Z:RUGAESMT")) != EOF) {
+               switch(opt) {
+               case 'r':
+                       mode = RECV;
+                       break;
+
+               case 's':
+                       mode = SEND;
+                       need_addr = 1;
+                       break;
+
+               case 'w':
+                       mode = LSEND;
+                       break;
+
+               case 'u':
+                       mode = CRECV;
+                       need_addr = 1;
+                       break;
+
+               case 'd':
+                       mode = DUMP;
+                       break;
+
+               case 'c':
+                       mode = RECONNECT;
+                       need_addr = 1;
+                       break;
+
+               case 'n':
+                       mode = CONNECT;
+                       need_addr = 1;
+                       break;
+
+               case 'm':
+                       mode = MULTY;
+                       need_addr = 1;
+                       break;
+
+               case 't':
+                       mode = LSENDRECV;
+                       break;
+
+               case 'q':
+                       mode = CSENDRECV;
+                       need_addr = 1;
+                       break;
+
+               case 'x':
+                       mode = LSENDDUMP;
+                       break;
+
+               case 'y':
+                       mode = SENDDUMP;
+                       break;
+
+               case 'z':
+                       mode = INFOREQ;
+                       need_addr = 1;
+                       break;
+
+               case 'p':
+                       mode = PAIRING;
+                       need_addr = 1;
+                       break;
+
+               case 'b':
+                       data_size = atoi(optarg);
+                       break;
+
+               case 'i':
+                       if (!strncasecmp(optarg, "hci", 3))
+                               hci_devba(atoi(optarg + 3), &bdaddr);
+                       else
+                               str2ba(optarg, &bdaddr);
+                       break;
+
+               case 'P':
+                       psm = atoi(optarg);
+                       break;
+
+               case 'I':
+                       imtu = atoi(optarg);
+                       break;
+
+               case 'O':
+                       omtu = atoi(optarg);
+                       break;
+
+               case 'L':
+                       linger = atoi(optarg);
+                       break;
+
+               case 'W':
+                       defer_setup = atoi(optarg);
+                       break;
+
+               case 'B':
+                       filename = strdup(optarg);
+                       break;
+
+               case 'N':
+                       num_frames = atoi(optarg);
+                       break;
+
+               case 'C':
+                       count = atoi(optarg);
+                       break;
+
+               case 'D':
+                       delay = atoi(optarg) * 1000;
+                       break;
+
+               case 'X':
+                       if (strcasecmp(optarg, "ertm") == 0)
+                               rfcmode = L2CAP_MODE_ERTM;
+                       else
+                               rfcmode = atoi(optarg);
+                       break;
+
+               case 'F':
+                       fcs = atoi(optarg);
+                       break;
+
+               case 'R':
+                       reliable = 1;
+                       break;
+
+               case 'M':
+                       master = 1;
+                       break;
+
+               case 'A':
+                       auth = 1;
+                       break;
+
+               case 'E':
+                       encrypt = 1;
+                       break;
+
+               case 'S':
+                       secure = 1;
+                       break;
+
+               case 'G':
+                       socktype = SOCK_DGRAM;
+                       break;
+
+               case 'U':
+                       socktype = SOCK_STREAM;
+                       break;
+
+               case 'T':
+                       timestamp = 1;
+                       break;
+
+               case 'Q':
+                       max_transmit = atoi(optarg);
+                       break;
+
+               case 'Z':
+                       txwin_size = atoi(optarg);
+                       break;
+
+               case 'J':
+                       cid = atoi(optarg);
+                       break;
+
+               default:
+                       usage();
+                       exit(1);
+               }
+       }
+
+       if (need_addr && !(argc - optind)) {
+               usage();
+               exit(1);
+       }
+
+       if (data_size < 0)
+               buffer_size = (omtu > imtu) ? omtu : imtu;
+       else
+               buffer_size = data_size;
+
+       if (!(buf = malloc(buffer_size))) {
+               perror("Can't allocate data buffer");
+               exit(1);
+       }
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = SIG_IGN;
+       sa.sa_flags   = SA_NOCLDSTOP;
+       sigaction(SIGCHLD, &sa, NULL);
+
+       openlog("l2test", LOG_PERROR | LOG_PID, LOG_LOCAL0);
+
+       switch (mode) {
+               case RECV:
+                       do_listen(recv_mode);
+                       break;
+
+               case CRECV:
+                       sk = do_connect(argv[optind]);
+                       if (sk < 0)
+                               exit(1);
+                       recv_mode(sk);
+                       break;
+
+               case DUMP:
+                       do_listen(dump_mode);
+                       break;
+
+               case SEND:
+                       sk = do_connect(argv[optind]);
+                       if (sk < 0)
+                               exit(1);
+                       send_mode(sk);
+                       break;
+
+               case LSEND:
+                       do_listen(send_mode);
+                       break;
+
+               case RECONNECT:
+                       reconnect_mode(argv[optind]);
+                       break;
+
+               case MULTY:
+                       multi_connect_mode(argc - optind, argv + optind);
+                       break;
+
+               case CONNECT:
+                       connect_mode(argv[optind]);
+                       break;
+
+               case SENDDUMP:
+                       sk = do_connect(argv[optind]);
+                       if (sk < 0)
+                               exit(1);
+                       senddump_mode(sk);
+                       break;
+
+               case LSENDDUMP:
+                       do_listen(senddump_mode);
+                       break;
+
+               case LSENDRECV:
+                       do_listen(send_and_recv_mode);
+                       break;
+
+               case CSENDRECV:
+                       sk = do_connect(argv[optind]);
+                       if (sk < 0)
+                               exit(1);
+
+                       send_and_recv_mode(sk);
+                       break;
+
+               case INFOREQ:
+                       info_request(argv[optind]);
+                       exit(0);
+
+               case PAIRING:
+                       do_pairing(argv[optind]);
+                       exit(0);
+       }
+
+       syslog(LOG_INFO, "Exit");
+
+       closelog();
+
+       return 0;
+}
diff --git a/test/list-devices b/test/list-devices
new file mode 100755 (executable)
index 0000000..9120714
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+                                               "org.bluez.Manager")
+
+def extract_objects(object_list):
+       list = ""
+       for object in object_list:
+               val = str(object)
+               list = list + val[val.rfind("/") + 1:] + " "
+       return list
+
+def extract_uuids(uuid_list):
+       list = ""
+       for uuid in uuid_list:
+               if (uuid.endswith("-0000-1000-8000-00805f9b34fb")):
+                       if (uuid.startswith("0000")):
+                               val = "0x" + uuid[4:8]
+                       else:
+                               val = "0x" + uuid[0:8]
+               else:
+                       val = str(uuid)
+               list = list + val + " "
+       return list
+
+adapter_list = manager.ListAdapters()
+
+for i in adapter_list:
+       adapter = dbus.Interface(bus.get_object("org.bluez", i),
+                                                       "org.bluez.Adapter")
+       print "[ " + i + " ]"
+
+       properties  = adapter.GetProperties()
+       for key in properties.keys():
+               value = properties[key]
+               if (key == "Devices"):
+                       list = extract_objects(value)
+                       print "    %s = %s" % (key, list)
+               elif (key == "UUIDs"):
+                       list = extract_uuids(value)
+                       print "    %s = %s" % (key, list)
+               else:
+                       print "    %s = %s" % (key, value)
+
+       try:
+               device_list = properties["Devices"]
+       except:
+               device_list = []
+
+       for n in device_list:
+               device = dbus.Interface(bus.get_object("org.bluez", n),
+                                                       "org.bluez.Device")
+               print "    [ " + n + " ]"
+
+               properties = device.GetProperties()
+               for key in properties.keys():
+                       value = properties[key]
+                       if (key == "Nodes"):
+                               list = extract_objects(value)
+                               print "        %s = %s" % (key, list)
+                       elif (key == "UUIDs"):
+                               list = extract_uuids(value)
+                               print "        %s = %s" % (key, list)
+                       elif (key == "Class"):
+                               print "        %s = 0x%06x" % (key, value)
+                       else:
+                               print "        %s = %s" % (key, value)
+
+               try:
+                       node_list = properties["Nodes"]
+               except:
+                       node_list = []
+
+               for x in node_list:
+                       node = dbus.Interface(bus.get_object("org.bluez", x),
+                                                       "org.bluez.Node")
+                       print "        [ " + x + " ]"
+
+                       properties = node.GetProperties()
+                       for key in properties.keys():
+                               print "            %s = %s" % (key, properties[key])
+
+       print
diff --git a/test/lmptest.c b/test/lmptest.c
new file mode 100644 (file)
index 0000000..549ae12
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#if 0
+#define OCF_ERICSSON_SEND_LMP          0x0021
+typedef struct {
+       uint16_t handle;
+       uint8_t  length;
+       uint8_t  data[17];
+} __attribute__ ((packed)) ericsson_send_lmp_cp;
+#define ERICSSON_SEND_LMP_CP_SIZE 20
+
+static int ericsson_send_lmp(int dd, uint16_t handle, uint8_t length, uint8_t *data)
+{
+       struct hci_request rq;
+       ericsson_send_lmp_cp cp;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = htobs(handle);
+       cp.length = length;
+       memcpy(cp.data, data, length);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = OCF_ERICSSON_SEND_LMP;
+       rq.cparam = &cp;
+       rq.clen   = ERICSSON_SEND_LMP_CP_SIZE;
+       rq.rparam = NULL;
+       rq.rlen   = 0;
+
+       if (hci_send_req(dd, &rq, 1000) < 0)
+               return -1;
+
+       return 0;
+}
+#endif
+
+#define OCF_ERICSSON_WRITE_EVENTS      0x0043
+typedef struct {
+       uint8_t mask;
+       uint8_t opcode;
+       uint8_t opcode_ext;
+} __attribute__ ((packed)) ericsson_write_events_cp;
+#define ERICSSON_WRITE_EVENTS_CP_SIZE 3
+
+static int ericsson_write_events(int dd, uint8_t mask)
+{
+       struct hci_request rq;
+       ericsson_write_events_cp cp;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.mask = mask;
+       cp.opcode = 0x00;
+       cp.opcode_ext = 0x00;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = OCF_ERICSSON_WRITE_EVENTS;
+       rq.cparam = &cp;
+       rq.clen   = ERICSSON_WRITE_EVENTS_CP_SIZE;
+       rq.rparam = NULL;
+       rq.rlen   = 0;
+
+       if (hci_send_req(dd, &rq, 1000) < 0)
+               return -1;
+
+       return 0;
+}
+
+static void usage(void)
+{
+       printf("lmptest - Utility for testing special LMP functions\n\n");
+       printf("Usage:\n"
+               "\tlmptest [-i <dev>]\n");
+}
+
+static struct option main_options[] = {
+       { "device",     1, 0, 'i' },
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       struct hci_version ver;
+       int dd, opt, dev = 0;
+
+       while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'i':
+                       dev = hci_devid(optarg);
+                       if (dev < 0) {
+                               perror("Invalid device");
+                               exit(1);
+                       }
+                       break;
+
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       dd = hci_open_dev(dev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (hci_read_local_version(dd, &ver, 1000) < 0) {
+               fprintf(stderr, "Can't read version for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               exit(1);
+       }
+
+       if (ver.manufacturer != 37 && ver.manufacturer != 48) {
+               fprintf(stderr, "Can't find supported device hci%d: %s (%d)\n",
+                                               dev, strerror(ENOSYS), ENOSYS);
+               hci_close_dev(dd);
+               exit(1);
+       }
+
+       if (ericsson_write_events(dd, 0x03) < 0) {
+               fprintf(stderr, "Can't activate events for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+
+       return 0;
+}
diff --git a/test/monitor-bluetooth b/test/monitor-bluetooth
new file mode 100755 (executable)
index 0000000..a5e5300
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+
+import gobject
+
+import dbus
+import dbus.mainloop.glib
+
+def property_changed(name, value, path, interface):
+       iface = interface[interface.rfind(".") + 1:]
+       val = str(value)
+       print "{%s.PropertyChanged} [%s] %s = %s" % (iface, path, name, val)
+
+def object_signal(value, path, interface, member):
+       iface = interface[interface.rfind(".") + 1:]
+       val = str(value)
+       print "{%s.%s} [%s] Path = %s" % (iface, member, path, val)
+
+if __name__ == '__main__':
+       dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+       bus = dbus.SystemBus()
+
+       bus.add_signal_receiver(property_changed, bus_name="org.bluez",
+                                       signal_name = "PropertyChanged",
+                                               path_keyword="path",
+                                               interface_keyword="interface")
+
+       bus.add_signal_receiver(object_signal, bus_name="org.bluez",
+                                       signal_name = "AdapterAdded",
+                                               path_keyword="path",
+                                               member_keyword="member",
+                                               interface_keyword="interface")
+       bus.add_signal_receiver(object_signal, bus_name="org.bluez",
+                                       signal_name = "AdapterRemoved",
+                                               path_keyword="path",
+                                               member_keyword="member",
+                                               interface_keyword="interface")
+       bus.add_signal_receiver(object_signal, bus_name="org.bluez",
+                                       signal_name = "DefaultAdapterChanged",
+                                               path_keyword="path",
+                                               member_keyword="member",
+                                               interface_keyword="interface")
+
+       bus.add_signal_receiver(object_signal, bus_name="org.bluez",
+                                       signal_name = "DeviceCreated",
+                                               path_keyword="path",
+                                               member_keyword="member",
+                                               interface_keyword="interface")
+       bus.add_signal_receiver(object_signal, bus_name="org.bluez",
+                                       signal_name = "DeviceRemoved",
+                                               path_keyword="path",
+                                               member_keyword="member",
+                                               interface_keyword="interface")
+
+       mainloop = gobject.MainLoop()
+       mainloop.run()
diff --git a/test/rctest.1 b/test/rctest.1
new file mode 100644 (file)
index 0000000..dfedbef
--- /dev/null
@@ -0,0 +1,90 @@
+.TH RCTEST 1 "Jul 6 2009" BlueZ ""
+.SH NAME
+rctest \- RFCOMM testing
+.SH SYNOPSIS
+.B rctest
+<\fImode\fR> [\fIoptions\fR] [\fIbdaddr\fR]
+
+.SH DESCRIPTION
+.LP
+.B
+rctest
+is used to test RFCOMM communications on the BlueZ stack
+
+.SH MODES
+.TP
+.B -r
+listen and receive
+.TP
+.B -w
+listen and send
+.TP
+.B -d
+listen and dump incoming data
+.TP
+.B -s
+connect and send
+.TP
+.B -u
+connect and receive
+.TP
+.B -n
+connect and be silent
+.TP
+.B -c
+connect, disconnect, connect, ...
+.TP
+.B -m
+multiple connects
+
+.SH OPTIONS
+.TP
+.BI -b\  bytes
+send/receive \fIbytes\fR bytes
+.TP
+.BI -i\  device
+select the specified \fIdevice\fR
+.TP
+.BI -P\  channel
+select the specified \fIchannel\fR
+.TP
+.BI -U\  uuid
+select the specified \fIuuid\fR
+.TP
+.BI -L\  seconds
+enable SO_LINGER options for \fIseconds\fR
+.TP
+.BI -W\  seconds
+enable deferred setup for \fIseconds\fR
+.TP
+.BI -B\  filename
+use data packets from \fIfilename\fR
+.TP
+.BI -N\  num
+send \fInum\fR frames
+.TP
+.BI -C\  num
+send \fInum\fR frames before delay (default: 1)
+.TP
+.BI -D\  milliseconds
+delay \fImilliseconds\fR after sending \fInum\fR frames (default: 0)
+.TP
+.B -A
+request authentication
+.TP
+.B -E
+request encryption
+.TP
+.B -S
+secure connection
+.TP
+.B -M
+become master
+.TP
+.B -T
+enable timestamps
+
+.SH AUTHORS
+Written by Marcel Holtmann <marcel@holtmann.org> and Maxim Krasnyansky
+<maxk@qualcomm.com>, man page by Filippo Giunchedi <filippo@debian.org>
+.PP
diff --git a/test/rctest.c b/test/rctest.c
new file mode 100644 (file)
index 0000000..b3804f5
--- /dev/null
@@ -0,0 +1,781 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+/* Test modes */
+enum {
+       SEND,
+       RECV,
+       RECONNECT,
+       MULTY,
+       DUMP,
+       CONNECT,
+       CRECV,
+       LSEND
+};
+
+static unsigned char *buf;
+
+/* Default data size */
+static long data_size = 127;
+static long num_frames = -1;
+
+/* Default number of consecutive frames before the delay */
+static int count = 1;
+
+/* Default delay after sending count number of frames */
+static unsigned long delay = 0;
+
+/* Default addr and channel */
+static bdaddr_t bdaddr;
+static uint16_t uuid = 0x0000;
+static uint8_t channel = 10;
+
+static char *filename = NULL;
+
+static int master = 0;
+static int auth = 0;
+static int encrypt = 0;
+static int secure = 0;
+static int socktype = SOCK_STREAM;
+static int linger = 0;
+static int timestamp = 0;
+static int defer_setup = 0;
+
+static float tv2fl(struct timeval tv)
+{
+       return (float)tv.tv_sec + (float)(tv.tv_usec/1000000.0);
+}
+
+static uint8_t get_channel(const char *svr, uint16_t uuid)
+{
+       sdp_session_t *sdp;
+       sdp_list_t *srch, *attrs, *rsp;
+       uuid_t svclass;
+       uint16_t attr;
+       bdaddr_t dst;
+       uint8_t channel = 0;
+       int err;
+
+       str2ba(svr, &dst);
+
+       sdp = sdp_connect(&bdaddr, &dst, SDP_RETRY_IF_BUSY);
+       if (!sdp)
+               return 0;
+
+       sdp_uuid16_create(&svclass, uuid);
+       srch = sdp_list_append(NULL, &svclass);
+
+       attr = SDP_ATTR_PROTO_DESC_LIST;
+       attrs = sdp_list_append(NULL, &attr);
+
+       err = sdp_service_search_attr_req(sdp, srch,
+                                       SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp);
+       if (err)
+               goto done;
+
+       for (; rsp; rsp = rsp->next) {
+               sdp_record_t *rec = (sdp_record_t *) rsp->data;
+               sdp_list_t *protos;
+
+               if (!sdp_get_access_protos(rec, &protos)) {
+                       channel = sdp_get_proto_port(protos, RFCOMM_UUID);
+                       if (channel > 0)
+                               break;
+               }
+       }
+
+done:
+       sdp_close(sdp);
+
+       return channel;
+}
+
+static int do_connect(const char *svr)
+{
+       struct sockaddr_rc addr;
+       struct rfcomm_conninfo conn;
+       socklen_t optlen;
+       int sk, opt;
+
+       if (uuid != 0x0000)
+               channel = get_channel(svr, uuid);
+
+       if (channel == 0) {
+               syslog(LOG_ERR, "Can't get channel number");
+               return -1;
+       }
+
+       /* Create socket */
+       sk = socket(PF_BLUETOOTH, socktype, BTPROTO_RFCOMM);
+       if (sk < 0) {
+               syslog(LOG_ERR, "Can't create socket: %s (%d)",
+                                                       strerror(errno), errno);
+               return -1;
+       }
+
+       /* Bind to local address */
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, &bdaddr);
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "Can't bind socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+#if 0
+       /* Enable SO_TIMESTAMP */
+       if (timestamp) {
+               int t = 1;
+
+               if (setsockopt(sk, SOL_SOCKET, SO_TIMESTAMP, &t, sizeof(t)) < 0) {
+                       syslog(LOG_ERR, "Can't enable SO_TIMESTAMP: %s (%d)",
+                                                       strerror(errno), errno);
+                       goto error;
+               }
+       }
+#endif
+
+       /* Enable SO_LINGER */
+       if (linger) {
+               struct linger l = { .l_onoff = 1, .l_linger = linger };
+
+               if (setsockopt(sk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) {
+                       syslog(LOG_ERR, "Can't enable SO_LINGER: %s (%d)",
+                                                       strerror(errno), errno);
+                       goto error;
+               }
+       }
+
+       /* Set link mode */
+       opt = 0;
+       if (master)
+               opt |= RFCOMM_LM_MASTER;
+       if (auth)
+               opt |= RFCOMM_LM_AUTH;
+       if (encrypt)
+               opt |= RFCOMM_LM_ENCRYPT;
+       if (secure)
+               opt |= RFCOMM_LM_SECURE;
+
+       if (opt && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0) {
+               syslog(LOG_ERR, "Can't set RFCOMM link mode: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Connect to remote device */
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       str2ba(svr, &addr.rc_bdaddr);
+       addr.rc_channel = channel;
+
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "Can't connect: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Get connection information */
+       memset(&conn, 0, sizeof(conn));
+       optlen = sizeof(conn);
+
+       if (getsockopt(sk, SOL_RFCOMM, RFCOMM_CONNINFO, &conn, &optlen) < 0) {
+               syslog(LOG_ERR, "Can't get RFCOMM connection information: %s (%d)",
+                                                       strerror(errno), errno);
+               //goto error;
+       }
+
+       syslog(LOG_INFO, "Connected [handle %d, class 0x%02x%02x%02x]",
+               conn.hci_handle,
+               conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]);
+
+       return sk;
+
+error:
+       close(sk);
+       return -1;
+}
+
+static void do_listen(void (*handler)(int sk))
+{
+       struct sockaddr_rc addr;
+       struct rfcomm_conninfo conn;
+       socklen_t optlen;
+       int sk, nsk, opt;
+       char ba[18];
+
+       /* Create socket */
+       sk = socket(PF_BLUETOOTH, socktype, BTPROTO_RFCOMM);
+       if (sk < 0) {
+               syslog(LOG_ERR, "Can't create socket: %s (%d)",
+                                                       strerror(errno), errno);
+               exit(1);
+       }
+
+       /* Bind to local address */
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, &bdaddr);
+       addr.rc_channel = channel;
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "Can't bind socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Set link mode */
+       opt = 0;
+       if (master)
+               opt |= RFCOMM_LM_MASTER;
+       if (auth)
+               opt |= RFCOMM_LM_AUTH;
+       if (encrypt)
+               opt |= RFCOMM_LM_ENCRYPT;
+       if (secure)
+               opt |= RFCOMM_LM_SECURE;
+
+       if (opt && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0) {
+               syslog(LOG_ERR, "Can't set RFCOMM link mode: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Enable deferred setup */
+       opt = defer_setup;
+
+       if (opt && setsockopt(sk, SOL_BLUETOOTH, BT_DEFER_SETUP,
+                                               &opt, sizeof(opt)) < 0) {
+               syslog(LOG_ERR, "Can't enable deferred setup : %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Listen for connections */
+       if (listen(sk, 10)) {
+               syslog(LOG_ERR,"Can not listen on the socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Check for socket address */
+       memset(&addr, 0, sizeof(addr));
+       optlen = sizeof(addr);
+
+       if (getsockname(sk, (struct sockaddr *) &addr, &optlen) < 0) {
+               syslog(LOG_ERR, "Can't get socket name: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       channel = addr.rc_channel;
+
+       syslog(LOG_INFO, "Waiting for connection on channel %d ...", channel);
+
+       while (1) {
+               memset(&addr, 0, sizeof(addr));
+               optlen = sizeof(addr);
+
+               nsk = accept(sk, (struct sockaddr *) &addr, &optlen);
+               if (nsk < 0) {
+                       syslog(LOG_ERR,"Accept failed: %s (%d)",
+                                                       strerror(errno), errno);
+                       goto error;
+               }
+               if (fork()) {
+                       /* Parent */
+                       close(nsk);
+                       continue;
+               }
+               /* Child */
+               close(sk);
+
+               /* Get connection information */
+               memset(&conn, 0, sizeof(conn));
+               optlen = sizeof(conn);
+
+               if (getsockopt(nsk, SOL_RFCOMM, RFCOMM_CONNINFO, &conn, &optlen) < 0) {
+                       syslog(LOG_ERR, "Can't get RFCOMM connection information: %s (%d)",
+                                                       strerror(errno), errno);
+                       //close(nsk);
+                       //goto error;
+               }
+
+               ba2str(&addr.rc_bdaddr, ba);
+               syslog(LOG_INFO, "Connect from %s [handle %d, class 0x%02x%02x%02x]",
+                       ba, conn.hci_handle,
+                       conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]);
+
+#if 0
+               /* Enable SO_TIMESTAMP */
+               if (timestamp) {
+                       int t = 1;
+
+                       if (setsockopt(nsk, SOL_SOCKET, SO_TIMESTAMP, &t, sizeof(t)) < 0) {
+                               syslog(LOG_ERR, "Can't enable SO_TIMESTAMP: %s (%d)",
+                                                       strerror(errno), errno);
+                               goto error;
+                       }
+               }
+#endif
+
+               /* Enable SO_LINGER */
+               if (linger) {
+                       struct linger l = { .l_onoff = 1, .l_linger = linger };
+
+                       if (setsockopt(nsk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) {
+                               syslog(LOG_ERR, "Can't enable SO_LINGER: %s (%d)",
+                                                       strerror(errno), errno);
+                               close(nsk);
+                               goto error;
+                       }
+               }
+
+               /* Handle deferred setup */
+               if (defer_setup) {
+                       syslog(LOG_INFO, "Waiting for %d seconds",
+                                                       abs(defer_setup) - 1);
+                       sleep(abs(defer_setup) - 1);
+
+                       if (defer_setup < 0) {
+                               close(nsk);
+                               goto error;
+                       }
+               }
+
+               handler(nsk);
+
+               syslog(LOG_INFO, "Disconnect: %m");
+               exit(0);
+       }
+
+       return;
+
+error:
+       close(sk);
+       exit(1);
+}
+
+static void dump_mode(int sk)
+{
+       int len;
+
+       syslog(LOG_INFO, "Receiving ...");
+       while ((len = read(sk, buf, data_size)) > 0)
+               syslog(LOG_INFO, "Recevied %d bytes", len);
+}
+
+static void recv_mode(int sk)
+{
+       struct timeval tv_beg, tv_end, tv_diff;
+       char ts[30];
+       long total;
+       uint32_t seq;
+
+       syslog(LOG_INFO, "Receiving ...");
+
+       memset(ts, 0, sizeof(ts));
+
+       seq = 0;
+       while (1) {
+               gettimeofday(&tv_beg,NULL);
+               total = 0;
+               while (total < data_size) {
+                       //uint32_t sq;
+                       //uint16_t l;
+                       int r;
+
+                       if ((r = recv(sk, buf, data_size, 0)) < 0) {
+                               if (r < 0)
+                                       syslog(LOG_ERR, "Read failed: %s (%d)",
+                                                       strerror(errno), errno);
+                               return;
+                       }
+
+                       if (timestamp) {
+                               struct timeval tv;
+
+                               if (ioctl(sk, SIOCGSTAMP, &tv) < 0) {
+                                       timestamp = 0;
+                                       memset(ts, 0, sizeof(ts));
+                               } else {
+                                       sprintf(ts, "[%ld.%ld] ",
+                                                       tv.tv_sec, tv.tv_usec);
+                               }
+                       }
+
+#if 0
+                       /* Check sequence */
+                       sq = btohl(*(uint32_t *) buf);
+                       if (seq != sq) {
+                               syslog(LOG_INFO, "seq missmatch: %d -> %d", seq, sq);
+                               seq = sq;
+                       }
+                       seq++;
+
+                       /* Check length */
+                       l = btohs(*(uint16_t *) (buf + 4));
+                       if (r != l) {
+                               syslog(LOG_INFO, "size missmatch: %d -> %d", r, l);
+                               continue;
+                       }
+
+                       /* Verify data */
+                       for (i = 6; i < r; i++) {
+                               if (buf[i] != 0x7f)
+                                       syslog(LOG_INFO, "data missmatch: byte %d 0x%2.2x", i, buf[i]);
+                       }
+#endif
+                       total += r;
+               }
+               gettimeofday(&tv_end,NULL);
+
+               timersub(&tv_end,&tv_beg,&tv_diff);
+
+               syslog(LOG_INFO,"%s%ld bytes in %.2f sec, %.2f kB/s", ts, total,
+                       tv2fl(tv_diff), (float)(total / tv2fl(tv_diff) ) / 1024.0);
+       }
+}
+
+static void do_send(int sk)
+{
+       uint32_t seq;
+       int i, fd, len;
+
+       syslog(LOG_INFO,"Sending ...");
+
+       if (filename) {
+               fd = open(filename, O_RDONLY);
+               if (fd < 0) {
+                       syslog(LOG_ERR, "Open failed: %s (%d)",
+                                                       strerror(errno), errno);
+                       exit(1);
+               }
+               len = read(fd, buf, data_size);
+               send(sk, buf, len, 0);
+               return;
+       } else {
+               for (i = 6; i < data_size; i++)
+                       buf[i] = 0x7f;
+       }
+
+       seq = 0;
+       while ((num_frames == -1) || (num_frames-- > 0)) {
+               *(uint32_t *) buf = htobl(seq);
+               *(uint16_t *) (buf + 4) = htobs(data_size);
+               seq++;
+
+               if (send(sk, buf, data_size, 0) <= 0) {
+                       syslog(LOG_ERR, "Send failed: %s (%d)",
+                                                       strerror(errno), errno);
+                       exit(1);
+               }
+
+               if (num_frames && delay && count && !(seq % count))
+                       usleep(delay);
+       }
+}
+
+static void send_mode(int sk)
+{
+       do_send(sk);
+
+       syslog(LOG_INFO, "Closing channel ...");
+       if (shutdown(sk, SHUT_RDWR) < 0)
+               syslog(LOG_INFO, "Close failed: %m");
+       else
+               syslog(LOG_INFO, "Done");
+}
+
+static void reconnect_mode(char *svr)
+{
+       while(1) {
+               int sk = do_connect(svr);
+               close(sk);
+       }
+}
+
+static void multi_connect_mode(int argc, char *argv[])
+{
+       int i, n, sk;
+
+       while (1) {
+               for (n = 0; n < argc; n++) {
+                       for (i = 0; i < count; i++) {
+                               if (fork())
+                                       continue;
+
+                               /* Child */
+                               sk = do_connect(argv[n]);
+                               usleep(500);
+                               close(sk);
+                               exit(0);
+                       }
+               }
+               sleep(4);
+       }
+}
+
+static void usage(void)
+{
+       printf("rctest - RFCOMM testing\n"
+               "Usage:\n");
+       printf("\trctest <mode> [options] [bdaddr]\n");
+       printf("Modes:\n"
+               "\t-r listen and receive\n"
+               "\t-w listen and send\n"
+               "\t-d listen and dump incoming data\n"
+               "\t-s connect and send\n"
+               "\t-u connect and receive\n"
+               "\t-n connect and be silent\n"
+               "\t-c connect, disconnect, connect, ...\n"
+               "\t-m multiple connects\n");
+
+       printf("Options:\n"
+               "\t[-b bytes] [-i device] [-P channel] [-U uuid]\n"
+               "\t[-L seconds] enabled SO_LINGER option\n"
+               "\t[-W seconds] enable deferred setup\n"
+               "\t[-B filename] use data packets from file\n"
+               "\t[-N num] number of frames to send\n"
+               "\t[-C num] send num frames before delay (default = 1)\n"
+               "\t[-D milliseconds] delay after sending num frames (default = 0)\n"
+               "\t[-A] request authentication\n"
+               "\t[-E] request encryption\n"
+               "\t[-S] secure connection\n"
+               "\t[-M] become master\n"
+               "\t[-T] enable timestamps\n");
+}
+
+int main(int argc, char *argv[])
+{
+       struct sigaction sa;
+       int opt, sk, mode = RECV, need_addr = 0;
+
+       bacpy(&bdaddr, BDADDR_ANY);
+
+       while ((opt=getopt(argc,argv,"rdscuwmnb:i:P:U:B:N:MAESL:W:C:D:T")) != EOF) {
+               switch (opt) {
+               case 'r':
+                       mode = RECV;
+                       break;
+
+               case 's':
+                       mode = SEND;
+                       need_addr = 1;
+                       break;
+
+               case 'w':
+                       mode = LSEND;
+                       break;
+
+               case 'u':
+                       mode = CRECV;
+                       need_addr = 1;
+                       break;
+
+               case 'd':
+                       mode = DUMP;
+                       break;
+
+               case 'c':
+                       mode = RECONNECT;
+                       need_addr = 1;
+                       break;
+
+               case 'n':
+                       mode = CONNECT;
+                       need_addr = 1;
+                       break;
+
+               case 'm':
+                       mode = MULTY;
+                       need_addr = 1;
+                       break;
+
+               case 'b':
+                       data_size = atoi(optarg);
+                       break;
+
+               case 'i':
+                       if (!strncasecmp(optarg, "hci", 3))
+                               hci_devba(atoi(optarg + 3), &bdaddr);
+                       else
+                               str2ba(optarg, &bdaddr);
+                       break;
+
+               case 'P':
+                       channel = atoi(optarg);
+                       break;
+
+               case 'U':
+                       if (!strcasecmp(optarg, "spp"))
+                               uuid = SERIAL_PORT_SVCLASS_ID;
+                       else if (!strncasecmp(optarg, "0x", 2))
+                               uuid = strtoul(optarg + 2, NULL, 16);
+                       else
+                               uuid = atoi(optarg);
+                       break;
+
+               case 'M':
+                       master = 1;
+                       break;
+
+               case 'A':
+                       auth = 1;
+                       break;
+
+               case 'E':
+                       encrypt = 1;
+                       break;
+
+               case 'S':
+                       secure = 1;
+                       break;
+
+               case 'L':
+                       linger = atoi(optarg);
+                       break;
+
+               case 'W':
+                       defer_setup = atoi(optarg);
+                       break;
+
+               case 'B':
+                       filename = strdup(optarg);
+                       break;
+
+               case 'N':
+                       num_frames = atoi(optarg);
+                       break;
+
+               case 'C':
+                       count = atoi(optarg);
+                       break;
+
+               case 'D':
+                       delay = atoi(optarg) * 1000;
+                       break;
+
+               case 'T':
+                       timestamp = 1;
+                       break;
+
+               default:
+                       usage();
+                       exit(1);
+               }
+       }
+
+       if (need_addr && !(argc - optind)) {
+               usage();
+               exit(1);
+       }
+
+       if (!(buf = malloc(data_size))) {
+               perror("Can't allocate data buffer");
+               exit(1);
+       }
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = SIG_IGN;
+       sa.sa_flags   = SA_NOCLDSTOP;
+       sigaction(SIGCHLD, &sa, NULL);
+
+       openlog("rctest", LOG_PERROR | LOG_PID, LOG_LOCAL0);
+
+       switch (mode) {
+               case RECV:
+                       do_listen(recv_mode);
+                       break;
+
+               case CRECV:
+                       sk = do_connect(argv[optind]);
+                       if (sk < 0)
+                               exit(1);
+                       recv_mode(sk);
+                       break;
+
+               case DUMP:
+                       do_listen(dump_mode);
+                       break;
+
+               case SEND:
+                       sk = do_connect(argv[optind]);
+                       if (sk < 0)
+                               exit(1);
+                       send_mode(sk);
+                       break;
+
+               case LSEND:
+                       do_listen(send_mode);
+                       break;
+
+               case RECONNECT:
+                       reconnect_mode(argv[optind]);
+                       break;
+
+               case MULTY:
+                       multi_connect_mode(argc - optind, argv + optind);
+                       break;
+
+               case CONNECT:
+                       sk = do_connect(argv[optind]);
+                       if (sk < 0)
+                               exit(1);
+                       dump_mode(sk);
+                       break;
+       }
+
+       syslog(LOG_INFO, "Exit");
+
+       closelog();
+
+       return 0;
+}
diff --git a/test/scotest.c b/test/scotest.c
new file mode 100644 (file)
index 0000000..50b622a
--- /dev/null
@@ -0,0 +1,434 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sco.h>
+
+/* Test modes */
+enum {
+       SEND,
+       RECV,
+       RECONNECT,
+       MULTY,
+       DUMP,
+       CONNECT
+};
+
+static unsigned char *buf;
+
+/* Default data size */
+static long data_size = 672;
+
+static bdaddr_t bdaddr;
+
+static float tv2fl(struct timeval tv)
+{
+       return (float)tv.tv_sec + (float)(tv.tv_usec/1000000.0);
+}
+
+static int do_connect(char *svr)
+{
+       struct sockaddr_sco addr;
+       struct sco_conninfo conn;
+       socklen_t optlen;
+       int sk;
+
+       /* Create socket */
+       sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+       if (sk < 0) {
+               syslog(LOG_ERR, "Can't create socket: %s (%d)",
+                                                       strerror(errno), errno);
+               return -1;
+       }
+
+       /* Bind to local address */
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, &bdaddr);
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "Can't bind socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Connect to remote device */
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       str2ba(svr, &addr.sco_bdaddr);
+
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "Can't connect: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Get connection information */
+       memset(&conn, 0, sizeof(conn));
+       optlen = sizeof(conn);
+
+       if (getsockopt(sk, SOL_SCO, SCO_CONNINFO, &conn, &optlen) < 0) {
+               syslog(LOG_ERR, "Can't get SCO connection information: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       syslog(LOG_INFO, "Connected [handle %d, class 0x%02x%02x%02x]",
+               conn.hci_handle,
+               conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]);
+
+       return sk;
+
+error:
+       close(sk);
+       return -1;
+}
+
+static void do_listen(void (*handler)(int sk))
+{
+       struct sockaddr_sco addr;
+       struct sco_conninfo conn;
+       socklen_t optlen;
+       int sk, nsk;
+       char ba[18];
+
+       /* Create socket */
+       sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+       if (sk < 0) {
+               syslog(LOG_ERR, "Can't create socket: %s (%d)",
+                                                       strerror(errno), errno);
+               exit(1);
+       }
+
+       /* Bind to local address */
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, &bdaddr);
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "Can't bind socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       /* Listen for connections */
+       if (listen(sk, 10)) {
+               syslog(LOG_ERR,"Can not listen on the socket: %s (%d)",
+                                                       strerror(errno), errno);
+               goto error;
+       }
+
+       syslog(LOG_INFO,"Waiting for connection ...");
+
+       while (1) {
+               memset(&addr, 0, sizeof(addr));
+               optlen = sizeof(addr);
+
+               nsk = accept(sk, (struct sockaddr *) &addr, &optlen);
+               if (nsk < 0) {
+                       syslog(LOG_ERR,"Accept failed: %s (%d)",
+                                                       strerror(errno), errno);
+                       goto error;
+               }
+               if (fork()) {
+                       /* Parent */
+                       close(nsk);
+                       continue;
+               }
+               /* Child */
+               close(sk);
+
+               /* Get connection information */
+               memset(&conn, 0, sizeof(conn));
+               optlen = sizeof(conn);
+
+               if (getsockopt(nsk, SOL_SCO, SCO_CONNINFO, &conn, &optlen) < 0) {
+                       syslog(LOG_ERR, "Can't get SCO connection information: %s (%d)",
+                                                       strerror(errno), errno);
+                       close(nsk);
+                       goto error;
+               }
+
+               ba2str(&addr.sco_bdaddr, ba);
+               syslog(LOG_INFO, "Connect from %s [handle %d, class 0x%02x%02x%02x]",
+                       ba, conn.hci_handle,
+                       conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]);
+
+               handler(nsk);
+
+               syslog(LOG_INFO, "Disconnect");
+               exit(0);
+       }
+
+       return;
+
+error:
+       close(sk);
+       exit(1);
+}
+
+static void dump_mode(int sk)
+{
+       int len;
+
+       syslog(LOG_INFO,"Receiving ...");
+       while ((len = read(sk, buf, data_size)) > 0)
+               syslog(LOG_INFO, "Recevied %d bytes", len);
+}
+
+static void recv_mode(int sk)
+{
+       struct timeval tv_beg,tv_end,tv_diff;
+       long total;
+       uint32_t seq;
+
+       syslog(LOG_INFO, "Receiving ...");
+
+       seq = 0;
+       while (1) {
+               gettimeofday(&tv_beg, NULL);
+               total = 0;
+               while (total < data_size) {
+                       int r;
+                       if ((r = recv(sk, buf, data_size, 0)) <= 0) {
+                               if (r < 0)
+                                       syslog(LOG_ERR, "Read failed: %s (%d)",
+                                                       strerror(errno), errno);
+                               return;
+                       }
+                       total += r;
+               }
+               gettimeofday(&tv_end, NULL);
+
+               timersub(&tv_end, &tv_beg, &tv_diff);
+
+               syslog(LOG_INFO,"%ld bytes in %.2fm speed %.2f kb", total,
+                       tv2fl(tv_diff) / 60.0,
+                       (float)( total / tv2fl(tv_diff) ) / 1024.0 );
+       }
+}
+
+static void send_mode(char *svr)
+{
+       struct sco_options so;
+       socklen_t len;
+       uint32_t seq;
+       int i, sk;
+
+       if ((sk = do_connect(svr)) < 0) {
+               syslog(LOG_ERR, "Can't connect to the server: %s (%d)",
+                                                       strerror(errno), errno);
+               exit(1);
+       }
+
+       len = sizeof(so);
+       if (getsockopt(sk, SOL_SCO, SCO_OPTIONS, &so, &len) < 0) {
+               syslog(LOG_ERR, "Can't get SCO options: %s (%d)",
+                                                       strerror(errno), errno);
+               exit(1);
+       }
+
+       syslog(LOG_INFO,"Sending ...");
+
+       for (i = 6; i < so.mtu; i++)
+               buf[i] = 0x7f;
+
+       seq = 0;
+       while (1) {
+               *(uint32_t *) buf = htobl(seq);
+               *(uint16_t *) (buf + 4) = htobs(data_size);
+               seq++;
+
+               if (send(sk, buf, so.mtu, 0) <= 0) {
+                       syslog(LOG_ERR, "Send failed: %s (%d)",
+                                                       strerror(errno), errno);
+                       exit(1);
+               }
+
+               usleep(1);
+       }
+}
+
+static void reconnect_mode(char *svr)
+{
+       while (1) {
+               int sk;
+
+               if ((sk = do_connect(svr)) < 0) {
+                       syslog(LOG_ERR, "Can't connect to the server: %s (%d)",
+                                                       strerror(errno), errno);
+                       exit(1);
+               }
+
+               close(sk);
+
+               sleep(5);
+       }
+}
+
+static void multy_connect_mode(char *svr)
+{
+       while (1) {
+               int i, sk;
+
+               for (i = 0; i < 10; i++){
+                       if (fork())
+                               continue;
+
+                       /* Child */
+                       sk = do_connect(svr);
+                       if (sk < 0) {
+                               syslog(LOG_ERR, "Can't connect to the server: %s (%d)",
+                                                       strerror(errno), errno);
+                       }
+                       close(sk);
+                       exit(0);
+               }
+
+               sleep(19);
+       }
+}
+
+static void usage(void)
+{
+       printf("scotest - SCO testing\n"
+               "Usage:\n");
+       printf("\tscotest <mode> [-b bytes] [bd_addr]\n");
+       printf("Modes:\n"
+               "\t-d dump (server)\n"
+               "\t-c reconnect (client)\n"
+               "\t-m multiple connects (client)\n"
+               "\t-r receive (server)\n"
+               "\t-s connect and send (client)\n"
+               "\t-n connect and be silent (client)\n");
+}
+
+int main(int argc ,char *argv[])
+{
+       struct sigaction sa;
+       int opt, sk, mode = RECV;
+
+       while ((opt=getopt(argc,argv,"rdscmnb:")) != EOF) {
+               switch(opt) {
+               case 'r':
+                       mode = RECV;
+                       break;
+
+               case 's':
+                       mode = SEND;
+                       break;
+
+               case 'd':
+                       mode = DUMP;
+                       break;
+
+               case 'c':
+                       mode = RECONNECT;
+                       break;
+
+               case 'm':
+                       mode = MULTY;
+                       break;
+
+               case 'n':
+                       mode = CONNECT;
+                       break;
+
+               case 'b':
+                       data_size = atoi(optarg);
+                       break;
+
+               default:
+                       usage();
+                       exit(1);
+               }
+       }
+
+       if (!(argc - optind) && (mode != RECV && mode != DUMP)) {
+               usage();
+               exit(1);
+       }
+
+       if (!(buf = malloc(data_size))) {
+               perror("Can't allocate data buffer");
+               exit(1);
+       }
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = SIG_IGN;
+       sa.sa_flags   = SA_NOCLDSTOP;
+       sigaction(SIGCHLD, &sa, NULL);
+
+       openlog("scotest", LOG_PERROR | LOG_PID, LOG_LOCAL0);
+
+       switch( mode ){
+               case RECV:
+                       do_listen(recv_mode);
+                       break;
+
+               case DUMP:
+                       do_listen(dump_mode);
+                       break;
+
+               case SEND:
+                       send_mode(argv[optind]);
+                       break;
+
+               case RECONNECT:
+                       reconnect_mode(argv[optind]);
+                       break;
+
+               case MULTY:
+                       multy_connect_mode(argv[optind]);
+                       break;
+
+               case CONNECT:
+                       sk = do_connect(argv[optind]);
+                       if (sk < 0)
+                               exit(1);
+                       dump_mode(sk);
+                       break;
+       }
+
+       syslog(LOG_INFO, "Exit");
+
+       closelog();
+
+       return 0;
+}
diff --git a/test/sdptest.c b/test/sdptest.c
new file mode 100644 (file)
index 0000000..480a468
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <signal.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+static volatile sig_atomic_t __io_finished = 0;
+
+static void callback(uint8_t type, uint16_t status,
+                               uint8_t *rsp, size_t size, void *udata)
+{
+       unsigned int i;
+
+       for (i = 0; i < size; i++) {
+               printf("%02x ", rsp[i]);
+               if ((i + 1) % 8 == 0)
+                       printf(" ");
+               if ((i + 1) % 16 == 0)
+                       printf("\n");
+       }
+       printf("\n");
+
+       __io_finished = 1;
+}
+
+static void cmd_search(bdaddr_t *src, bdaddr_t *dst)
+{
+       sdp_session_t *session;
+       sdp_list_t *search, *attrids;
+       uint32_t range = 0x0000ffff;
+       uuid_t uuid;
+
+       session = sdp_connect(src, dst, 0);
+       if (!session) {
+               perror("Can't connect to SDP service");
+               exit(1);
+       }
+
+       sdp_set_notify(session, callback, NULL);
+
+       sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP);
+
+       search = sdp_list_append(NULL, &uuid);
+
+       attrids = sdp_list_append(NULL, &range);
+
+       //sdp_service_search_attr_async(session, search,
+       //                              SDP_ATTR_REQ_RANGE, attrids);
+
+       sdp_service_search_async(session, search, 0xffff);
+
+       sdp_list_free(attrids, NULL);
+
+       sdp_list_free(search, NULL);
+
+       while (!__io_finished)
+               sdp_process(session);
+
+       sdp_close(session);
+}
+
+static void usage(void)
+{
+       printf("sdptest - Utility for SDP testing\n\n");
+       printf("Usage:\n"
+               "\tsdptest [-i <dev>] <bdaddr>\n");
+}
+
+static struct option main_options[] = {
+       { "device",     1, 0, 'i' },
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       bdaddr_t src, dst;
+       int opt;
+
+       bacpy(&src, BDADDR_ANY);
+
+       while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'i':
+                       if (!strncasecmp(optarg, "hci", 3))
+                               hci_devba(atoi(optarg + 3), &src);
+                       else
+                               str2ba(optarg, &dst);
+                       break;
+
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       str2ba(argv[0], &dst);
+
+       cmd_search(&src, &dst);
+
+       return 0;
+}
diff --git a/test/service-did.xml b/test/service-did.xml
new file mode 100644 (file)
index 0000000..52eb68c
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<record>
+  <attribute id="0x0001">
+    <sequence>
+      <uuid value="0x1200"/>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0200">
+    <uint16 value="0x0102" name="id"/>
+  </attribute>
+
+  <attribute id="0x0201">
+    <uint16 value="0x0a12" name="vendor"/>
+  </attribute>
+
+  <attribute id="0x0202">
+    <uint16 value="0x4711" name="product"/>
+  </attribute>
+
+  <attribute id="0x0203">
+    <uint16 value="0x0000" name="version"/>
+  </attribute>
+
+  <attribute id="0x0204">
+    <boolean value="true"/>
+  </attribute>
+
+  <attribute id="0x0205">
+    <uint16 value="0x0002" name="source"/>
+  </attribute>
+</record>
diff --git a/test/service-ftp.xml b/test/service-ftp.xml
new file mode 100644 (file)
index 0000000..1bda885
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<record>
+  <attribute id="0x0001">
+    <sequence>
+      <uuid value="0x1106"/>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0004">
+    <sequence>
+      <sequence>
+        <uuid value="0x0100"/>
+      </sequence>
+      <sequence>
+        <uuid value="0x0003"/>
+        <uint8 value="23" name="channel"/>
+      </sequence>
+      <sequence>
+        <uuid value="0x0008"/>
+      </sequence>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0009">
+    <sequence>
+      <sequence>
+        <uuid value="0x1106"/>
+        <uint16 value="0x0100" name="version"/>
+      </sequence>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0100">
+    <text value="OBEX File Transfer" name="name"/>
+  </attribute>
+</record>
diff --git a/test/service-opp.xml b/test/service-opp.xml
new file mode 100644 (file)
index 0000000..351b4a4
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<record>
+  <attribute id="0x0001">
+    <sequence>
+      <uuid value="0x1105"/>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0004">
+    <sequence>
+      <sequence>
+        <uuid value="0x0100"/>
+      </sequence>
+      <sequence>
+        <uuid value="0x0003"/>
+        <uint8 value="23" name="channel"/>
+      </sequence>
+      <sequence>
+        <uuid value="0x0008"/>
+      </sequence>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0009">
+    <sequence>
+      <sequence>
+        <uuid value="0x1105"/>
+        <uint16 value="0x0100" name="version"/>
+      </sequence>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0100">
+    <text value="OBEX Object Push" name="name"/>
+  </attribute>
+
+  <attribute id="0x0303">
+    <sequence>
+      <uint8 value="0x01"/>
+      <uint8 value="0x01"/>
+      <uint8 value="0x02"/>
+      <uint8 value="0x03"/>
+      <uint8 value="0x04"/>
+      <uint8 value="0x05"/>
+      <uint8 value="0x06"/>
+      <uint8 value="0xff"/>
+    </sequence>
+  </attribute>
+</record>
diff --git a/test/service-record.dtd b/test/service-record.dtd
new file mode 100644 (file)
index 0000000..f53be5d
--- /dev/null
@@ -0,0 +1,66 @@
+<!ELEMENT record (attribute)*>
+
+<!ELEMENT attribute (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|nil)+>
+<!ATTLIST attribute id CDATA #REQUIRED>
+
+<!ELEMENT sequence (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|uint128|int8|int16|int32|int64|int128|nil)+>
+
+<!ELEMENT alternate (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|uint128|int8|int16|int32|int64|int128|nil)+>
+
+<!ELEMENT text EMPTY>
+<!ATTLIST text value CDATA #REQUIRED>
+<!ATTLIST text name CDATA>
+<!ATTLIST text encoding (normal|hex) "normal">
+
+<!ELEMENT url EMPTY>
+<!ATTLIST url value CDATA #REQUIRED>
+<!ATTLIST url name CDATA>
+
+<!ELEMENT uuid EMPTY>
+<!ATTLIST uuid value CDATA #REQUIRED>
+
+<!ELEMENT boolean EMPTY>
+<!ATTLIST boolean value CDATA #REQUIRED>
+<!ATTLIST boolean name CDATA>
+
+<!ELEMENT uint8 EMPTY>
+<!ATTLIST uint8 value CDATA #REQUIRED>
+<!ATTLIST uint8 name CDATA>
+
+<!ELEMENT uint16 EMPTY>
+<!ATTLIST uint16 value CDATA #REQUIRED>
+<!ATTLIST uint16 name CDATA>
+
+<!ELEMENT uint32 EMPTY>
+<!ATTLIST uint32 value CDATA #REQUIRED>
+<!ATTLIST uint32 name CDATA>
+
+<!ELEMENT uint64 EMPTY>
+<!ATTLIST uint64 value CDATA #REQUIRED>
+<!ATTLIST uint64 name CDATA>
+
+<!ELEMENT uint128 EMPTY>
+<!ATTLIST uint128 value CDATA #REQUIRED>
+<!ATTLIST uint128 name CDATA>
+
+<!ELEMENT int8 EMPTY>
+<!ATTLIST int8 value CDATA #REQUIRED>
+<!ATTLIST int8 name CDATA>
+
+<!ELEMENT int16 EMPTY>
+<!ATTLIST int16 value CDATA #REQUIRED>
+<!ATTLIST int16 name CDATA>
+
+<!ELEMENT int32 EMPTY>
+<!ATTLIST int32 value CDATA #REQUIRED>
+<!ATTLIST int32 name CDATA>
+
+<!ELEMENT int64 EMPTY>
+<!ATTLIST int64 value CDATA #REQUIRED>
+<!ATTLIST int64 name CDATA>
+
+<!ELEMENT int128 EMPTY>
+<!ATTLIST int128 value CDATA #REQUIRED>
+<!ATTLIST int128 name CDATA>
+
+<!ELEMENT nil EMPTY>
diff --git a/test/service-spp.xml b/test/service-spp.xml
new file mode 100644 (file)
index 0000000..2b156c3
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<record>
+  <attribute id="0x0001">
+    <sequence>
+      <uuid value="0x1101"/>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0004">
+    <sequence>
+      <sequence>
+        <uuid value="0x0100"/>
+      </sequence>
+      <sequence>
+        <uuid value="0x0003"/>
+        <uint8 value="23" name="channel"/>
+      </sequence>
+    </sequence>
+  </attribute>
+
+  <attribute id="0x0100">
+    <text value="COM5" name="name"/>
+  </attribute>
+</record>
diff --git a/test/simple-agent b/test/simple-agent
new file mode 100755 (executable)
index 0000000..f2cc3dd
--- /dev/null
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+
+import gobject
+
+import sys
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+
+class Rejected(dbus.DBusException):
+       _dbus_error_name = "org.bluez.Error.Rejected"
+
+class Agent(dbus.service.Object):
+       exit_on_release = True
+
+       def set_exit_on_release(self, exit_on_release):
+               self.exit_on_release = exit_on_release
+
+       @dbus.service.method("org.bluez.Agent",
+                                       in_signature="", out_signature="")
+       def Release(self):
+               print "Release"
+               if self.exit_on_release:
+                       mainloop.quit()
+
+       @dbus.service.method("org.bluez.Agent",
+                                       in_signature="os", out_signature="")
+       def Authorize(self, device, uuid):
+               print "Authorize (%s, %s)" % (device, uuid)
+               authorize = raw_input("Authorize connection (yes/no): ")
+               if (authorize == "yes"):
+                       return
+               raise Rejected("Connection rejected by user")
+
+       @dbus.service.method("org.bluez.Agent",
+                                       in_signature="o", out_signature="s")
+       def RequestPinCode(self, device):
+               print "RequestPinCode (%s)" % (device)
+               return raw_input("Enter PIN Code: ")
+
+       @dbus.service.method("org.bluez.Agent",
+                                       in_signature="o", out_signature="u")
+       def RequestPasskey(self, device):
+               print "RequestPasskey (%s)" % (device)
+               passkey = raw_input("Enter passkey: ")
+               return dbus.UInt32(passkey)
+
+       @dbus.service.method("org.bluez.Agent",
+                                       in_signature="ou", out_signature="")
+       def DisplayPasskey(self, device, passkey):
+               print "DisplayPasskey (%s, %d)" % (device, passkey)
+
+       @dbus.service.method("org.bluez.Agent",
+                                       in_signature="ou", out_signature="")
+       def RequestConfirmation(self, device, passkey):
+               print "RequestConfirmation (%s, %d)" % (device, passkey)
+               confirm = raw_input("Confirm passkey (yes/no): ")
+               if (confirm == "yes"):
+                       return
+               raise Rejected("Passkey doesn't match")
+
+       @dbus.service.method("org.bluez.Agent",
+                                       in_signature="s", out_signature="")
+       def ConfirmModeChange(self, mode):
+               print "ConfirmModeChange (%s)" % (mode)
+               authorize = raw_input("Authorize mode change (yes/no): ")
+               if (authorize == "yes"):
+                       return
+               raise Rejected("Mode change by user")
+
+       @dbus.service.method("org.bluez.Agent",
+                                       in_signature="", out_signature="")
+       def Cancel(self):
+               print "Cancel"
+
+def create_device_reply(device):
+       print "New device (%s)" % (device)
+       mainloop.quit()
+
+def create_device_error(error):
+       print "Creating device failed: %s" % (error)
+       mainloop.quit()
+
+if __name__ == '__main__':
+       dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+       bus = dbus.SystemBus()
+       manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+                                                       "org.bluez.Manager")
+
+       if len(sys.argv) > 1:
+               path = manager.FindAdapter(sys.argv[1])
+       else:
+               path = manager.DefaultAdapter()
+
+       adapter = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Adapter")
+
+       path = "/test/agent"
+       agent = Agent(bus, path)
+
+       mainloop = gobject.MainLoop()
+
+       if len(sys.argv) > 2:
+               if len(sys.argv) > 3:
+                       device = adapter.FindDevice(sys.argv[2])
+                       adapter.RemoveDevice(device)
+
+               agent.set_exit_on_release(False)
+               adapter.CreatePairedDevice(sys.argv[2], path, "DisplayYesNo",
+                                       reply_handler=create_device_reply,
+                                       error_handler=create_device_error)
+       else:
+               adapter.RegisterAgent(path, "DisplayYesNo")
+               print "Agent registered"
+
+       mainloop.run()
+
+       #adapter.UnregisterAgent(path)
+       #print "Agent unregistered"
diff --git a/test/simple-endpoint b/test/simple-endpoint
new file mode 100644 (file)
index 0000000..e09a528
--- /dev/null
@@ -0,0 +1,126 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+import gobject
+
+A2DP_SOURCE_UUID = "0000110A-0000-1000-8000-00805F9B34FB"
+A2DP_SINK_UUID = "0000110B-0000-1000-8000-00805F9B34FB"
+HFP_AG_UUID = "0000111F-0000-1000-8000-00805F9B34FB"
+HSP_AG_UUID = "00001112-0000-1000-8000-00805F9B34FB"
+
+SBC_CODEC = dbus.Byte(0x00)
+#Channel Modes: Mono DualChannel Stereo JointStereo
+#Frequencies: 16Khz 32Khz 44.1Khz 48Khz
+#Subbands: 4 8
+#Blocks: 4 8 12 16
+#Bitpool Range: 2-64
+SBC_CAPABILITIES = dbus.Array([dbus.Byte(0xff), dbus.Byte(0xff), dbus.Byte(2), dbus.Byte(64)])
+# JointStereo 44.1Khz Subbands: Blocks: 16 Bitpool Range: 2-32
+SBC_CONFIGURATION = dbus.Array([dbus.Byte(0x21), dbus.Byte(0x15), dbus.Byte(2), dbus.Byte(32)])
+
+MP3_CODEC = dbus.Byte(0x01)
+#Channel Modes: Mono DualChannel Stereo JointStereo
+#Frequencies: 32Khz 44.1Khz 48Khz
+#CRC: YES
+#Layer: 3
+#Bit Rate: All except Free format
+#VBR: Yes
+#Payload Format: RFC-2250
+MP3_CAPABILITIES = dbus.Array([dbus.Byte(0x3f), dbus.Byte(0x07), dbus.Byte(0xff), dbus.Byte(0xfe)])
+# JointStereo 44.1Khz Layer: 3 Bit Rate: VBR Format: RFC-2250
+MP3_CONFIGURATION = dbus.Array([dbus.Byte(0x21), dbus.Byte(0x02), dbus.Byte(0x00), dbus.Byte(0x80)])
+
+PCM_CODEC = dbus.Byte(0x00)
+PCM_CONFIGURATION = dbus.Array([], signature="ay")
+
+class Rejected(dbus.DBusException):
+       _dbus_error_name = "org.bluez.Error.Rejected"
+
+class Endpoint(dbus.service.Object):
+       exit_on_release = True
+       configuration = SBC_CONFIGURATION
+
+       def set_exit_on_release(self, exit_on_release):
+               self.exit_on_release = exit_on_release
+
+       def default_configuration(self, configuration):
+               self.configuration = configuration
+
+       @dbus.service.method("org.bluez.MediaEndpoint",
+                                       in_signature="", out_signature="")
+       def Release(self):
+               print "Release"
+               if self.exit_on_release:
+                       mainloop.quit()
+
+       @dbus.service.method("org.bluez.MediaEndpoint",
+                                       in_signature="", out_signature="")
+       def ClearConfiguration(self):
+               print "ClearConfiguration"
+
+       @dbus.service.method("org.bluez.MediaEndpoint",
+                                       in_signature="oay", out_signature="")
+       def SetConfiguration(self, transport, config):
+               print "SetConfiguration (%s, %s)" % (transport, config)
+               return
+
+       @dbus.service.method("org.bluez.MediaEndpoint",
+                                       in_signature="ay", out_signature="ay")
+       def SelectConfiguration(self, caps):
+               print "SelectConfiguration (%s)" % (caps)
+               return self.configuration
+
+if __name__ == '__main__':
+       dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+       bus = dbus.SystemBus()
+       manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+                                               "org.bluez.Manager")
+
+       if len(sys.argv) > 1:
+               path = manager.FindAdapter(sys.argv[1])
+       else:
+               path = manager.DefaultAdapter()
+
+       media = dbus.Interface(bus.get_object("org.bluez", path),
+                                               "org.bluez.Media")
+
+       path = "/test/endpoint"
+       endpoint = Endpoint(bus, path)
+       mainloop = gobject.MainLoop()
+
+       properties = dbus.Dictionary({ "UUID" : A2DP_SOURCE_UUID,
+                                       "Codec" : SBC_CODEC,
+                                       "DelayReporting" : True,
+                                       "Capabilities" : SBC_CAPABILITIES })
+
+       if len(sys.argv) > 2:
+               if sys.argv[2] == "sbcsink":
+                       properties = dbus.Dictionary({ "UUID" : A2DP_SINK_UUID,
+                                                       "Codec" : SBC_CODEC,
+                                                       "DelayReporting" : True,
+                                                       "Capabilities" : SBC_CAPABILITIES })
+               if sys.argv[2] == "mp3source":
+                       properties = dbus.Dictionary({ "UUID" : A2DP_SOURCE_UUID,
+                                                       "Codec" : MP3_CODEC,
+                                                       "Capabilities" : MP3_CAPABILITIES })
+                       endpoint.default_configuration(MP3_CONFIGURATION)
+               if sys.argv[2] == "mp3sink":
+                       properties = dbus.Dictionary({ "UUID" : A2DP_SINK_UUID,
+                                                       "Codec" : MP3_CODEC,
+                                                       "Capabilities" : MP3_CAPABILITIES })
+                       endpoint.default_configuration(MP3_CONFIGURATION)
+               if sys.argv[2] == "hfpag" or sys.argv[2] == "hspag":
+                       properties = dbus.Dictionary({ "UUID" : HFP_AG_UUID,
+                                                       "Codec" : PCM_CODEC,
+                                                       "Capabilities" :  PCM_CONFIGURATION })
+                       endpoint.default_configuration(dbus.Array([]))
+
+       print properties
+
+       media.RegisterEndpoint(path, properties)
+
+       mainloop.run()
diff --git a/test/simple-service b/test/simple-service
new file mode 100755 (executable)
index 0000000..d03ec3d
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/python
+
+import sys
+import time
+import dbus
+
+xml = ' \
+<?xml version="1.0" encoding="UTF-8" ?>        \
+<record>                                       \
+  <attribute id="0x0001">                      \
+    <sequence>                                 \
+      <uuid value="0x1101"/>                   \
+    </sequence>                                        \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0002">                      \
+     <uint32 value="0"/>                       \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0003">                      \
+    <uuid value="00001101-0000-1000-8000-00805f9b34fb"/> \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0004">                      \
+    <sequence>                                 \
+      <sequence>                               \
+        <uuid value="0x0100"/>                 \
+      </sequence>                              \
+      <sequence>                               \
+        <uuid value="0x0003"/>                 \
+        <uint8 value="23"/>                    \
+      </sequence>                              \
+    </sequence>                                        \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0005">                      \
+    <sequence>                                 \
+      <uuid value="0x1002"/>                   \
+    </sequence>                                        \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0006">                      \
+    <sequence>                                 \
+      <uint16 value="0x656e"/>                 \
+      <uint16 value="0x006a"/>                 \
+      <uint16 value="0x0100"/>                 \
+    </sequence>                                        \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0007">                      \
+     <uint32 value="0"/>                       \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0008">                      \
+     <uint8 value="0xff"/>                     \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0009">                      \
+    <sequence>                                 \
+      <sequence>                               \
+        <uuid value="0x1101"/>                 \
+        <uint16 value="0x0100"/>               \
+      </sequence>                              \
+    </sequence>                                        \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x000a">                      \
+    <url value="http://www.bluez.org/"/>       \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x000b">                      \
+    <url value="http://www.bluez.org/"/>       \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x000c">                      \
+    <url value="http://www.bluez.org/"/>       \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0100">                      \
+    <text value="Serial Port"/>                        \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0101">                      \
+    <text value="Serial Port Service"/>                \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0102">                      \
+     <text value="BlueZ"/>                     \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0200">                      \
+    <sequence>                                 \
+      <uint16 value="0x0100"/>                 \
+    </sequence>                                        \
+  </attribute>                                 \
+                                               \
+  <attribute id="0x0201">                      \
+     <uint32 value="0"/>                       \
+  </attribute>                                 \
+</record>                                      \
+'
+
+bus = dbus.SystemBus()
+manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+                                               "org.bluez.Manager")
+
+if len(sys.argv) > 1:
+       path = manager.FindAdapter(sys.argv[1])
+else:
+       path = manager.DefaultAdapter()
+
+service = dbus.Interface(bus.get_object("org.bluez", path),
+                                               "org.bluez.Service")
+
+handle = service.AddRecord(xml)
+
+print "Service record with handle 0x%04x added" % (handle)
+
+print "Press CTRL-C to remove service record"
+
+try:
+       time.sleep(1000)
+       print "Terminating session"
+except:
+       pass
+
+service.RemoveRecord(dbus.UInt32(handle))
diff --git a/test/test-adapter b/test/test-adapter
new file mode 100755 (executable)
index 0000000..00ef6f5
--- /dev/null
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import time
+from optparse import OptionParser, make_option
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+if (len(args) < 1):
+       print "Usage: %s <command>" % (sys.argv[0])
+       print ""
+       print "  address"
+       print "  name [name]"
+       print "  powered [on/off]"
+       print "  pairable [on/off]"
+       print "  pairabletimeout [timeout]"
+       print "  discoverable [on/off]"
+       print "  discoverabletimeout [timeout]"
+       print "  discovering"
+       sys.exit(1)
+
+if (args[0] == "address"):
+       properties = adapter.GetProperties()
+       print properties["Address"]
+       sys.exit(0)
+
+if (args[0] == "name"):
+       if (len(args) < 2):
+               properties = adapter.GetProperties()
+               print properties["Name"]
+       else:
+               adapter.SetProperty("Name", args[1])
+       sys.exit(0)
+
+if (args[0] == "powered"):
+       if (len(args) < 2):
+               properties = adapter.GetProperties()
+               print properties["Powered"]
+       else:
+               if (args[1] == "on"):
+                       value = dbus.Boolean(1)
+               elif (args[1] == "off"):
+                       value = dbus.Boolean(0)
+               else:
+                       value = dbus.Boolean(args[1])
+               adapter.SetProperty("Powered", value)
+       sys.exit(0)
+
+if (args[0] == "pairable"):
+       if (len(args) < 2):
+               properties = adapter.GetProperties()
+               print properties["Pairable"]
+       else:
+               if (args[1] == "on"):
+                       value = dbus.Boolean(1)
+               elif (args[1] == "off"):
+                       value = dbus.Boolean(0)
+               else:
+                       value = dbus.Boolean(args[1])
+               adapter.SetProperty("Pairable", value)
+       sys.exit(0)
+
+if (args[0] == "pairabletimeout"):
+       if (len(args) < 2):
+               properties = adapter.GetProperties()
+               print properties["PairableTimeout"]
+       else:
+               timeout = dbus.UInt32(args[1])
+               adapter.SetProperty("PairableTimeout", timeout)
+       sys.exit(0)
+
+if (args[0] == "discoverable"):
+       if (len(args) < 2):
+               properties = adapter.GetProperties()
+               print properties["Discoverable"]
+       else:
+               if (args[1] == "on"):
+                       value = dbus.Boolean(1)
+               elif (args[1] == "off"):
+                       value = dbus.Boolean(0)
+               else:
+                       value = dbus.Boolean(args[1])
+               adapter.SetProperty("Discoverable", value)
+       sys.exit(0)
+
+if (args[0] == "discoverabletimeout"):
+       if (len(args) < 2):
+               properties = adapter.GetProperties()
+               print properties["DiscoverableTimeout"]
+       else:
+               timeout = dbus.UInt32(args[1])
+               adapter.SetProperty("DiscoverableTimeout", timeout)
+       sys.exit(0)
+
+if (args[0] == "discovering"):
+       properties = adapter.GetProperties()
+       print properties["Discovering"]
+       sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/test/test-attrib b/test/test-attrib
new file mode 100644 (file)
index 0000000..b9e83c5
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/python
+# Script for testing the Attribute D-Bus API
+
+import sys
+from optparse import OptionParser, OptionValueError
+from binascii import hexlify, unhexlify
+
+import gobject
+
+import sys
+import dbus
+import dbus.mainloop.glib
+from optparse import OptionParser, make_option
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+bus = dbus.SystemBus()
+mainloop = gobject.MainLoop()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+if (len(args) < 1):
+       print "Usage: %s <command>" % (sys.argv[0])
+       print ""
+       print "  list"
+       print "  services <address>"
+       print "  discover <service path>"
+       print "  chars <service path>"
+       sys.exit(1)
+
+if (args[0] == "list"):
+       for path in adapter.ListDevices():
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               devprop = device.GetProperties()
+               print "[ %s ]" % devprop["Address"]
+               for path in devprop["Services"]:
+
+                       service = dbus.Interface(bus.get_object("org.bluez", path),
+                                                                        "org.bluez.Characteristic")
+                       srvprop = service.GetProperties()
+                       print " * %s" % (path)
+                       print " UUID: %s" % srvprop["UUID"]
+                       print " Chars: ",
+                       for char in srvprop["Characteristics"]:
+                               print "%s " % char,
+                       print
+                       print
+               print
+       sys.exit(0)
+
+if (args[0] == "services"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               properties = device.GetProperties()
+               for path in properties["Services"]:
+                       print path
+       sys.exit(0)
+
+if (args[0] == "discover"):
+       if (len(args) < 2):
+               print "Need service path parameter"
+       else:
+               service = dbus.Interface(bus.get_object("org.bluez", args[1]),
+                                                       "org.bluez.Characteristic")
+               for path in service.DiscoverCharacteristics():
+                       print path
+       sys.exit(0)
+
+if (args[0] == "chars"):
+       if (len(args) < 2):
+               print "Need service path parameter"
+       else:
+               service = dbus.Interface(bus.get_object("org.bluez", args[1]),
+                                                                "org.bluez.Characteristic")
+               srvprop = service.GetProperties()
+               for path in srvprop["Characteristics"]:
+                       print "[ %s ]" % (path)
+                       char = dbus.Interface(bus.get_object("org.bluez", path),
+                                                                "org.bluez.Characteristic")
+                       charprop = char.GetProperties()
+                       print " Name: %s" % charprop["Name"]
+                       print " UUID: %s" % charprop["UUID"]
+                       print
+               print
+       sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/test/test-audio b/test/test-audio
new file mode 100755 (executable)
index 0000000..8b7a62d
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+from optparse import OptionParser, make_option
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+if len(args) < 2:
+       print """Usage: %s <command>
+
+       connect <bdaddr>
+       disconnect <bdaddr>
+       """ % sys.argv[0]
+       sys.exit(1)
+
+device = adapter.FindDevice(args[1])
+audio = dbus.Interface(bus.get_object("org.bluez", device),
+                               "org.bluez.Audio")
+
+if args[0] == "connect":
+       audio.Connect()
+elif args[0] == "disconnect":
+       audio.Disconnect()
+else:
+       print "Unknown command"
+       sys.exit(1)
diff --git a/test/test-device b/test/test-device
new file mode 100755 (executable)
index 0000000..154af19
--- /dev/null
@@ -0,0 +1,207 @@
+#!/usr/bin/python
+
+import gobject
+
+import sys
+import dbus
+import dbus.mainloop.glib
+import re
+from optparse import OptionParser, make_option
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+bus = dbus.SystemBus()
+mainloop = gobject.MainLoop()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+if (len(args) < 1):
+       print "Usage: %s <command>" % (sys.argv[0])
+       print ""
+       print "  list"
+       print "  services <address>"
+       print "  create <address>"
+       print "  remove <address|path>"
+       print "  disconnect <address>"
+       print "  discover <address> [pattern]"
+       print "  class <address>"
+       print "  name <address>"
+       print "  alias <address> [alias]"
+       print "  trusted <address> [yes/no]"
+       print "  blocked <address> [yes/no]"
+       sys.exit(1)
+
+if (args[0] == "list"):
+       for path in adapter.ListDevices():
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               properties = device.GetProperties()
+               print "%s %s" % (properties["Address"], properties["Alias"])
+
+       sys.exit(0)
+
+def create_device_reply(device):
+       print "New device (%s)" % device
+       mainloop.quit()
+       sys.exit(0)
+
+def create_device_error(error):
+       print "Creating device failed: %s" % error
+       mainloop.quit()
+       sys.exit(1)
+
+if (args[0] == "create"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               adapter.CreateDevice(args[1],
+                               reply_handler=create_device_reply,
+                               error_handler=create_device_error)
+       mainloop.run()
+
+if (args[0] == "remove"):
+       if (len(args) < 2):
+               print "Need address or object path parameter"
+       else:
+               try:
+                       path = adapter.FindDevice(args[1])
+               except:
+                       path = args[1]
+               adapter.RemoveDevice(path)
+       sys.exit(0)
+
+if (args[0] == "disconnect"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               device.Disconnect()
+       sys.exit(0)
+
+if (args[0] == "discover"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               if (len(args) < 3):
+                       pattern = ""
+               else:
+                       pattern = args[2]
+               services = device.DiscoverServices(pattern);
+               for key in services.keys():
+                       p = re.compile(">.*?<")
+                       xml = p.sub("><", services[key].replace("\n", ""))
+                       print "[ 0x%5x ]" % (key)
+                       print xml
+                       print
+       sys.exit(0)
+
+if (args[0] == "class"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               properties = device.GetProperties()
+               print "0x%06x" % (properties["Class"])
+       sys.exit(0)
+
+if (args[0] == "name"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               properties = device.GetProperties()
+               print properties["Name"]
+       sys.exit(0)
+
+if (args[0] == "alias"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               if (len(args) < 3):
+                       properties = device.GetProperties()
+                       print properties["Alias"]
+               else:
+                       device.SetProperty("Alias", args[2])
+       sys.exit(0)
+
+if (args[0] == "trusted"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               if (len(args) < 3):
+                       properties = device.GetProperties()
+                       print properties["Trusted"]
+               else:
+                       if (args[2] == "yes"):
+                               value = dbus.Boolean(1)
+                       elif (args[2] == "no"):
+                               value = dbus.Boolean(0)
+                       else:
+                               value = dbus.Boolean(args[2])
+                       device.SetProperty("Trusted", value)
+       sys.exit(0)
+
+if (args[0] == "blocked"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               if (len(args) < 3):
+                       properties = device.GetProperties()
+                       print properties["Blocked"]
+               else:
+                       if (args[2] == "yes"):
+                               value = dbus.Boolean(1)
+                       elif (args[2] == "no"):
+                               value = dbus.Boolean(0)
+                       else:
+                               value = dbus.Boolean(args[2])
+                       device.SetProperty("Blocked", value)
+       sys.exit(0)
+
+if (args[0] == "services"):
+       if (len(args) < 2):
+               print "Need address parameter"
+       else:
+               path = adapter.FindDevice(args[1])
+               device = dbus.Interface(bus.get_object("org.bluez", path),
+                                                       "org.bluez.Device")
+               properties = device.GetProperties()
+               for path in properties["Services"]:
+                       print path
+       sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/test/test-discovery b/test/test-discovery
new file mode 100755 (executable)
index 0000000..22c88c3
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+
+import gobject
+
+import dbus
+import dbus.mainloop.glib
+from optparse import OptionParser, make_option
+
+def device_found(address, properties):
+       print "[ " + address + " ]"
+
+       for key in properties.keys():
+               value = properties[key]
+               if (key == "Class"):
+                       print "    %s = 0x%06x" % (key, value)
+               else:
+                       print "    %s = %s" % (key, value)
+
+def property_changed(name, value):
+       if (name == "Discovering" and not value):
+               mainloop.quit()
+
+if __name__ == '__main__':
+       dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+       bus = dbus.SystemBus()
+       manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+                                                       "org.bluez.Manager")
+
+       option_list = [
+                       make_option("-i", "--device", action="store",
+                                       type="string", dest="dev_id"),
+                       ]
+       parser = OptionParser(option_list=option_list)
+
+       (options, args) = parser.parse_args()
+
+       if options.dev_id:
+               adapter_path = manager.FindAdapter(options.dev_id)
+       else:
+               adapter_path = manager.DefaultAdapter()
+
+       adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+       bus.add_signal_receiver(device_found,
+                       dbus_interface = "org.bluez.Adapter",
+                                       signal_name = "DeviceFound")
+
+       bus.add_signal_receiver(property_changed,
+                       dbus_interface = "org.bluez.Adapter",
+                                       signal_name = "PropertyChanged")
+
+       adapter.StartDiscovery()
+
+       mainloop = gobject.MainLoop()
+       mainloop.run()
diff --git a/test/test-input b/test/test-input
new file mode 100755 (executable)
index 0000000..405bb59
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+from optparse import OptionParser, make_option
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+if len(args) < 2:
+       print """Usage: %s <command>
+
+       connect <bdaddr>
+       disconnect <bdaddr>
+       """ % sys.argv[0]
+       sys.exit(1)
+
+device = adapter.FindDevice(args[1])
+input = dbus.Interface(bus.get_object("org.bluez", device),
+                               "org.bluez.Input")
+
+if args[0] == "connect":
+       input.Connect()
+elif args[0] == "disconnect":
+       input.Disconnect()
+else:
+       print "Unknown command"
+       sys.exit(1)
diff --git a/test/test-manager b/test/test-manager
new file mode 100755 (executable)
index 0000000..c6cf560
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+
+import gobject
+
+import dbus
+import dbus.mainloop.glib
+
+def adapter_added(path):
+       print "Adapter with path %s added" % (path)
+
+def adapter_removed(path):
+       print "Adapter with path %s removed" % (path)
+
+def default_changed(path):
+       print "Default adapter is now at path %s" % (path)
+
+if __name__ == "__main__":
+       dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+       bus = dbus.SystemBus()
+
+       manager = dbus.Interface(bus.get_object('org.bluez', '/'),
+                                                       'org.bluez.Manager')
+
+       manager.connect_to_signal("AdapterAdded", adapter_added)
+
+       manager.connect_to_signal("AdapterRemoved", adapter_removed)
+
+       manager.connect_to_signal("DefaultAdapterChanged", default_changed)
+
+       try:
+               path = manager.DefaultAdapter()
+               default_changed(path)
+       except:
+               pass
+
+       mainloop = gobject.MainLoop()
+       mainloop.run()
diff --git a/test/test-network b/test/test-network
new file mode 100755 (executable)
index 0000000..676fb30
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+
+import sys
+import time
+import dbus
+from optparse import OptionParser, make_option
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+                                               "org.bluez.Manager")
+
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+if (len(args) < 1):
+       print "Usage: %s <address> [service]" % (sys.argv[0])
+       sys.exit(1)
+
+address = args[0]
+
+if (len(args) < 2):
+       service = "panu"
+else:
+       service = args[1]
+
+device = adapter.FindDevice(address)
+
+network = dbus.Interface(bus.get_object("org.bluez", device),
+                                               "org.bluez.Network")
+
+iface = network.Connect(service)
+
+print "Connected %s to %s" % (device, address)
+
+print "Press CTRL-C to disconnect"
+
+try:
+       time.sleep(1000)
+       print "Terminating connection"
+except:
+       pass
+
+network.Disconnect()
diff --git a/test/test-serial b/test/test-serial
new file mode 100755 (executable)
index 0000000..cc496df
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+
+import sys
+import time
+import dbus
+from optparse import OptionParser, make_option
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+                                               "org.bluez.Manager")
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+if (len(args) < 1):
+       print "Usage: %s <address> [service]" % (sys.argv[0])
+       sys.exit(1)
+
+address = args[0]
+
+if (len(args) < 2):
+       service = "spp"
+else:
+       service = args[1]
+
+path = adapter.FindDevice(address)
+
+serial = dbus.Interface(bus.get_object("org.bluez", path),
+                                               "org.bluez.Serial")
+
+node = serial.Connect(service)
+
+print "Connected %s to %s" % (node, address)
+
+print "Press CTRL-C to disconnect"
+
+try:
+       time.sleep(1000)
+       print "Terminating connection"
+except:
+       pass
+
+serial.Disconnect(node)
diff --git a/test/test-service b/test/test-service
new file mode 100755 (executable)
index 0000000..8958201
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import time
+from optparse import OptionParser, make_option
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+service = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                               "org.bluez.Service")
+
+if (len(args) < 1):
+       print "Usage: %s <command>" % (sys.argv[0])
+       print ""
+       print "  addrecord <file>"
+       sys.exit(1)
+
+if (args[0] == "addrecord"):
+       if (len(args) < 2):
+               print "Need file parameter"
+       else:
+               f = open(args[1])
+               record = f.read()
+               f.close()
+               handle = service.AddRecord(record)
+               print "0x%x" % (handle)
+               time.sleep(120)
+       sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/test/test-telephony b/test/test-telephony
new file mode 100755 (executable)
index 0000000..5ef0ac8
--- /dev/null
@@ -0,0 +1,176 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+from optparse import OptionParser, make_option
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+               make_option("-i", "--device", action="store",
+                               type="string", dest="dev_id"),
+               ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+       adapter_path = manager.FindAdapter(options.dev_id)
+else:
+       adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+                                                       "org.bluez.Adapter")
+
+test = dbus.Interface(bus.get_object("org.bluez", "/org/bluez/test"),
+                       "org.bluez.TelephonyTest")
+
+if len(args) < 1:
+       print """Usage: %s <command>
+
+       connect <bdaddr>
+       disconnect <bdaddr>
+       outgoing <number>
+       incoming <number>
+       cancel
+       signal <level>
+       battery <level>
+       roaming <yes|no>
+       registration <status>
+       subscriber <number>
+       speakergain <bdaddr> [level]
+       microphonegain <bdaddr> [level]
+       play <bdaddr>
+       stop <bdaddr>
+       """ % sys.argv[0]
+       sys.exit(1)
+
+if args[0] == "connect":
+       if len(args) < 2:
+               print "Need device address parameter"
+               sys.exit(1)
+       device = adapter.FindDevice(args[1])
+       headset = dbus.Interface(bus.get_object("org.bluez", device),
+                                       "org.bluez.Headset")
+       headset.Connect()
+       sys.exit(0)
+
+if args[0] == "disconnect":
+       if len(args) < 2:
+               print "Need device address parameter"
+               sys.exit(1)
+       device = adapter.FindDevice(args[1])
+       headset = dbus.Interface(bus.get_object("org.bluez", device),
+                                       "org.bluez.Headset")
+       headset.Disconnect()
+       sys.exit(0)
+
+if args[0] == "speakergain":
+       if len(args) < 2:
+               print "Need device address parameter"
+               sys.exit(1)
+       device = adapter.FindDevice(args[1])
+       headset = dbus.Interface(bus.get_object("org.bluez", device),
+                                       "org.bluez.Headset")
+       if len(args) > 2:
+               headset.SetProperty('SpeakerGain', dbus.UInt16(args[2]))
+       else:
+               props = headset.GetProperties()
+               print props['SpeakerGain']
+
+       sys.exit(0)
+
+if args[0] == "microphonegain":
+       if len(args) < 2:
+               print "Need device address parameter"
+               sys.exit(1)
+       device = adapter.FindDevice(args[1])
+       headset = dbus.Interface(bus.get_object("org.bluez", device),
+                                       "org.bluez.Headset")
+       if len(args) > 2:
+               headset.SetProperty('MicrophoneGain', dbus.UInt16(args[2]))
+       else:
+               props = headset.GetProperties()
+               print props['MicrophoneGain']
+
+       sys.exit(0)
+
+if args[0] == "play":
+       if len(args) < 2:
+               print "Need device address parameter"
+               sys.exit(1)
+       device = adapter.FindDevice(args[1])
+       headset = dbus.Interface(bus.get_object("org.bluez", device),
+                                       "org.bluez.Headset")
+       headset.Play()
+
+       sys.exit(0)
+
+if args[0] == "stop":
+       if len(args) < 2:
+               print "Need device address parameter"
+               sys.exit(1)
+       device = adapter.FindDevice(args[1])
+       headset = dbus.Interface(bus.get_object("org.bluez", device),
+                                       "org.bluez.Headset")
+       headset.Stop()
+
+       sys.exit(0)
+
+if args[0] == "outgoing":
+       if len(args) > 1:
+               test.OutgoingCall(args[1])
+       else:
+               print "Need number parameter"
+       sys.exit(0)
+
+if args[0] == "incoming":
+       if len(args) > 1:
+               test.IncomingCall(args[1])
+       else:
+               print "Need number parameter"
+       sys.exit(0)
+
+if args[0] == "cancel":
+       test.CancelCall()
+       sys.exit(0)
+
+if args[0] == "signal":
+       if len(args) > 1:
+               test.SignalStrength(args[1])
+       else:
+               print "Need signal strength parameter"
+       sys.exit(0)
+
+if args[0] == "battery":
+       if len(args) > 1:
+               test.BatteryLevel(args[1])
+       else:
+               print "Need battery level parameter"
+       sys.exit(0)
+
+if args[0] == "roaming":
+       if len(args) > 1:
+               test.RoamingStatus(args[1] == "yes" or False)
+       else:
+               print "Need yes/no parameter"
+       sys.exit(0)
+
+if args[0] == "registration":
+       if len(args) > 1:
+               test.RegistrationStatus(args[1] == "yes" or False)
+       else:
+               print "Need yes/no parameter"
+       sys.exit(0)
+
+if args[0] == "subscriber":
+       if len(args) > 1:
+               test.SetSubscriberNumber(args[1])
+       else:
+               print "Need number parameter"
+       sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/test/test-textfile.c b/test/test-textfile.c
new file mode 100644 (file)
index 0000000..970e9e7
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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/test/uuidtest.c b/test/uuidtest.c
new file mode 100644 (file)
index 0000000..a8b46d7
--- /dev/null
@@ -0,0 +1,319 @@
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+const char *base = "00000000-0000-1000-8000-00805F9B34FB";
+
+uint8_t xbase[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+                       0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb};
+
+uint16_t sixteen = 0x1234;
+const char *uuidsixteen128 = "00001234-0000-1000-8000-00805F9B34FB";
+const char *uuidsixteen16 = "0x1234";
+const char *uuidsixteen16a = "1234";
+
+uint8_t xuuidsixteen[] = {0x00, 0x00, 0x12, 0x34, 0x00, 0x00, 0x10, 0x00,
+                       0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb};
+
+uint32_t thirtytwo = 0x12345678;
+const char *uuidthirtytwo32 = "0x12345678";
+const char *uuidthirtytwo32a = "12345678";
+const char *uuidthirtytwo128 = "12345678-0000-1000-8000-00805F9B34FB";
+
+uint8_t xuuidthirtytwo[] = {0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x10, 0x00,
+                       0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb};
+
+const char *malformed[] = {
+       "0",
+       "01",
+       "012",
+       "xxxx",
+       "xxxxx",
+       "0xxxxx",
+       "0123456",
+       "012g4567",
+       "012345678",
+       "0x234567u9",
+       "01234567890",
+       "00001234-0000-1000-8000-00805F9B34F",
+       "00001234-0000-1000-8000 00805F9B34FB",
+       "00001234-0000-1000-8000-00805F9B34FBC",
+       "00001234-0000-1000-800G-00805F9B34FB",
+       NULL,
+       };
+
+int main(int argc, char *argv[])
+{
+       bt_uuid_t u, u2, u3, u4, u5, ub, u128;
+       uint128_t n, i;
+       char buf[512];
+       int s;
+
+       memcpy(&n, xbase, 16);
+       ntoh128(&n, &i);
+
+       if (bt_string_to_uuid(&u, base)) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_string_to_uuid(&ub, base)) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u.type != 128) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (ub.type != 128) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (memcmp(&u.value.u128, &i, 16) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (memcmp(&ub.value.u128, &i, 16) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (memcmp(&ub.value.u128, &u.value.u128, 16) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u, &ub) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       bt_uuid_to_string(&u, buf, sizeof(buf));
+       /* printf("%s\n", buf); */
+
+       if (strcasecmp(buf, base) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       memcpy(&n, xuuidsixteen, 16);
+       ntoh128(&n, &i);
+
+       bt_uuid16_create(&u, sixteen);
+       bt_uuid_to_uuid128(&u, &u128);
+
+       if (bt_string_to_uuid(&u2, uuidsixteen16)) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_string_to_uuid(&u3, uuidsixteen16a)) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_string_to_uuid(&u4, uuidsixteen128)) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       bt_uuid128_create(&u5, i);
+
+       if (u.type != 16) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u128.type != 128) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u.value.u16 != sixteen) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u2.type != 16) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u3.type != 16) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u4.type != 128) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u5.type != 128) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u, &u2) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u2, &u3) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u, &u3) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u3, &u4) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u4, &u5) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u5, &u) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u, &ub) == 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u5, &ub) == 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u, &u128) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&ub, &u128) == 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       memcpy(&n, xuuidthirtytwo, 16);
+       ntoh128(&n, &i);
+
+       bt_uuid32_create(&u, thirtytwo);
+       bt_uuid_to_uuid128(&u, &u128);
+       bt_string_to_uuid(&u2, uuidthirtytwo32);
+       bt_string_to_uuid(&u3, uuidthirtytwo32a);
+       bt_string_to_uuid(&u4, uuidthirtytwo128);
+       bt_uuid128_create(&u5, i);
+
+       /*
+       bt_uuid_to_string(&u2, buf, sizeof(buf));
+       printf("%s\n", buf);
+
+       bt_uuid_to_string(&u3, buf, sizeof(buf));
+       printf("%s\n", buf);
+
+       bt_uuid_to_string(&u4, buf, sizeof(buf));
+       printf("%s\n", buf);
+       */
+
+       if (u.type != 32) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u128.type != 128) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u.value.u32 != thirtytwo) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u2.type != 32) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u3.type != 32) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u4.type != 128) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (u5.type != 128) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u, &u2) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u2, &u3) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u3, &u4) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u4, &u5) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u5, &u) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u, &ub) == 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u5, &ub) == 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&u, &u128) != 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       if (bt_uuid_cmp(&ub, &u128) == 0) {
+               printf("Fail %d\n", __LINE__);
+               return 1;
+       }
+
+       for (s = 0; malformed[s]; ++s) {
+               if (bt_string_to_uuid(&u3, malformed[s]) == 0) {
+                       printf("Fail %s %d\n", malformed[s], __LINE__);
+                       return 1;
+               }
+       }
+
+       return 0;
+}
diff --git a/tools/avctrl.8 b/tools/avctrl.8
new file mode 100644 (file)
index 0000000..7c3759d
--- /dev/null
@@ -0,0 +1,39 @@
+.\"
+.\"    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., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.TH AVCTRL 8 "JUNE 6, 2005" "" ""
+
+.SH NAME
+avctrl \- Bluetooth Audio/Video control utility
+.SH SYNOPSIS
+.BR "avctrl
+[
+.I options
+]
+<command>
+.SH DESCRIPTION
+.B avctrl
+is used to control the Audio/Video dongles.
+.SH OPTIONS
+.TP
+.BI -h
+Gives a list of possible options.
+.TP
+.BI -q
+Don't display any messages.
+.SH AUTHOR
+Written by Marcel Holtmann <marcel@holtmann.org>.
+.br
diff --git a/tools/avctrl.c b/tools/avctrl.c
new file mode 100644 (file)
index 0000000..3621a68
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <usb.h>
+
+#ifdef NEED_USB_GET_BUSSES
+static inline struct usb_bus *usb_get_busses(void)
+{
+       return usb_busses;
+}
+#endif
+
+#ifndef USB_DIR_OUT
+#define USB_DIR_OUT    0x00
+#endif
+
+#ifndef USB_DIR_IN
+#define USB_DIR_IN     0x80
+#endif
+
+#define HID_REQ_GET_REPORT     0x01
+#define HID_REQ_GET_IDLE       0x02
+#define HID_REQ_GET_PROTOCOL   0x03
+#define HID_REQ_SET_REPORT     0x09
+#define HID_REQ_SET_IDLE       0x0a
+#define HID_REQ_SET_PROTOCOL   0x0b
+
+struct device_info;
+
+struct device_id {
+       uint16_t vendor;
+       uint16_t product;
+       int (*func)(struct device_info *dev, int argc, char *argv[]);
+};
+
+struct device_info {
+       struct usb_device *dev;
+       struct device_id *id;
+};
+
+#define GET_STATE              0x01
+#define GET_REMOTE_BDADDR      0x02
+#define DISCOVER               0x03
+#define SWITCH_TO_DFU          0x04
+#define READ_CODEC             0x05
+
+static int dongle_csr(struct device_info *devinfo, int argc, char *argv[])
+{
+       char buf[8];
+       struct usb_dev_handle *udev;
+       int err, intf = 2;
+
+       memset(buf, 0, sizeof(buf));
+
+       if (!strncasecmp(argv[0], "discover", 4))
+               buf[0] = DISCOVER;
+       else if (!strncasecmp(argv[0], "switch", 3))
+               buf[0] = SWITCH_TO_DFU;
+       else if (!strncasecmp(argv[0], "dfu", 3))
+               buf[0] = SWITCH_TO_DFU;
+       else
+               return -EINVAL;
+
+       udev = usb_open(devinfo->dev);
+       if (!udev)
+               return -errno;
+
+       if (usb_claim_interface(udev, intf) < 0) {
+               err = -errno;
+               usb_close(udev);
+               return err;
+       }
+
+       err = usb_control_msg(udev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                               HID_REQ_SET_REPORT, 0x03 << 8, intf, buf, sizeof(buf), 10000);
+
+       if (err == 0) {
+               err = -1;
+               errno = EALREADY;
+       } else {
+               if (errno == ETIMEDOUT)
+                       err = 0;
+       }
+
+       usb_release_interface(udev, intf);
+       usb_close(udev);
+
+       return err;
+}
+
+static struct device_id device_list[] = {
+       { 0x0a12, 0x1004, dongle_csr },
+       { -1 }
+};
+
+static struct device_id *match_device(uint16_t vendor, uint16_t product)
+{
+       int i;
+
+       for (i = 0; device_list[i].func; i++) {
+               if (vendor == device_list[i].vendor &&
+                               product == device_list[i].product)
+                       return &device_list[i];
+       }
+
+       return NULL;
+}
+
+static int find_devices(struct device_info *devinfo, size_t size)
+{
+       struct usb_bus *bus;
+       struct usb_device *dev;
+       struct device_id *id;
+       unsigned int count = 0;
+
+       usb_find_busses();
+       usb_find_devices();
+
+       for (bus = usb_get_busses(); bus; bus = bus->next)
+               for (dev = bus->devices; dev; dev = dev->next) {
+                       id = match_device(dev->descriptor.idVendor,
+                                               dev->descriptor.idProduct);
+                       if (!id)
+                               continue;
+
+                       if (count < size) {
+                               devinfo[count].dev = dev;
+                               devinfo[count].id = id;
+                               count++;
+                       }
+               }
+
+       return count;
+}
+
+static void usage(void)
+{
+       printf("avctrl - Bluetooth Audio/Video control utility\n\n");
+
+       printf("Usage:\n"
+               "\tavctrl [options] <command>\n"
+               "\n");
+
+       printf("Options:\n"
+               "\t-h, --help           Display help\n"
+               "\t-q, --quiet          Don't display any messages\n"
+               "\n");
+
+       printf("Commands:\n"
+               "\tdiscover         Simulate pressing the discover button\n"
+               "\tswitch           Switch the dongle to DFU mode\n"
+               "\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "quiet",      0, 0, 'q' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       struct device_info dev[16];
+       int i, opt, num, quiet = 0;
+
+       while ((opt = getopt_long(argc, argv, "+qh", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'q':
+                       quiet = 1;
+                       break;
+               case 'h':
+                       usage();
+                       exit(0);
+               default:
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       usb_init();
+
+       num = find_devices(dev, sizeof(dev) / sizeof(dev[0]));
+       if (num <= 0) {
+               if (!quiet)
+                       fprintf(stderr, "No Audio/Video devices found\n");
+               exit(1);
+       }
+
+       for (i = 0; i < num; i++) {
+               struct device_id *id = dev[i].id;
+               int err;
+
+               if (!quiet)
+                       printf("Selecting device %04x:%04x ",
+                                               id->vendor, id->product);
+               fflush(stdout);
+
+               err = id->func(&dev[i], argc, argv);
+               if (err < 0) {
+                       if (!quiet)
+                               printf("failed (%s)\n", strerror(-err));
+               } else {
+                       if (!quiet)
+                               printf("was successful\n");
+               }
+       }
+
+       return 0;
+}
diff --git a/tools/avinfo.c b/tools/avinfo.c
new file mode 100644 (file)
index 0000000..7f76c03
--- /dev/null
@@ -0,0 +1,672 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+
+#define AVDTP_PSM                      25
+
+/* Commands */
+#define AVDTP_DISCOVER                 0x01
+#define AVDTP_GET_CAPABILITIES         0x02
+
+#define AVDTP_PKT_TYPE_SINGLE          0x00
+
+#define AVDTP_MSG_TYPE_COMMAND         0x00
+
+/* 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
+
+/* 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
+
+#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 << 0)
+
+#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 << 0)
+
+#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 << 0)
+
+#define SBC_SUBBANDS_4                 (1 << 1)
+#define SBC_SUBBANDS_8                 (1 << 0)
+
+#define SBC_ALLOCATION_SNR             (1 << 1)
+#define SBC_ALLOCATION_LOUDNESS                (1 << 0)
+
+#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 << 0)
+
+#define MPEG_LAYER_MP1                 (1 << 2)
+#define MPEG_LAYER_MP2                 (1 << 1)
+#define MPEG_LAYER_MP3                 (1 << 0)
+
+#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 << 0)
+
+#define MPEG_BIT_RATE_VBR              0x8000
+#define MPEG_BIT_RATE_320000           0x4000
+#define MPEG_BIT_RATE_256000           0x2000
+#define MPEG_BIT_RATE_224000           0x1000
+#define MPEG_BIT_RATE_192000           0x0800
+#define MPEG_BIT_RATE_160000           0x0400
+#define MPEG_BIT_RATE_128000           0x0200
+#define MPEG_BIT_RATE_112000           0x0100
+#define MPEG_BIT_RATE_96000            0x0080
+#define MPEG_BIT_RATE_80000            0x0040
+#define MPEG_BIT_RATE_64000            0x0020
+#define MPEG_BIT_RATE_56000            0x0010
+#define MPEG_BIT_RATE_48000            0x0008
+#define MPEG_BIT_RATE_40000            0x0004
+#define MPEG_BIT_RATE_32000            0x0002
+#define MPEG_BIT_RATE_FREE             0x0001
+
+struct avdtp_service_capability {
+       uint8_t category;
+       uint8_t length;
+       uint8_t data[0];
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_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 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_req {
+       struct avdtp_header header;
+       uint8_t rfa0:2;
+       uint8_t acp_seid:6;
+} __attribute__ ((packed));
+
+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));
+
+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 avdtp_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 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_req {
+       struct avdtp_header header;
+       uint8_t acp_seid:6;
+       uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+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));
+
+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 discover_resp {
+       struct avdtp_header header;
+       struct seid_info seps[0];
+} __attribute__ ((packed));
+
+struct getcap_resp {
+       struct avdtp_header header;
+       uint8_t caps[0];
+} __attribute__ ((packed));
+
+
+static void print_mpeg12(struct mpeg_codec_cap *mpeg)
+{
+       printf("\tMedia Codec: MPEG12\n\t\tChannel Modes: ");
+
+       if (mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO)
+               printf("Mono ");
+       if (mpeg->channel_mode & MPEG_CHANNEL_MODE_DUAL_CHANNEL)
+               printf("DualChannel ");
+       if (mpeg->channel_mode & MPEG_CHANNEL_MODE_STEREO)
+               printf("Stereo ");
+       if (mpeg->channel_mode & MPEG_CHANNEL_MODE_JOINT_STEREO)
+               printf("JointStereo");
+
+       printf("\n\t\tFrequencies: ");
+       if (mpeg->frequency & MPEG_SAMPLING_FREQ_16000)
+               printf("16Khz ");
+       if (mpeg->frequency & MPEG_SAMPLING_FREQ_22050)
+               printf("22.05Khz ");
+       if (mpeg->frequency & MPEG_SAMPLING_FREQ_24000)
+               printf("24Khz ");
+       if (mpeg->frequency & MPEG_SAMPLING_FREQ_32000)
+               printf("32Khz ");
+       if (mpeg->frequency & MPEG_SAMPLING_FREQ_44100)
+               printf("44.1Khz ");
+       if (mpeg->frequency & MPEG_SAMPLING_FREQ_48000)
+               printf("48Khz ");
+
+       printf("\n\t\tCRC: %s", mpeg->crc ? "Yes" : "No");
+
+       printf("\n\t\tLayer: ");
+       if (mpeg->layer & MPEG_LAYER_MP1)
+               printf("1 ");
+       if (mpeg->layer & MPEG_LAYER_MP2)
+               printf("2 ");
+       if (mpeg->layer & MPEG_LAYER_MP3)
+               printf("3 ");
+
+       printf("\n\t\tBit Rate: ");
+       if (mpeg->bitrate & MPEG_BIT_RATE_FREE)
+               printf("Free format");
+       else {
+               if (mpeg->bitrate & MPEG_BIT_RATE_32000)
+                       printf("32kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_40000)
+                       printf("40kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_48000)
+                       printf("48kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_56000)
+                       printf("56kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_64000)
+                       printf("64kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_80000)
+                       printf("80kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_96000)
+                       printf("96kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_112000)
+                       printf("112kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_128000)
+                       printf("128kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_160000)
+                       printf("160kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_192000)
+                       printf("192kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_224000)
+                       printf("224kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_256000)
+                       printf("256kbps ");
+               if (mpeg->bitrate & MPEG_BIT_RATE_320000)
+                       printf("320kbps ");
+       }
+
+       printf("\n\t\tVBR: %s", mpeg->bitrate & MPEG_BIT_RATE_VBR ? "Yes" :
+               "No");
+
+       printf("\n\t\tPayload Format: ");
+       if (mpeg->mpf)
+               printf("RFC-2250 RFC-3119\n");
+       else
+               printf("RFC-2250\n");
+}
+
+static void print_sbc(struct sbc_codec_cap *sbc)
+{
+       printf("\tMedia Codec: SBC\n\t\tChannel Modes: ");
+
+       if (sbc->channel_mode & SBC_CHANNEL_MODE_MONO)
+               printf("Mono ");
+       if (sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+               printf("DualChannel ");
+       if (sbc->channel_mode & SBC_CHANNEL_MODE_STEREO)
+               printf("Stereo ");
+       if (sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+               printf("JointStereo");
+
+       printf("\n\t\tFrequencies: ");
+       if (sbc->frequency & SBC_SAMPLING_FREQ_16000)
+               printf("16Khz ");
+       if (sbc->frequency & SBC_SAMPLING_FREQ_32000)
+               printf("32Khz ");
+       if (sbc->frequency & SBC_SAMPLING_FREQ_44100)
+               printf("44.1Khz ");
+       if (sbc->frequency & SBC_SAMPLING_FREQ_48000)
+               printf("48Khz ");
+
+       printf("\n\t\tSubbands: ");
+       if (sbc->allocation_method & SBC_SUBBANDS_4)
+               printf("4 ");
+       if (sbc->allocation_method & SBC_SUBBANDS_8)
+               printf("8");
+
+       printf("\n\t\tBlocks: ");
+       if (sbc->block_length & SBC_BLOCK_LENGTH_4)
+               printf("4 ");
+       if (sbc->block_length & SBC_BLOCK_LENGTH_8)
+               printf("8 ");
+       if (sbc->block_length & SBC_BLOCK_LENGTH_12)
+               printf("12 ");
+       if (sbc->block_length & SBC_BLOCK_LENGTH_16)
+               printf("16 ");
+
+       printf("\n\t\tBitpool Range: %d-%d\n",
+                               sbc->min_bitpool, sbc->max_bitpool);
+}
+
+static void print_media_codec(struct avdtp_media_codec_capability *cap)
+{
+       switch (cap->media_codec_type) {
+       case A2DP_CODEC_SBC:
+               print_sbc((void *) cap);
+               break;
+       case A2DP_CODEC_MPEG12:
+               print_mpeg12((void *) cap);
+               break;
+       default:
+               printf("\tMedia Codec: Unknown\n");
+       }
+}
+
+static void print_caps(void *data, int size)
+{
+       int processed;
+
+       for (processed = 0; processed + 2 < size;) {
+               struct avdtp_service_capability *cap;
+
+               cap = data;
+
+               if (processed + 2 + cap->length > size) {
+                       printf("Invalid capability data in getcap resp\n");
+                       break;
+               }
+
+               switch (cap->category) {
+               case AVDTP_MEDIA_TRANSPORT:
+               case AVDTP_REPORTING:
+               case AVDTP_RECOVERY:
+               case AVDTP_CONTENT_PROTECTION:
+               case AVDTP_MULTIPLEXING:
+                       /* FIXME: Add proper functions */
+                       break;
+               case AVDTP_MEDIA_CODEC:
+                       print_media_codec((void *) cap->data);
+                       break;
+               }
+
+               processed += 2 + cap->length;
+               data += 2 + cap->length;
+       }
+}
+
+static void init_request(struct avdtp_header *header, int request_id)
+{
+       static int transaction = 0;
+
+       header->packet_type = AVDTP_PKT_TYPE_SINGLE;
+       header->message_type = AVDTP_MSG_TYPE_COMMAND;
+       header->transaction = transaction;
+       header->signal_id = request_id;
+
+       /* clear rfa bits */
+       header->rfa0 = 0;
+
+       transaction = (transaction + 1) % 16;
+}
+
+static ssize_t avdtp_send(int sk, void *data, int len)
+{
+       ssize_t ret;
+
+       ret = send(sk, data, len, 0);
+
+       if (ret < 0)
+               ret = -errno;
+       else if (ret != len)
+               ret = -EIO;
+
+       if (ret < 0) {
+               printf("Unable to send message: %s (%zd)\n",
+                                               strerror(-ret), -ret);
+               return ret;
+       }
+
+       return ret;
+}
+
+static ssize_t avdtp_receive(int sk, void *data, int len)
+{
+       ssize_t ret;
+
+       ret = recv(sk, data, len, 0);
+
+       if (ret < 0) {
+               printf("Unable to receive message: %s (%d)\n",
+                                               strerror(errno), errno);
+               return -errno;
+       }
+
+       return ret;
+}
+
+static ssize_t avdtp_get_caps(int sk, int seid)
+{
+       struct seid_req req;
+       char buffer[1024];
+       struct getcap_resp *caps = (void *) buffer;
+       ssize_t ret;
+
+       memset(&req, 0, sizeof(req));
+       init_request(&req.header, AVDTP_GET_CAPABILITIES);
+       req.acp_seid = seid;
+
+       ret = avdtp_send(sk, &req, sizeof(req));
+       if (ret < 0)
+               return ret;
+
+       memset(&buffer, 0, sizeof(buffer));
+       ret = avdtp_receive(sk, caps, sizeof(buffer));
+       if (ret < 0)
+               return ret;
+
+       if ((size_t) ret < (sizeof(struct getcap_resp) + 4 +
+                       sizeof(struct avdtp_media_codec_capability))) {
+               printf("Invalid capabilities\n");
+               return -1;
+       }
+
+       print_caps(caps, ret);
+
+       return 0;
+}
+
+static ssize_t avdtp_discover(int sk)
+{
+       struct avdtp_header req;
+       char buffer[256];
+       struct discover_resp *discover = (void *) buffer;
+       int seps, i;
+       ssize_t ret;
+
+       memset(&req, 0, sizeof(req));
+       init_request(&req, AVDTP_DISCOVER);
+
+       ret = avdtp_send(sk, &req, sizeof(req));
+       if (ret < 0)
+               return ret;
+
+       memset(&buffer, 0, sizeof(buffer));
+       ret = avdtp_receive(sk, discover, sizeof(buffer));
+       if (ret < 0)
+               return ret;
+
+       seps = (ret - sizeof(struct avdtp_header)) / sizeof(struct seid_info);
+       for (i = 0; i < seps; i++) {
+               const char *type, *media;
+
+               switch (discover->seps[i].type) {
+               case AVDTP_SEP_TYPE_SOURCE:
+                       type = "Source";
+                       break;
+               case AVDTP_SEP_TYPE_SINK:
+                       type = "Sink";
+                       break;
+               default:
+                       type = "Invalid";
+               }
+
+               switch (discover->seps[i].media_type) {
+               case AVDTP_MEDIA_TYPE_AUDIO:
+                       media = "Audio";
+                       break;
+               case AVDTP_MEDIA_TYPE_VIDEO:
+                       media = "Video";
+                       break;
+               case AVDTP_MEDIA_TYPE_MULTIMEDIA:
+                       media = "Multimedia";
+                       break;
+               default:
+                       media = "Invalid";
+               }
+
+               printf("Stream End-Point #%d: %s %s %s\n",
+                                       discover->seps[i].seid, media, type,
+                                       discover->seps[i].inuse ? "*" : "");
+
+               avdtp_get_caps(sk, discover->seps[i].seid);
+       }
+
+       return 0;
+}
+
+static int l2cap_connect(bdaddr_t *src, bdaddr_t *dst)
+{
+       struct sockaddr_l2 l2a;
+       int sk;
+
+       memset(&l2a, 0, sizeof(l2a));
+       l2a.l2_family = AF_BLUETOOTH;
+       bacpy(&l2a.l2_bdaddr, src);
+
+       sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+       if (sk < 0) {
+               printf("Cannot create L2CAP socket. %s(%d)\n", strerror(errno),
+                               errno);
+               return -errno;
+       }
+
+       if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) {
+               printf("Bind failed. %s (%d)\n", strerror(errno), errno);
+               return -errno;
+       }
+
+       memset(&l2a, 0, sizeof(l2a));
+       l2a.l2_family = AF_BLUETOOTH;
+       bacpy(&l2a.l2_bdaddr, dst);
+       l2a.l2_psm = htobs(AVDTP_PSM);
+
+       if (connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) {
+               printf("Connect failed. %s(%d)\n", strerror(errno), errno);
+               return -errno;
+       }
+
+       return sk;
+}
+
+static void usage()
+{
+       printf("avinfo - Audio/Video Info Tool ver %s\n", VERSION);
+       printf("Usage:\n"
+               "\tavinfo [options] <remote address>\n");
+       printf("Options:\n"
+               "\t-h\t\tDisplay help\n"
+               "\t-i\t\tSpecify source interface\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "device",     1, 0, 'i' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       bdaddr_t src, dst;
+       int opt, sk, dev_id;
+
+       if (argc < 2) {
+               usage();
+               exit(0);
+       }
+
+       bacpy(&src, BDADDR_ANY);
+       dev_id = hci_get_route(&src);
+       if ((dev_id < 0) || (hci_devba(dev_id, &src) < 0)) {
+               printf("Cannot find any local adapter\n");
+               exit(-1);
+       }
+
+       while ((opt = getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'i':
+                       if (!strncmp(optarg, "hci", 3))
+                               hci_devba(atoi(optarg + 3), &src);
+                       else
+                               str2ba(optarg, &src);
+                       break;
+
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       printf("Connecting ... \n");
+
+       if (bachk(argv[optind]) < 0) {
+               printf("Invalid argument\n");
+               exit(1);
+       }
+
+       str2ba(argv[optind], &dst);
+       sk = l2cap_connect(&src, &dst);
+       if (sk < 0)
+               exit(1);
+
+       if (avdtp_discover(sk) < 0)
+               exit(1);
+
+       return 0;
+}
diff --git a/tools/bccmd.8 b/tools/bccmd.8
new file mode 100644 (file)
index 0000000..28cbe88
--- /dev/null
@@ -0,0 +1,130 @@
+.TH BCCMD 8 "Jun 20 2006" BlueZ "Linux System Administration"
+.SH NAME
+bccmd \- Utility for the CSR BCCMD interface
+.SH SYNOPSIS
+.B bccmd
+.br
+.B bccmd [-t <transport>] [-d <device>] <command> [<args>]
+.br
+.B bccmd [-h --help]
+.br
+.SH DESCRIPTION
+.B
+bccmd
+issues BlueCore commands to
+.B
+Cambridge Silicon Radio
+devices. If run without the <command> argument, a short help page will be displayed.
+.SH OPTIONS
+.TP
+.BI -t\ <transport>
+Specify the communication transport. Valid options are:
+.RS
+.TP
+.BI HCI
+Local device with Host Controller Interface (default).
+.TP
+.BI USB
+Direct USB connection.
+.TP
+.BI BCSP
+Blue Core Serial Protocol.
+.TP
+.BI H4
+H4 serial protocol.
+.TP
+.BI 3WIRE
+3WIRE protocol (not implemented).
+.SH
+.TP
+.BI -d\ <dev>
+Specify a particular device to operate on. If not specified, default is the first available HCI device
+or /dev/ttyS0 for serial transports.
+.SH COMMANDS
+.TP
+.BI builddef
+Get build definitions
+.TP
+.BI keylen\ <handle>
+Get current crypt key length
+.TP
+.BI clock
+Get local Bluetooth clock
+.TP
+.BI rand
+Get random number
+.TP
+.BI chiprev
+Get chip revision
+.TP
+.BI buildname
+Get the full build name
+.TP
+.BI panicarg
+Get panic code argument
+.TP
+.BI faultarg
+Get fault code argument
+.TP
+.BI coldreset
+Perform cold reset
+.TP
+.BI warmreset
+Perform warm reset
+.TP
+.BI disabletx
+Disable TX on the device
+.TP
+.BI enabletx
+Enable TX on the device
+.TP
+.BI singlechan\ <channel>
+Lock radio on specific channel
+.TP
+.BI hoppingon
+Revert to channel hopping
+.TP
+.BI rttxdata1\ <decimal\ freq\ MHz>\ <level>
+TXData1 radio test
+.TP
+.BI radiotest\ <decimal\ freq\ MHz>\ <level>\ <id>
+Run radio tests, tests 4, 6 and 7 are transmit tests
+.TP
+.BI memtypes
+Get memory types
+.TP
+.BI psget\ [-r]\ [-s\ <stores>]\ <key>
+Get value for PS key.
+-r sends a warm reset afterwards
+.TP
+.BI psset\ [-r]\ [-s\ <stores>]\ <key>\ <value>
+Set value for PS key.
+-r sends a warm reset afterwards
+.TP
+.BI psclr\ [-r]\ [-s\ <stores>]\ <key>
+Clear value for PS key.
+-r sends a warm reset afterwards
+.TP
+.BI pslist\ [-r]\ [-s\ <stores>]
+List all PS keys.
+-r sends a warm reset afterwards
+.TP
+.BI psread\ [-r]\ [-s\ <stores>]
+Read all PS keys.
+-r sends a warm reset afterwards
+.TP
+.BI psload\ [-r]\ [-s\ <stores>]\ <file>
+Load all PS keys from PSR file.
+-r sends a warm reset afterwards
+.TP
+.BI pscheck\ [-r]\ [-s\ <stores>]\ <file>
+Check syntax of PSR file.
+-r sends a warm reset afterwards
+.SH KEYS
+bdaddr country devclass keymin keymax features commands version
+remver hciextn mapsco baudrate hostintf anafreq anaftrim usbvid
+usbpid dfupid bootmode
+.SH AUTHORS
+Written by Marcel Holtmann <marcel@holtmann.org>,
+man page by Adam Laurie <adam@algroup.co.uk>
+.PP
diff --git a/tools/bccmd.c b/tools/bccmd.c
new file mode 100644 (file)
index 0000000..5cb9255
--- /dev/null
@@ -0,0 +1,1254 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "csr.h"
+
+#define CSR_TRANSPORT_UNKNOWN  0
+#define CSR_TRANSPORT_HCI      1
+#define CSR_TRANSPORT_USB      2
+#define CSR_TRANSPORT_BCSP     3
+#define CSR_TRANSPORT_H4       4
+#define CSR_TRANSPORT_3WIRE    5
+
+#define CSR_STORES_PSI         (0x0001)
+#define CSR_STORES_PSF         (0x0002)
+#define CSR_STORES_PSROM       (0x0004)
+#define CSR_STORES_PSRAM       (0x0008)
+#define CSR_STORES_DEFAULT     (CSR_STORES_PSI | CSR_STORES_PSF)
+
+#define CSR_TYPE_NULL          0
+#define CSR_TYPE_COMPLEX       1
+#define CSR_TYPE_UINT8         2
+#define CSR_TYPE_UINT16                3
+#define CSR_TYPE_UINT32                4
+
+#define CSR_TYPE_ARRAY         CSR_TYPE_COMPLEX
+#define CSR_TYPE_BDADDR                CSR_TYPE_COMPLEX
+
+static inline int transport_open(int transport, char *device, speed_t bcsp_rate)
+{
+       switch (transport) {
+       case CSR_TRANSPORT_HCI:
+               return csr_open_hci(device);
+#ifdef HAVE_LIBUSB
+       case CSR_TRANSPORT_USB:
+               return csr_open_usb(device);
+#endif
+       case CSR_TRANSPORT_BCSP:
+               return csr_open_bcsp(device, bcsp_rate);
+       case CSR_TRANSPORT_H4:
+               return csr_open_h4(device);
+       case CSR_TRANSPORT_3WIRE:
+               return csr_open_3wire(device);
+       default:
+               fprintf(stderr, "Unsupported transport\n");
+               return -1;
+       }
+}
+
+static inline int transport_read(int transport, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       switch (transport) {
+       case CSR_TRANSPORT_HCI:
+               return csr_read_hci(varid, value, length);
+#ifdef HAVE_LIBUSB
+       case CSR_TRANSPORT_USB:
+               return csr_read_usb(varid, value, length);
+#endif
+       case CSR_TRANSPORT_BCSP:
+               return csr_read_bcsp(varid, value, length);
+       case CSR_TRANSPORT_H4:
+               return csr_read_h4(varid, value, length);
+       case CSR_TRANSPORT_3WIRE:
+               return csr_read_3wire(varid, value, length);
+       default:
+               errno = EOPNOTSUPP;
+               return -1;
+       }
+}
+
+static inline int transport_write(int transport, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       switch (transport) {
+       case CSR_TRANSPORT_HCI:
+               return csr_write_hci(varid, value, length);
+#ifdef HAVE_LIBUSB
+       case CSR_TRANSPORT_USB:
+               return csr_write_usb(varid, value, length);
+#endif
+       case CSR_TRANSPORT_BCSP:
+               return csr_write_bcsp(varid, value, length);
+       case CSR_TRANSPORT_H4:
+               return csr_write_h4(varid, value, length);
+       case CSR_TRANSPORT_3WIRE:
+               return csr_write_3wire(varid, value, length);
+       default:
+               errno = EOPNOTSUPP;
+               return -1;
+       }
+}
+
+static inline void transport_close(int transport)
+{
+       switch (transport) {
+       case CSR_TRANSPORT_HCI:
+               csr_close_hci();
+               break;
+#ifdef HAVE_LIBUSB
+       case CSR_TRANSPORT_USB:
+               csr_close_usb();
+               break;
+#endif
+       case CSR_TRANSPORT_BCSP:
+               csr_close_bcsp();
+               break;
+       case CSR_TRANSPORT_H4:
+               csr_close_h4();
+               break;
+       case CSR_TRANSPORT_3WIRE:
+               csr_close_3wire();
+               break;
+       }
+}
+
+static struct {
+       uint16_t pskey;
+       int type;
+       int size;
+       char *str;
+} storage[] = {
+       { CSR_PSKEY_BDADDR,                   CSR_TYPE_BDADDR,  8,  "bdaddr"   },
+       { CSR_PSKEY_COUNTRYCODE,              CSR_TYPE_UINT16,  0,  "country"  },
+       { CSR_PSKEY_CLASSOFDEVICE,            CSR_TYPE_UINT32,  0,  "devclass" },
+       { CSR_PSKEY_ENC_KEY_LMIN,             CSR_TYPE_UINT16,  0,  "keymin"   },
+       { CSR_PSKEY_ENC_KEY_LMAX,             CSR_TYPE_UINT16,  0,  "keymax"   },
+       { CSR_PSKEY_LOCAL_SUPPORTED_FEATURES, CSR_TYPE_ARRAY,   8,  "features" },
+       { CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS, CSR_TYPE_ARRAY,   18, "commands" },
+       { CSR_PSKEY_HCI_LMP_LOCAL_VERSION,    CSR_TYPE_UINT16,  0,  "version"  },
+       { CSR_PSKEY_LMP_REMOTE_VERSION,       CSR_TYPE_UINT8,   0,  "remver"   },
+       { CSR_PSKEY_HOSTIO_USE_HCI_EXTN,      CSR_TYPE_UINT16,  0,  "hciextn"  },
+       { CSR_PSKEY_HOSTIO_MAP_SCO_PCM,       CSR_TYPE_UINT16,  0,  "mapsco"   },
+       { CSR_PSKEY_UART_BAUDRATE,            CSR_TYPE_UINT16,  0,  "baudrate" },
+       { CSR_PSKEY_HOST_INTERFACE,           CSR_TYPE_UINT16,  0,  "hostintf" },
+       { CSR_PSKEY_ANA_FREQ,                 CSR_TYPE_UINT16,  0,  "anafreq"  },
+       { CSR_PSKEY_ANA_FTRIM,                CSR_TYPE_UINT16,  0,  "anaftrim" },
+       { CSR_PSKEY_USB_VENDOR_ID,            CSR_TYPE_UINT16,  0,  "usbvid"   },
+       { CSR_PSKEY_USB_PRODUCT_ID,           CSR_TYPE_UINT16,  0,  "usbpid"   },
+       { CSR_PSKEY_USB_DFU_PRODUCT_ID,       CSR_TYPE_UINT16,  0,  "dfupid"   },
+       { CSR_PSKEY_INITIAL_BOOTMODE,         CSR_TYPE_UINT16,  0,  "bootmode" },
+       { 0x0000 },
+};
+
+static char *storestostr(uint16_t stores)
+{
+       switch (stores) {
+       case 0x0000:
+               return "Default";
+       case 0x0001:
+               return "psi";
+       case 0x0002:
+               return "psf";
+       case 0x0004:
+               return "psrom";
+       case 0x0008:
+               return "psram";
+       default:
+               return "Unknown";
+       }
+}
+
+static char *memorytostr(uint16_t type)
+{
+       switch (type) {
+       case 0x0000:
+               return "Flash memory";
+       case 0x0001:
+               return "EEPROM";
+       case 0x0002:
+               return "RAM (transient)";
+       case 0x0003:
+               return "ROM (or \"read-only\" flash memory)";
+       default:
+               return "Unknown";
+       }
+}
+
+#define OPT_RANGE(min, max) \
+               if (argc < (min)) { errno = EINVAL; return -1; } \
+               if (argc > (max)) { errno = E2BIG; return -1; }
+
+static struct option help_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static int opt_help(int argc, char *argv[], int *help)
+{
+       int opt;
+
+       while ((opt=getopt_long(argc, argv, "+h", help_options, NULL)) != EOF) {
+               switch (opt) {
+               case 'h':
+                       if (help)
+                               *help = 1;
+                       break;
+               }
+       }
+
+       return optind;
+}
+
+#define OPT_HELP(range, help) \
+               opt_help(argc, argv, (help)); \
+               argc -= optind; argv += optind; optind = 0; \
+               OPT_RANGE((range), (range))
+
+static int cmd_builddef(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t def = 0x0000, nextdef = 0x0000;
+       int err = 0;
+
+       OPT_HELP(0, NULL);
+
+       printf("Build definitions:\n");
+
+       while (1) {
+               memset(array, 0, sizeof(array));
+               array[0] = def & 0xff;
+               array[1] = def >> 8;
+
+               err = transport_read(transport, CSR_VARID_GET_NEXT_BUILDDEF, array, 8);
+               if (err < 0) {
+                       errno = -err;
+                       break;
+               }
+
+               nextdef = array[2] | (array[3] << 8);
+
+               if (nextdef == 0x0000)
+                       break;
+
+               def = nextdef;
+
+               printf("0x%04x - %s\n", def, csr_builddeftostr(def));
+       }
+
+       return err;
+}
+
+static int cmd_keylen(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t handle, keylen;
+       int err;
+
+       OPT_HELP(1, NULL);
+
+       handle = atoi(argv[0]);
+
+       memset(array, 0, sizeof(array));
+       array[0] = handle & 0xff;
+       array[1] = handle >> 8;
+
+       err = transport_read(transport, CSR_VARID_CRYPT_KEY_LENGTH, array, 8);
+       if (err < 0) {
+               errno = -err;
+               return -1;
+       }
+
+       handle = array[0] | (array[1] << 8);
+       keylen = array[2] | (array[3] << 8);
+
+       printf("Crypt key length: %d bit\n", keylen * 8);
+
+       return 0;
+}
+
+static int cmd_clock(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint32_t clock;
+       int err;
+
+       OPT_HELP(0, NULL);
+
+       memset(array, 0, sizeof(array));
+
+       err = transport_read(transport, CSR_VARID_BT_CLOCK, array, 8);
+       if (err < 0) {
+               errno = -err;
+               return -1;
+       }
+
+       clock = array[2] | (array[3] << 8) | (array[0] << 16) | (array[1] << 24);
+
+       printf("Bluetooth clock: 0x%04x (%d)\n", clock, clock);
+
+       return 0;
+}
+
+static int cmd_rand(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t rand;
+       int err;
+
+       OPT_HELP(0, NULL);
+
+       memset(array, 0, sizeof(array));
+
+       err = transport_read(transport, CSR_VARID_RAND, array, 8);
+       if (err < 0) {
+               errno = -err;
+               return -1;
+       }
+
+       rand = array[0] | (array[1] << 8);
+
+       printf("Random number: 0x%02x (%d)\n", rand, rand);
+
+       return 0;
+}
+
+static int cmd_chiprev(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t rev;
+       char *str;
+       int err;
+
+       OPT_HELP(0, NULL);
+
+       memset(array, 0, sizeof(array));
+
+       err = transport_read(transport, CSR_VARID_CHIPREV, array, 8);
+       if (err < 0) {
+               errno = -err;
+               return -1;
+       }
+
+       rev = array[0] | (array[1] << 8);
+
+       switch (rev) {
+       case 0x64:
+               str = "BC1 ES";
+               break;
+       case 0x65:
+               str = "BC1";
+               break;
+       case 0x89:
+               str = "BC2-External A";
+               break;
+       case 0x8a:
+               str = "BC2-External B";
+               break;
+       case 0x28:
+               str = "BC2-ROM";
+               break;
+       case 0x43:
+               str = "BC3-Multimedia";
+               break;
+       case 0x15:
+               str = "BC3-ROM";
+               break;
+       case 0xe2:
+               str = "BC3-Flash";
+               break;
+       case 0x26:
+               str = "BC4-External";
+               break;
+       case 0x30:
+               str = "BC4-ROM";
+               break;
+       default:
+               str = "NA";
+               break;
+       }
+
+       printf("Chip revision: 0x%04x (%s)\n", rev, str);
+
+       return 0;
+}
+
+static int cmd_buildname(int transport, int argc, char *argv[])
+{
+       uint8_t array[130];
+       char name[64];
+       unsigned int i;
+       int err;
+
+       OPT_HELP(0, NULL);
+
+       memset(array, 0, sizeof(array));
+
+       err = transport_read(transport, CSR_VARID_READ_BUILD_NAME, array, 128);
+       if (err < 0) {
+               errno = -err;
+               return -1;
+       }
+
+       for (i = 0; i < sizeof(name); i++)
+               name[i] = array[(i * 2) + 4];
+
+       printf("Build name: %s\n", name);
+
+       return 0;
+}
+
+static int cmd_panicarg(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t error;
+       int err;
+
+       OPT_HELP(0, NULL);
+
+       memset(array, 0, sizeof(array));
+
+       err = transport_read(transport, CSR_VARID_PANIC_ARG, array, 8);
+       if (err < 0) {
+               errno = -err;
+               return -1;
+       }
+
+       error = array[0] | (array[1] << 8);
+
+       printf("Panic code: 0x%02x (%s)\n", error,
+                                       error < 0x100 ? "valid" : "invalid");
+
+       return 0;
+}
+
+static int cmd_faultarg(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t error;
+       int err;
+
+       OPT_HELP(0, NULL);
+
+       memset(array, 0, sizeof(array));
+
+       err = transport_read(transport, CSR_VARID_FAULT_ARG, array, 8);
+       if (err < 0) {
+               errno = -err;
+               return -1;
+       }
+
+       error = array[0] | (array[1] << 8);
+
+       printf("Fault code: 0x%02x (%s)\n", error,
+                                       error < 0x100 ? "valid" : "invalid");
+
+       return 0;
+}
+
+static int cmd_coldreset(int transport, int argc, char *argv[])
+{
+       return transport_write(transport, CSR_VARID_COLD_RESET, NULL, 0);
+}
+
+static int cmd_warmreset(int transport, int argc, char *argv[])
+{
+       return transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);
+}
+
+static int cmd_disabletx(int transport, int argc, char *argv[])
+{
+       return transport_write(transport, CSR_VARID_DISABLE_TX, NULL, 0);
+}
+
+static int cmd_enabletx(int transport, int argc, char *argv[])
+{
+       return transport_write(transport, CSR_VARID_ENABLE_TX, NULL, 0);
+}
+
+static int cmd_singlechan(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t channel;
+
+       OPT_HELP(1, NULL);
+
+       channel = atoi(argv[0]);
+
+       if (channel > 2401 && channel < 2481)
+               channel -= 2402;
+
+       if (channel > 78) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       memset(array, 0, sizeof(array));
+       array[0] = channel & 0xff;
+       array[1] = channel >> 8;
+
+       return transport_write(transport, CSR_VARID_SINGLE_CHAN, array, 8);
+}
+
+static int cmd_hoppingon(int transport, int argc, char *argv[])
+{
+       return transport_write(transport, CSR_VARID_HOPPING_ON, NULL, 0);
+}
+
+static int cmd_rttxdata1(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t freq, level;
+
+       OPT_HELP(2, NULL);
+
+       freq = atoi(argv[0]);
+
+       if (!strncasecmp(argv[1], "0x", 2))
+               level = strtol(argv[1], NULL, 16);
+       else
+               level = atoi(argv[1]);
+
+       memset(array, 0, sizeof(array));
+       array[0] = 0x04;
+       array[1] = 0x00;
+       array[2] = freq & 0xff;
+       array[3] = freq >> 8;
+       array[4] = level & 0xff;
+       array[5] = level >> 8;
+
+       return transport_write(transport, CSR_VARID_RADIOTEST, array, 8);
+}
+
+static int cmd_radiotest(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t freq, level, test;
+
+       OPT_HELP(3, NULL);
+
+       freq = atoi(argv[0]);
+
+       if (!strncasecmp(argv[1], "0x", 2))
+               level = strtol(argv[1], NULL, 16);
+       else
+               level = atoi(argv[1]);
+
+       test = atoi(argv[2]);
+
+       memset(array, 0, sizeof(array));
+       array[0] = test & 0xff;
+       array[1] = test >> 8;
+       array[2] = freq & 0xff;
+       array[3] = freq >> 8;
+       array[4] = level & 0xff;
+       array[5] = level >> 8;
+
+       return transport_write(transport, CSR_VARID_RADIOTEST, array, 8);
+}
+
+static int cmd_memtypes(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t type, stores[4] = { 0x0001, 0x0002, 0x0004, 0x0008 };
+       int i, err;
+
+       OPT_HELP(0, NULL);
+
+       for (i = 0; i < 4; i++) {
+               memset(array, 0, sizeof(array));
+               array[0] = stores[i] & 0xff;
+               array[1] = stores[i] >> 8;
+
+               err = transport_read(transport, CSR_VARID_PS_MEMORY_TYPE, array, 8);
+               if (err < 0)
+                       continue;
+
+               type = array[2] + (array[3] << 8);
+
+               printf("%s (0x%04x) = %s (%d)\n", storestostr(stores[i]),
+                                       stores[i], memorytostr(type), type);
+       }
+
+       return 0;
+}
+
+static struct option pskey_options[] = {
+       { "stores",     1, 0, 's' },
+       { "reset",      0, 0, 'r' },
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static int opt_pskey(int argc, char *argv[], uint16_t *stores, int *reset, int *help)
+{
+       int opt;
+
+       while ((opt=getopt_long(argc, argv, "+s:rh", pskey_options, NULL)) != EOF) {
+               switch (opt) {
+               case 's':
+                       if (!stores)
+                               break;
+                       if (!strcasecmp(optarg, "default"))
+                               *stores = 0x0000;
+                       else if (!strcasecmp(optarg, "implementation"))
+                               *stores = 0x0001;
+                       else if (!strcasecmp(optarg, "factory"))
+                               *stores = 0x0002;
+                       else if (!strcasecmp(optarg, "rom"))
+                               *stores = 0x0004;
+                       else if (!strcasecmp(optarg, "ram"))
+                               *stores = 0x0008;
+                       else if (!strcasecmp(optarg, "psi"))
+                               *stores = 0x0001;
+                       else if (!strcasecmp(optarg, "psf"))
+                               *stores = 0x0002;
+                       else if (!strcasecmp(optarg, "psrom"))
+                               *stores = 0x0004;
+                       else if (!strcasecmp(optarg, "psram"))
+                               *stores = 0x0008;
+                       else if (!strncasecmp(optarg, "0x", 2))
+                               *stores = strtol(optarg, NULL, 16);
+                       else
+                               *stores = atoi(optarg);
+                       break;
+
+               case 'r':
+                       if (reset)
+                               *reset = 1;
+                       break;
+
+               case 'h':
+                       if (help)
+                               *help = 1;
+                       break;
+               }
+       }
+
+       return optind;
+}
+
+#define OPT_PSKEY(min, max, stores, reset, help) \
+               opt_pskey(argc, argv, (stores), (reset), (help)); \
+               argc -= optind; argv += optind; optind = 0; \
+               OPT_RANGE((min), (max))
+
+static int cmd_psget(int transport, int argc, char *argv[])
+{
+       uint8_t array[128];
+       uint16_t pskey, length, value, stores = CSR_STORES_DEFAULT;
+       uint32_t val32;
+       int i, err, reset = 0;
+
+       memset(array, 0, sizeof(array));
+
+       OPT_PSKEY(1, 1, &stores, &reset, NULL);
+
+       if (strncasecmp(argv[0], "0x", 2)) {
+               pskey = atoi(argv[0]);
+
+               for (i = 0; storage[i].pskey; i++) {
+                       if (strcasecmp(storage[i].str, argv[0]))
+                               continue;
+
+                       pskey = storage[i].pskey;
+                       break;
+               }
+       } else
+               pskey = strtol(argv[0] + 2, NULL, 16);
+
+       memset(array, 0, sizeof(array));
+       array[0] = pskey & 0xff;
+       array[1] = pskey >> 8;
+       array[2] = stores & 0xff;
+       array[3] = stores >> 8;
+
+       err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8);
+       if (err < 0)
+               return err;
+
+       length = array[2] + (array[3] << 8);
+       if (length + 6 > (int) sizeof(array) / 2)
+               return -EIO;
+
+       memset(array, 0, sizeof(array));
+       array[0] = pskey & 0xff;
+       array[1] = pskey >> 8;
+       array[2] = length & 0xff;
+       array[3] = length >> 8;
+       array[4] = stores & 0xff;
+       array[5] = stores >> 8;
+
+       err = transport_read(transport, CSR_VARID_PS, array, (length + 3) * 2);
+       if (err < 0)
+               return err;
+
+       switch (length) {
+       case 1:
+               value = array[6] | (array[7] << 8);
+               printf("%s: 0x%04x (%d)\n", csr_pskeytostr(pskey), value, value);
+               break;
+
+       case 2:
+               val32 = array[8] | (array[9] << 8) | (array[6] << 16) | (array[7] << 24);
+               printf("%s: 0x%08x (%d)\n", csr_pskeytostr(pskey), val32, val32);
+               break;
+
+       default:
+               printf("%s:", csr_pskeytostr(pskey));
+               for (i = 0; i < length; i++)
+                       printf(" 0x%02x%02x", array[(i * 2) + 6], array[(i * 2) + 7]);
+               printf("\n");
+               break;
+       }
+
+       if (reset)
+               transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);
+
+       return err;
+}
+
+static int cmd_psset(int transport, int argc, char *argv[])
+{
+       uint8_t array[128];
+       uint16_t pskey, length, value, stores = CSR_STORES_PSRAM;
+       uint32_t val32;
+       int i, err, reset = 0;
+
+       memset(array, 0, sizeof(array));
+
+       OPT_PSKEY(2, 81, &stores, &reset, NULL);
+
+       if (strncasecmp(argv[0], "0x", 2)) {
+               pskey = atoi(argv[0]);
+
+               for (i = 0; storage[i].pskey; i++) {
+                       if (strcasecmp(storage[i].str, argv[0]))
+                               continue;
+
+                       pskey = storage[i].pskey;
+                       break;
+               }
+       } else
+               pskey = strtol(argv[0] + 2, NULL, 16);
+
+       memset(array, 0, sizeof(array));
+       array[0] = pskey & 0xff;
+       array[1] = pskey >> 8;
+       array[2] = stores & 0xff;
+       array[3] = stores >> 8;
+
+       err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8);
+       if (err < 0)
+               return err;
+
+       length = array[2] + (array[3] << 8);
+       if (length + 6 > (int) sizeof(array) / 2)
+               return -EIO;
+
+       memset(array, 0, sizeof(array));
+       array[0] = pskey & 0xff;
+       array[1] = pskey >> 8;
+       array[2] = length & 0xff;
+       array[3] = length >> 8;
+       array[4] = stores & 0xff;
+       array[5] = stores >> 8;
+
+       argc--;
+       argv++;
+
+       switch (length) {
+       case 1:
+               if (argc != 1) {
+                       errno = E2BIG;
+                       return -1;
+               }
+
+               if (!strncasecmp(argv[0], "0x", 2))
+                       value = strtol(argv[0] + 2, NULL, 16);
+               else
+                       value = atoi(argv[0]);
+
+               array[6] = value & 0xff;
+               array[7] = value >> 8;
+               break;
+
+       case 2:
+               if (argc != 1) {
+                       errno = E2BIG;
+                       return -1;
+               }
+
+               if (!strncasecmp(argv[0], "0x", 2))
+                       val32 = strtol(argv[0] + 2, NULL, 16);
+               else
+                       val32 = atoi(argv[0]);
+
+               array[6] = (val32 & 0xff0000) >> 16;
+               array[7] = val32 >> 24;
+               array[8] = val32 & 0xff;
+               array[9] = (val32 & 0xff00) >> 8;
+               break;
+
+       default:
+               if (argc != length * 2) {
+                       errno = EINVAL;
+                       return -1;
+               }
+
+               for (i = 0; i < length * 2; i++)
+                       if (!strncasecmp(argv[0], "0x", 2))
+                               array[i + 6] = strtol(argv[i] + 2, NULL, 16);
+                       else
+                               array[i + 6] = atoi(argv[i]);
+               break;
+       }
+
+       err = transport_write(transport, CSR_VARID_PS, array, (length + 3) * 2);
+       if (err < 0)
+               return err;
+
+       if (reset)
+               transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);
+
+       return err;
+}
+
+static int cmd_psclr(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t pskey, stores = CSR_STORES_PSRAM;
+       int i, err, reset = 0;
+
+       OPT_PSKEY(1, 1, &stores, &reset, NULL);
+
+       if (strncasecmp(argv[0], "0x", 2)) {
+               pskey = atoi(argv[0]);
+
+               for (i = 0; storage[i].pskey; i++) {
+                       if (strcasecmp(storage[i].str, argv[0]))
+                               continue;
+
+                       pskey = storage[i].pskey;
+                       break;
+               }
+       } else
+               pskey = strtol(argv[0] + 2, NULL, 16);
+
+       memset(array, 0, sizeof(array));
+       array[0] = pskey & 0xff;
+       array[1] = pskey >> 8;
+       array[2] = stores & 0xff;
+       array[3] = stores >> 8;
+
+       err = transport_write(transport, CSR_VARID_PS_CLR_STORES, array, 8);
+       if (err < 0)
+               return err;
+
+       if (reset)
+               transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);
+
+       return err;
+}
+
+static int cmd_pslist(int transport, int argc, char *argv[])
+{
+       uint8_t array[8];
+       uint16_t pskey = 0x0000, length, stores = CSR_STORES_DEFAULT;
+       int err, reset = 0;
+
+       OPT_PSKEY(0, 0, &stores, &reset, NULL);
+
+       while (1) {
+               memset(array, 0, sizeof(array));
+               array[0] = pskey & 0xff;
+               array[1] = pskey >> 8;
+               array[2] = stores & 0xff;
+               array[3] = stores >> 8;
+
+               err = transport_read(transport, CSR_VARID_PS_NEXT, array, 8);
+               if (err < 0)
+                       break;
+
+               pskey = array[4] + (array[5] << 8);
+               if (pskey == 0x0000)
+                       break;
+
+               memset(array, 0, sizeof(array));
+               array[0] = pskey & 0xff;
+               array[1] = pskey >> 8;
+               array[2] = stores & 0xff;
+               array[3] = stores >> 8;
+
+               err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8);
+               if (err < 0)
+                       continue;
+
+               length = array[2] + (array[3] << 8);
+
+               printf("0x%04x - %s (%d bytes)\n", pskey,
+                                       csr_pskeytostr(pskey), length * 2);
+       }
+
+       if (reset)
+               transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);
+
+       return 0;
+}
+
+static int cmd_psread(int transport, int argc, char *argv[])
+{
+       uint8_t array[256];
+       uint16_t pskey = 0x0000, length, stores = CSR_STORES_DEFAULT;
+       char *str, val[7];
+       int i, err, reset = 0;
+
+       OPT_PSKEY(0, 0, &stores, &reset, NULL);
+
+       while (1) {
+               memset(array, 0, sizeof(array));
+               array[0] = pskey & 0xff;
+               array[1] = pskey >> 8;
+               array[2] = stores & 0xff;
+               array[3] = stores >> 8;
+
+               err = transport_read(transport, CSR_VARID_PS_NEXT, array, 8);
+               if (err < 0)
+                       break;
+
+               pskey = array[4] + (array[5] << 8);
+               if (pskey == 0x0000)
+                       break;
+
+               memset(array, 0, sizeof(array));
+               array[0] = pskey & 0xff;
+               array[1] = pskey >> 8;
+               array[2] = stores & 0xff;
+               array[3] = stores >> 8;
+
+               err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8);
+               if (err < 0)
+                       continue;
+
+               length = array[2] + (array[3] << 8);
+               if (length + 6 > (int) sizeof(array) / 2)
+                       continue;
+
+               memset(array, 0, sizeof(array));
+               array[0] = pskey & 0xff;
+               array[1] = pskey >> 8;
+               array[2] = length & 0xff;
+               array[3] = length >> 8;
+               array[4] = stores & 0xff;
+               array[5] = stores >> 8;
+
+               err = transport_read(transport, CSR_VARID_PS, array, (length + 3) * 2);
+               if (err < 0)
+                       continue;
+
+               str = csr_pskeytoval(pskey);
+               if (!strcasecmp(str, "UNKNOWN")) {
+                       sprintf(val, "0x%04x", pskey);
+                       str = NULL;
+               }
+
+               printf("// %s%s\n&%04x =", str ? "PSKEY_" : "",
+                                               str ? str : val, pskey);
+               for (i = 0; i < length; i++)
+                       printf(" %02x%02x", array[(i * 2) + 7], array[(i * 2) + 6]);
+               printf("\n");
+       }
+
+       if (reset)
+               transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);
+
+       return 0;
+}
+
+static int cmd_psload(int transport, int argc, char *argv[])
+{
+       uint8_t array[256];
+       uint16_t pskey, length, size, stores = CSR_STORES_PSRAM;
+       char *str, val[7];
+       int err, reset = 0;
+
+       OPT_PSKEY(1, 1, &stores, &reset, NULL);
+
+       psr_read(argv[0]);
+
+       memset(array, 0, sizeof(array));
+       size = sizeof(array) - 6;
+
+       while (psr_get(&pskey, array + 6, &size) == 0) {
+               str = csr_pskeytoval(pskey);
+               if (!strcasecmp(str, "UNKNOWN")) {
+                       sprintf(val, "0x%04x", pskey);
+                       str = NULL;
+               }
+
+               printf("Loading %s%s ... ", str ? "PSKEY_" : "",
+                                                       str ? str : val);
+               fflush(stdout);
+
+               length = size / 2;
+
+               array[0] = pskey & 0xff;
+               array[1] = pskey >> 8;
+               array[2] = length & 0xff;
+               array[3] = length >> 8;
+               array[4] = stores & 0xff;
+               array[5] = stores >> 8;
+
+               err = transport_write(transport, CSR_VARID_PS, array, size + 6);
+
+               printf("%s\n", err < 0 ? "failed" : "done");
+
+               memset(array, 0, sizeof(array));
+               size = sizeof(array) - 6;
+       }
+
+       if (reset)
+               transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);
+
+       return 0;
+}
+
+static int cmd_pscheck(int transport, int argc, char *argv[])
+{
+       uint8_t array[256];
+       uint16_t pskey, size;
+       int i;
+
+       OPT_HELP(1, NULL);
+
+       psr_read(argv[0]);
+
+       while (psr_get(&pskey, array, &size) == 0) {
+               printf("0x%04x =", pskey);
+               for (i = 0; i < size; i++)
+                       printf(" 0x%02x", array[i]);
+               printf("\n");
+       }
+
+       return 0;
+}
+
+static struct {
+       char *str;
+       int (*func)(int transport, int argc, char *argv[]);
+       char *arg;
+       char *doc;
+} commands[] = {
+       { "builddef",  cmd_builddef,  "",                    "Get build definitions"          },
+       { "keylen",    cmd_keylen,    "<handle>",            "Get current crypt key length"   },
+       { "clock",     cmd_clock,     "",                    "Get local Bluetooth clock"      },
+       { "rand",      cmd_rand,      "",                    "Get random number"              },
+       { "chiprev",   cmd_chiprev,   "",                    "Get chip revision"              },
+       { "buildname", cmd_buildname, "",                    "Get the full build name"        },
+       { "panicarg",  cmd_panicarg,  "",                    "Get panic code argument"        },
+       { "faultarg",  cmd_faultarg,  "",                    "Get fault code argument"        },
+       { "coldreset", cmd_coldreset, "",                    "Perform cold reset"             },
+       { "warmreset", cmd_warmreset, "",                    "Perform warm reset"             },
+       { "disabletx", cmd_disabletx, "",                    "Disable TX on the device"       },
+       { "enabletx",  cmd_enabletx,  "",                    "Enable TX on the device"        },
+       { "singlechan",cmd_singlechan,"<channel>",           "Lock radio on specific channel" },
+       { "hoppingon", cmd_hoppingon, "",                    "Revert to channel hopping"      },
+       { "rttxdata1", cmd_rttxdata1, "<freq> <level>",      "TXData1 radio test"             },
+       { "radiotest", cmd_radiotest, "<freq> <level> <id>", "Run radio tests"                },
+       { "memtypes",  cmd_memtypes,  NULL,                  "Get memory types"               },
+       { "psget",     cmd_psget,     "<key>",               "Get value for PS key"           },
+       { "psset",     cmd_psset,     "<key> <value>",       "Set value for PS key"           },
+       { "psclr",     cmd_psclr,     "<key>",               "Clear value for PS key"         },
+       { "pslist",    cmd_pslist,    NULL,                  "List all PS keys"               },
+       { "psread",    cmd_psread,    NULL,                  "Read all PS keys"               },
+       { "psload",    cmd_psload,    "<file>",              "Load all PS keys from PSR file" },
+       { "pscheck",   cmd_pscheck,   "<file>",              "Check PSR file"                 },
+       { NULL }
+};
+
+static void usage(void)
+{
+       int i, pos = 0;
+
+       printf("bccmd - Utility for the CSR BCCMD interface\n\n");
+       printf("Usage:\n"
+               "\tbccmd [options] <command>\n\n");
+
+       printf("Options:\n"
+               "\t-t <transport>     Select the transport\n"
+               "\t-d <device>        Select the device\n"
+               "\t-b <bcsp rate>     Select the bcsp transfer rate\n"
+               "\t-h, --help         Display help\n"
+               "\n");
+
+       printf("Transports:\n"
+               "\tHCI USB BCSP H4 3WIRE\n\n");
+
+       printf("Commands:\n");
+       for (i = 0; commands[i].str; i++)
+               printf("\t%-10s %-20s\t%s\n", commands[i].str,
+               commands[i].arg ? commands[i].arg : " ",
+               commands[i].doc);
+       printf("\n");
+
+       printf("Keys:\n\t");
+       for (i = 0; storage[i].pskey; i++) {
+               printf("%s ", storage[i].str);
+               pos += strlen(storage[i].str) + 1;
+               if (pos > 60) {
+                       printf("\n\t");
+                       pos = 0;
+               }
+       }
+       printf("\n");
+}
+
+static struct option main_options[] = {
+       { "transport",  1, 0, 't' },
+       { "device",     1, 0, 'd' },
+       { "bcsprate", 1, 0, 'b'},
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       char *device = NULL;
+       int i, err, opt, transport = CSR_TRANSPORT_HCI;
+       speed_t bcsp_rate = B38400;
+
+       while ((opt=getopt_long(argc, argv, "+t:d:i:b:h", main_options, NULL)) != EOF) {
+               switch (opt) {
+               case 't':
+                       if (!strcasecmp(optarg, "hci"))
+                               transport = CSR_TRANSPORT_HCI;
+                       else if (!strcasecmp(optarg, "usb"))
+                               transport = CSR_TRANSPORT_USB;
+                       else if (!strcasecmp(optarg, "bcsp"))
+                               transport = CSR_TRANSPORT_BCSP;
+                       else if (!strcasecmp(optarg, "h4"))
+                               transport = CSR_TRANSPORT_H4;
+                       else if (!strcasecmp(optarg, "h5"))
+                               transport = CSR_TRANSPORT_3WIRE;
+                       else if (!strcasecmp(optarg, "3wire"))
+                               transport = CSR_TRANSPORT_3WIRE;
+                       else if (!strcasecmp(optarg, "twutl"))
+                               transport = CSR_TRANSPORT_3WIRE;
+                       else
+                               transport = CSR_TRANSPORT_UNKNOWN;
+                       break;
+
+               case 'd':
+               case 'i':
+                       device = strdup(optarg);
+                       break;
+               case 'b':
+                       switch (atoi(optarg)) {
+                       case 9600: bcsp_rate = B9600; break;
+                       case 19200: bcsp_rate = B19200; break;
+                       case 38400: bcsp_rate = B38400; break;
+                       case 57600: bcsp_rate = B57600; break;
+                       case 115200: bcsp_rate = B115200; break;
+                       case 230400: bcsp_rate = B230400; break;
+                       case 460800: bcsp_rate = B460800; break;
+                       case 500000: bcsp_rate = B500000; break;
+                       case 576000: bcsp_rate = B576000; break;
+                       case 921600: bcsp_rate = B921600; break;
+                       case 1000000: bcsp_rate = B1000000; break;
+                       case 1152000: bcsp_rate = B1152000; break;
+                       case 1500000: bcsp_rate = B1500000; break;
+                       case 2000000: bcsp_rate = B2000000; break;
+#ifdef B2500000
+                       case 2500000: bcsp_rate = B2500000; break;
+#endif
+#ifdef B3000000
+                       case 3000000: bcsp_rate = B3000000; break;
+#endif
+#ifdef B3500000
+                       case 3500000: bcsp_rate = B3500000; break;
+#endif
+#ifdef B4000000
+                       case 4000000: bcsp_rate = B4000000; break;
+#endif
+                       default:
+                               printf("Unknown BCSP baud rate specified, defaulting to 38400bps\n");
+                               bcsp_rate = B38400;
+                       }
+                       break;
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       if (transport_open(transport, device, bcsp_rate) < 0)
+               exit(1);
+
+       if (device)
+               free(device);
+
+       for (i = 0; commands[i].str; i++) {
+               if (strcasecmp(commands[i].str, argv[0]))
+                       continue;
+
+               err = commands[i].func(transport, argc, argv);
+
+               transport_close(transport);
+
+               if (err < 0) {
+                       fprintf(stderr, "Can't execute command: %s (%d)\n",
+                                                       strerror(errno), errno);
+                       exit(1);
+               }
+
+               exit(0);
+       }
+
+       fprintf(stderr, "Unsupported command\n");
+
+       transport_close(transport);
+
+       exit(1);
+}
diff --git a/tools/ciptool.1 b/tools/ciptool.1
new file mode 100644 (file)
index 0000000..65d903d
--- /dev/null
@@ -0,0 +1,68 @@
+.\"
+.\"    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., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.TH CIPTOOL 1 "JUNE 6, 2003" "" ""
+
+.SH NAME
+ciptool \- Bluetooth Common ISDN Access Profile (CIP)
+.SH SYNOPSIS
+.BR "ciptool
+[
+.I options
+] <
+.I command
+>
+.SH DESCRIPTION
+.B ciptool
+is used to set up, maintain, and inspect the CIP configuration
+of the Bluetooth subsystem in the Linux kernel.
+.SH OPTIONS
+.TP
+.BI -h
+Gives a list of possible commands.
+.TP
+.BI -i " <hciX> | <bdaddr>"
+The command is applied to device
+.I
+hciX
+, which must be the name or the address of an installed Bluetooth
+device. If not specified, the command will be use the first
+available Bluetooth device.
+.SH COMMANDS
+.TP
+.BI show
+Display information about the connected devices.
+.TP
+.BI search
+Search for Bluetooth devices and connect to first one that
+offers CIP support.
+.TP
+.BI connect " <bdaddr> [psm]"
+Connect the local device to the remote Bluetooth device on the
+specified PSM number. If no PSM is specified, it will use the
+SDP to retrieve it from the remote device.
+.TP
+.BI release " [bdaddr]"
+Release a connection to the specific device. If no address is
+given and only one device is connected this will be released.
+.TP
+.BI loopback " <bdaddr> [psm]"
+Create a connection to the remote device for Bluetooth testing.
+This command will not provide a CAPI controller, because it is
+only for testing the CAPI Message Transport Protocol.
+.SH AUTHOR
+Written by Marcel Holtmann <marcel@holtmann.org>.
+.br
diff --git a/tools/ciptool.c b/tools/ciptool.c
new file mode 100644 (file)
index 0000000..edce9da
--- /dev/null
@@ -0,0 +1,498 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+#include <signal.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/cmtp.h>
+
+#ifdef NEED_PPOLL
+#include "ppoll.h"
+#endif
+
+static volatile sig_atomic_t __io_canceled = 0;
+
+static void sig_hup(int sig)
+{
+       return;
+}
+
+static void sig_term(int sig)
+{
+       __io_canceled = 1;
+}
+
+static char *cmtp_state[] = {
+       "unknown",
+       "connected",
+       "open",
+       "bound",
+       "listening",
+       "connecting",
+       "connecting",
+       "config",
+       "disconnecting",
+       "closed"
+};
+
+static char *cmtp_flagstostr(uint32_t flags)
+{
+       static char str[100] = "";
+
+       strcat(str, "[");
+
+       if (flags & (1 << CMTP_LOOPBACK))
+               strcat(str, "loopback");
+
+       strcat(str, "]");
+
+       return str;
+}
+
+static int get_psm(bdaddr_t *src, bdaddr_t *dst, unsigned short *psm)
+{
+       sdp_session_t *s;
+       sdp_list_t *srch, *attrs, *rsp;
+       uuid_t svclass;
+       uint16_t attr;
+       int err;
+
+       if (!(s = sdp_connect(src, dst, 0)))
+               return -1;
+
+       sdp_uuid16_create(&svclass, CIP_SVCLASS_ID);
+       srch = sdp_list_append(NULL, &svclass);
+
+       attr = SDP_ATTR_PROTO_DESC_LIST;
+       attrs = sdp_list_append(NULL, &attr);
+
+       err = sdp_service_search_attr_req(s, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp);
+
+       sdp_close(s);
+
+       if (err)
+               return 0;
+
+       for (; rsp; rsp = rsp->next) {
+               sdp_record_t *rec = (sdp_record_t *) rsp->data;
+               sdp_list_t *protos;
+
+               if (!sdp_get_access_protos(rec, &protos)) {
+                       unsigned short p = sdp_get_proto_port(protos, L2CAP_UUID);
+                       if (p > 0) {
+                               *psm = p;
+                               return 1;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static int do_connect(int ctl, int dev_id, bdaddr_t *src, bdaddr_t *dst, unsigned short psm, uint32_t flags)
+{
+       struct cmtp_connadd_req req;
+       struct hci_dev_info di;
+       struct sockaddr_l2 addr;
+       struct l2cap_options opts;
+       socklen_t size;
+       int sk;
+
+       hci_devinfo(dev_id, &di);
+       if (!(di.link_policy & HCI_LP_RSWITCH)) {
+               printf("Local device is not accepting role switch\n");
+       }
+
+       if ((sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) {
+               perror("Can't create L2CAP socket");
+               exit(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) {
+               perror("Can't bind L2CAP socket");
+               close(sk);
+               exit(1);
+       }
+
+       memset(&opts, 0, sizeof(opts));
+       size = sizeof(opts);
+
+       if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &size) < 0) {
+               perror("Can't get L2CAP options");
+               close(sk);
+               exit(1);
+       }
+
+       opts.imtu = CMTP_DEFAULT_MTU;
+       opts.omtu = CMTP_DEFAULT_MTU;
+       opts.flush_to = 0xffff;
+
+       if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) {
+               perror("Can't set L2CAP options");
+               close(sk);
+               exit(1);
+       }
+
+       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) {
+               perror("Can't connect L2CAP socket");
+               close(sk);
+               exit(1);
+       }
+
+       req.sock = sk;
+       req.flags = flags;
+
+       if (ioctl(ctl, CMTPCONNADD, &req) < 0) {
+               perror("Can't create connection");
+               exit(1);
+       }
+
+       return sk;
+}
+
+static void cmd_show(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       struct cmtp_connlist_req req;
+       struct cmtp_conninfo ci[16];
+       char addr[18];
+       unsigned int i;
+
+       req.cnum = 16;
+       req.ci   = ci;
+
+       if (ioctl(ctl, CMTPGETCONNLIST, &req) < 0) {
+               perror("Can't get connection list");
+               exit(1);
+       }
+
+       for (i = 0; i < req.cnum; i++) {
+               ba2str(&ci[i].bdaddr, addr);
+               printf("%d %s %s %s\n", ci[i].num, addr,
+                       cmtp_state[ci[i].state],
+                       ci[i].flags ? cmtp_flagstostr(ci[i].flags) : "");
+       }
+}
+
+static void cmd_search(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       inquiry_info *info = NULL;
+       bdaddr_t src, dst;
+       unsigned short psm;
+       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   = 0;
+
+       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] == 2) && ((class[0] / 4) == 5)) {
+                       bacpy(&dst, &(info+i)->bdaddr);
+                       ba2str(&dst, addr);
+
+                       printf("\tChecking service for %s\n", addr);
+                       if (!get_psm(&src, &dst, &psm))
+                               continue;
+
+                       bt_free(info);
+
+                       printf("\tConnecting to device %s\n", addr);
+                       do_connect(ctl, dev_id, &src, &dst, psm, 0);
+                       return;
+               }
+       }
+
+       bt_free(info);
+       fprintf(stderr, "\tNo devices in range or visible\n");
+       exit(1);
+}
+
+static void cmd_create(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       bdaddr_t src, dst;
+       unsigned short psm;
+       int dev_id;
+       char addr[18];
+
+       if (argc < 2)
+               return;
+
+       str2ba(argv[1], &dst);
+
+       ba2str(bdaddr, addr);
+       dev_id = hci_devid(addr);
+       if (dev_id < 0) {
+               dev_id = hci_get_route(&dst);
+               hci_devba(dev_id, &src);
+       } else
+               bacpy(&src, bdaddr);
+
+       if (argc < 3) {
+               if (!get_psm(&src, &dst, &psm))
+                       psm = 4099;
+       } else
+               psm = atoi(argv[2]);
+
+       do_connect(ctl, dev_id, &src, &dst, psm, 0);
+}
+
+static void cmd_release(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       struct cmtp_conndel_req req;
+       struct cmtp_connlist_req cl;
+       struct cmtp_conninfo ci[16];
+
+       if (argc < 2) {
+               cl.cnum = 16;
+               cl.ci   = ci;
+
+               if (ioctl(ctl, CMTPGETCONNLIST, &cl) < 0) {
+                       perror("Can't get connection list");
+                       exit(1);
+               }
+
+               if (cl.cnum == 0)
+                       return;
+
+               if (cl.cnum != 1) {
+                       fprintf(stderr, "You have to specifiy the device address.\n");
+                       exit(1);
+               }
+
+               bacpy(&req.bdaddr, &ci[0].bdaddr);
+       } else
+               str2ba(argv[1], &req.bdaddr);
+
+       if (ioctl(ctl, CMTPCONNDEL, &req) < 0) {
+               perror("Can't release connection");
+               exit(1);
+       }
+}
+
+static void cmd_loopback(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       struct cmtp_conndel_req req;
+       struct sigaction sa;
+       struct pollfd p;
+       sigset_t sigs;
+       bdaddr_t src, dst;
+       unsigned short psm;
+       int dev_id, sk;
+       char addr[18];
+
+       if (argc < 2)
+               return;
+
+       str2ba(argv[1], &dst);
+
+       ba2str(bdaddr, addr);
+       dev_id = hci_devid(addr);
+       if (dev_id < 0) {
+               dev_id = hci_get_route(&dst);
+               hci_devba(dev_id, &src);
+       } else
+               bacpy(&src, bdaddr);
+
+       ba2str(&dst, addr);
+       printf("Connecting to %s in loopback mode\n", addr);
+
+       if (argc < 3) {
+               if (!get_psm(&src, &dst, &psm))
+                       psm = 4099;
+       } else
+               psm = atoi(argv[2]);
+
+       sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK));
+
+       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 = POLLERR | POLLHUP;
+
+       while (!__io_canceled) {
+               p.revents = 0;
+               if (ppoll(&p, 1, NULL, &sigs) > 0)
+                       break;
+       }
+
+       bacpy(&req.bdaddr, &dst);
+       ioctl(ctl, CMTPCONNDEL, &req);
+}
+
+static struct {
+       char *cmd;
+       char *alt;
+       void (*func)(int ctl, bdaddr_t *bdaddr, int argc, char **argv);
+       char *opt;
+       char *doc;
+} command[] = {
+       { "show",     "list",       cmd_show,     0,          "Show remote connections"      },
+       { "search",   "scan",       cmd_search,   0,          "Search for a remote device"   },
+       { "connect",  "create",     cmd_create,   "<bdaddr>", "Connect a remote device"      },
+       { "release",  "disconnect", cmd_release,  "[bdaddr]", "Disconnect the remote device" },
+       { "loopback", "test",       cmd_loopback, "<bdaddr>", "Loopback test of a device"    },
+       { NULL, NULL, NULL, 0, 0 }
+};
+
+static void usage(void)
+{
+       int i;
+
+       printf("ciptool - Bluetooth Common ISDN Access Profile (CIP)\n\n");
+
+       printf("Usage:\n"
+               "\tciptool [options] [command]\n"
+               "\n");
+
+       printf("Options:\n"
+               "\t-i [hciX|bdaddr]   Local HCI device or BD Address\n"
+               "\t-h, --help         Display help\n"
+               "\n");
+
+       printf("Commands:\n");
+       for (i = 0; command[i].cmd; i++)
+               printf("\t%-8s %-10s\t%s\n", command[i].cmd,
+               command[i].opt ? command[i].opt : " ",
+               command[i].doc);
+       printf("\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "device",     1, 0, 'i' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       bdaddr_t bdaddr;
+       int i, opt, ctl;
+
+       bacpy(&bdaddr, BDADDR_ANY);
+
+       while ((opt = getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
+               switch(opt) {
+               case 'i':
+                       if (!strncmp(optarg, "hci", 3))
+                               hci_devba(atoi(optarg + 3), &bdaddr);
+                       else
+                               str2ba(optarg, &bdaddr);
+                       break;
+               case 'h':
+                       usage();
+                       exit(0);
+               default:
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               return 0;
+       }
+
+       if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_CMTP)) < 0 ) {
+               perror("Can't open CMTP control socket");
+               exit(1);
+       }
+
+       for (i = 0; command[i].cmd; i++) {
+               if (strncmp(command[i].cmd, argv[0], 4) && strncmp(command[i].alt, argv[0], 4))
+                       continue;
+               command[i].func(ctl, &bdaddr, argc, argv);
+               close(ctl);
+               exit(0);
+       }
+
+       usage();
+
+       close(ctl);
+
+       return 0;
+}
diff --git a/tools/csr.c b/tools/csr.c
new file mode 100644 (file)
index 0000000..b4ea1fb
--- /dev/null
@@ -0,0 +1,2853 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "csr.h"
+
+struct psr_data {
+       uint16_t pskey;
+       uint8_t *value;
+       uint8_t size;
+       struct psr_data *next;
+};
+
+static struct psr_data *head = NULL, *tail = NULL;
+
+static struct {
+       uint16_t id;
+       char *str;
+} csr_map[] = {
+       {   66, "HCI 9.8"       },
+       {   97, "HCI 10.3"      },
+       {  101, "HCI 10.5"      },
+       {  111, "HCI 11.0"      },
+       {  112, "HCI 11.1"      },
+       {  114, "HCI 11.2"      },
+       {  115, "HCI 11.3"      },
+       {  117, "HCI 12.0"      },
+       {  119, "HCI 12.1"      },
+       {  133, "HCI 12.2"      },
+       {  134, "HCI 12.3"      },
+       {  162, "HCI 12.4"      },
+       {  165, "HCI 12.5"      },
+       {  169, "HCI 12.6"      },
+       {  188, "HCI 12.7"      },
+       {  218, "HCI 12.8"      },
+       {  283, "HCI 12.9"      },
+       {  203, "HCI 13.2"      },
+       {  204, "HCI 13.2"      },
+       {  210, "HCI 13.3"      },
+       {  211, "HCI 13.3"      },
+       {  213, "HCI 13.4"      },
+       {  214, "HCI 13.4"      },
+       {  225, "HCI 13.5"      },
+       {  226, "HCI 13.5"      },
+       {  237, "HCI 13.6"      },
+       {  238, "HCI 13.6"      },
+       {  242, "HCI 14.0"      },
+       {  243, "HCI 14.0"      },
+       {  244, "HCI 14.0"      },
+       {  245, "HCI 14.0"      },
+       {  254, "HCI 13.7"      },
+       {  255, "HCI 13.7"      },
+       {  264, "HCI 14.1"      },
+       {  265, "HCI 14.1"      },
+       {  267, "HCI 14.2"      },
+       {  268, "HCI 14.2"      },
+       {  272, "HCI 14.3"      },
+       {  273, "HCI 14.3"      },
+       {  274, "HCI 13.8"      },
+       {  275, "HCI 13.8"      },
+       {  286, "HCI 13.9"      },
+       {  287, "HCI 13.9"      },
+       {  309, "HCI 13.10"     },
+       {  310, "HCI 13.10"     },
+       {  313, "HCI 14.4"      },
+       {  314, "HCI 14.4"      },
+       {  323, "HCI 14.5"      },
+       {  324, "HCI 14.5"      },
+       {  336, "HCI 14.6"      },
+       {  337, "HCI 14.6"      },
+       {  351, "HCI 13.11"     },
+       {  352, "HCI 13.11"     },
+       {  362, "HCI 15.0"      },
+       {  363, "HCI 15.0"      },
+       {  364, "HCI 15.0"      },
+       {  365, "HCI 15.0"      },
+       {  373, "HCI 14.7"      },
+       {  374, "HCI 14.7"      },
+       {  379, "HCI 15.1"      },
+       {  380, "HCI 15.1"      },
+       {  381, "HCI 15.1"      },
+       {  382, "HCI 15.1"      },
+       {  392, "HCI 15.2"      },
+       {  393, "HCI 15.2"      },
+       {  394, "HCI 15.2"      },
+       {  395, "HCI 15.2"      },
+       {  436, "HCI 16.0"      },
+       {  437, "HCI 16.0"      },
+       {  438, "HCI 16.0"      },
+       {  439, "HCI 16.0"      },
+       {  443, "HCI 15.3"      },
+       {  444, "HCI 15.3"      },
+       {  465, "HCI 16.1"      },
+       {  466, "HCI 16.1"      },
+       {  467, "HCI 16.1"      },
+       {  468, "HCI 16.1"      },
+       {  487, "HCI 14.8"      },
+       {  488, "HCI 14.8"      },
+       {  492, "HCI 16.2"      },
+       {  493, "HCI 16.2"      },
+       {  495, "HCI 16.2"      },
+       {  496, "HCI 16.2"      },
+       {  502, "HCI 16.1.1"    },
+       {  503, "HCI 16.1.1"    },
+       {  504, "HCI 16.1.1"    },
+       {  505, "HCI 16.1.1"    },
+       {  506, "HCI 16.1.2"    },
+       {  507, "HCI 16.1.2"    },
+       {  508, "HCI 16.1.2"    },
+       {  509, "HCI 16.1.2"    },
+       {  516, "HCI 16.3"      },
+       {  517, "HCI 16.3"      },
+       {  518, "HCI 16.3"      },
+       {  519, "HCI 16.3"      },
+       {  523, "HCI 16.4"      },
+       {  524, "HCI 16.4"      },
+       {  525, "HCI 16.4"      },
+       {  526, "HCI 16.4"      },
+       {  553, "HCI 15.3"      },
+       {  554, "HCI 15.3"      },
+       {  562, "HCI 16.5"      },
+       {  563, "HCI 16.5"      },
+       {  564, "HCI 16.5"      },
+       {  565, "HCI 16.5"      },
+       {  593, "HCI 17.0"      },
+       {  594, "HCI 17.0"      },
+       {  595, "HCI 17.0"      },
+       {  599, "HCI 17.0"      },
+       {  600, "HCI 17.0"      },
+       {  608, "HCI 13.10.1"   },
+       {  609, "HCI 13.10.1"   },
+       {  613, "HCI 17.1"      },
+       {  614, "HCI 17.1"      },
+       {  615, "HCI 17.1"      },
+       {  616, "HCI 17.1"      },
+       {  618, "HCI 17.1"      },
+       {  624, "HCI 17.2"      },
+       {  625, "HCI 17.2"      },
+       {  626, "HCI 17.2"      },
+       {  627, "HCI 17.2"      },
+       {  637, "HCI 16.6"      },
+       {  638, "HCI 16.6"      },
+       {  639, "HCI 16.6"      },
+       {  640, "HCI 16.6"      },
+       {  642, "HCI 13.10.2"   },
+       {  643, "HCI 13.10.2"   },
+       {  644, "HCI 13.10.3"   },
+       {  645, "HCI 13.10.3"   },
+       {  668, "HCI 13.10.4"   },
+       {  669, "HCI 13.10.4"   },
+       {  681, "HCI 16.7"      },
+       {  682, "HCI 16.7"      },
+       {  683, "HCI 16.7"      },
+       {  684, "HCI 16.7"      },
+       {  704, "HCI 16.8"      },
+       {  718, "HCI 16.4.1"    },
+       {  719, "HCI 16.4.1"    },
+       {  720, "HCI 16.4.1"    },
+       {  721, "HCI 16.4.1"    },
+       {  722, "HCI 16.7.1"    },
+       {  723, "HCI 16.7.1"    },
+       {  724, "HCI 16.7.1"    },
+       {  725, "HCI 16.7.1"    },
+       {  731, "HCI 16.7.2"    },
+       {  732, "HCI 16.7.2"    },
+       {  733, "HCI 16.7.2"    },
+       {  734, "HCI 16.7.2"    },
+       {  735, "HCI 16.4.2"    },
+       {  736, "HCI 16.4.2"    },
+       {  737, "HCI 16.4.2"    },
+       {  738, "HCI 16.4.2"    },
+       {  750, "HCI 16.7.3"    },
+       {  751, "HCI 16.7.3"    },
+       {  752, "HCI 16.7.3"    },
+       {  753, "HCI 16.7.3"    },
+       {  760, "HCI 16.7.4"    },
+       {  761, "HCI 16.7.4"    },
+       {  762, "HCI 16.7.4"    },
+       {  763, "HCI 16.7.4"    },
+       {  770, "HCI 16.9"      },
+       {  771, "HCI 16.9"      },
+       {  772, "HCI 16.9"      },
+       {  773, "HCI 16.9"      },
+       {  774, "HCI 17.3"      },
+       {  775, "HCI 17.3"      },
+       {  776, "HCI 17.3"      },
+       {  777, "HCI 17.3"      },
+       {  781, "HCI 16.7.5"    },
+       {  786, "HCI 16.10"     },
+       {  787, "HCI 16.10"     },
+       {  788, "HCI 16.10"     },
+       {  789, "HCI 16.10"     },
+       {  791, "HCI 16.4.3"    },
+       {  792, "HCI 16.4.3"    },
+       {  793, "HCI 16.4.3"    },
+       {  794, "HCI 16.4.3"    },
+       {  798, "HCI 16.11"     },
+       {  799, "HCI 16.11"     },
+       {  800, "HCI 16.11"     },
+       {  801, "HCI 16.11"     },
+       {  806, "HCI 16.7.5"    },
+       {  807, "HCI 16.12"     },
+       {  808, "HCI 16.12"     },
+       {  809, "HCI 16.12"     },
+       {  810, "HCI 16.12"     },
+       {  817, "HCI 16.13"     },
+       {  818, "HCI 16.13"     },
+       {  819, "HCI 16.13"     },
+       {  820, "HCI 16.13"     },
+       {  823, "HCI 13.10.5"   },
+       {  824, "HCI 13.10.5"   },
+       {  826, "HCI 16.14"     },
+       {  827, "HCI 16.14"     },
+       {  828, "HCI 16.14"     },
+       {  829, "HCI 16.14"     },
+       {  843, "HCI 17.3.1"    },
+       {  856, "HCI 17.3.2"    },
+       {  857, "HCI 17.3.2"    },
+       {  858, "HCI 17.3.2"    },
+       { 1120, "HCI 17.11"     },
+       { 1168, "HCI 18.1"      },
+       { 1169, "HCI 18.1"      },
+       { 1241, "HCI 18.x"      },
+       { 1298, "HCI 18.2"      },
+       { 1354, "HCI 18.2"      },
+       { 1392, "HCI 18.2"      },
+       { 1393, "HCI 18.2"      },
+       { 1501, "HCI 18.2"      },
+       { 1503, "HCI 18.2"      },
+       { 1504, "HCI 18.2"      },
+       { 1505, "HCI 18.2"      },
+       { 1506, "HCI 18.2"      },
+       { 1520, "HCI 18.2"      },
+       { 1586, "HCI 18.2"      },
+       { 1591, "HCI 18.2"      },
+       { 1592, "HCI 18.2"      },
+       { 1593, "HCI 18.2.1"    },
+       { 1733, "HCI 18.3"      },
+       { 1734, "HCI 18.3"      },
+       { 1735, "HCI 18.3"      },
+       { 1737, "HCI 18.3"      },
+       { 1915, "HCI 19.2"      },
+       { 1916, "HCI 19.2"      },
+       { 1958, "HCI 19.2"      },
+       { 1981, "Unified 20a"   },
+       { 1982, "Unified 20a"   },
+       { 1989, "HCI 18.4"      },
+       { 2062, "Unified 20a1"  },
+       { 2063, "Unified 20a1"  },
+       { 2067, "Unified 18f"   },
+       { 2068, "Unified 18f"   },
+       { 2243, "Unified 18e"   },
+       { 2244, "Unified 18e"   },
+       { 2258, "Unified 20d"   },
+       { 2259, "Unified 20d"   },
+       { 2361, "Unified 20e"   },
+       { 2362, "Unified 20e"   },
+       { 2386, "Unified 21a"   },
+       { 2387, "Unified 21a"   },
+       { 2423, "Unified 21a"   },
+       { 2424, "Unified 21a"   },
+       { 2623, "Unified 21c"   },
+       { 2624, "Unified 21c"   },
+       { 2625, "Unified 21c"   },
+       { 2626, "Unified 21c"   },
+       { 2627, "Unified 21c"   },
+       { 2628, "Unified 21c"   },
+       { 2629, "Unified 21c"   },
+       { 2630, "Unified 21c"   },
+       { 2631, "Unified 21c"   },
+       { 2632, "Unified 21c"   },
+       { 2633, "Unified 21c"   },
+       { 2634, "Unified 21c"   },
+       { 2635, "Unified 21c"   },
+       { 2636, "Unified 21c"   },
+       { 2649, "Unified 21c"   },
+       { 2650, "Unified 21c"   },
+       { 2651, "Unified 21c"   },
+       { 2652, "Unified 21c"   },
+       { 2653, "Unified 21c"   },
+       { 2654, "Unified 21c"   },
+       { 2655, "Unified 21c"   },
+       { 2656, "Unified 21c"   },
+       { 2658, "Unified 21c"   },
+       { 3057, "Unified 21d"   },
+       { 3058, "Unified 21d"   },
+       { 3059, "Unified 21d"   },
+       { 3060, "Unified 21d"   },
+       { 3062, "Unified 21d"   },
+       { 3063, "Unified 21d"   },
+       { 3064, "Unified 21d"   },
+       { 3164, "Unified 21e"   },
+       { 3413, "Unified 21f"   },
+       { 3414, "Unified 21f"   },
+       { 3415, "Unified 21f"   },
+       { 3424, "Unified 21f"   },
+       { 3454, "Unified 21f"   },
+       { 3684, "Unified 21f"   },
+       { 3764, "Unified 21f"   },
+       { 4276, "Unified 22b"   },
+       { 4277, "Unified 22b"   },
+       { 4279, "Unified 22b"   },
+       { 4281, "Unified 22b"   },
+       { 4282, "Unified 22b"   },
+       { 4283, "Unified 22b"   },
+       { 4284, "Unified 22b"   },
+       { 4285, "Unified 22b"   },
+       { 4289, "Unified 22b"   },
+       { 4290, "Unified 22b"   },
+       { 4291, "Unified 22b"   },
+       { 4292, "Unified 22b"   },
+       { 4293, "Unified 22b"   },
+       { 4294, "Unified 22b"   },
+       { 4295, "Unified 22b"   },
+       { 4363, "Unified 22c"   },
+       { 4373, "Unified 22c"   },
+       { 4374, "Unified 22c"   },
+       { 4532, "Unified 22d"   },
+       { 4533, "Unified 22d"   },
+       { 4698, "Unified 23c"   },
+       { 4839, "Unified 23c"   },
+       { 4841, "Unified 23c"   },
+       { 4866, "Unified 23c"   },
+       { 4867, "Unified 23c"   },
+       { 4868, "Unified 23c"   },
+       { 4869, "Unified 23c"   },
+       { 4870, "Unified 23c"   },
+       { 4871, "Unified 23c"   },
+       { 4872, "Unified 23c"   },
+       { 4874, "Unified 23c"   },
+       { 4875, "Unified 23c"   },
+       { 4876, "Unified 23c"   },
+       { 4877, "Unified 23c"   },
+       { 2526, "Marcel 1 (2005-09-26)" },
+       { 2543, "Marcel 2 (2005-09-28)" },
+       { 2622, "Marcel 3 (2005-10-27)" },
+       { 3326, "Marcel 4 (2006-06-16)" },
+       { 3612, "Marcel 5 (2006-10-24)" },
+       { 4509, "Marcel 6 (2007-06-11)" },
+       { 5417, "Marcel 7 (2008-08-26)" },
+       {  195, "Sniff 1 (2001-11-27)"  },
+       {  220, "Sniff 2 (2002-01-03)"  },
+       {  269, "Sniff 3 (2002-02-22)"  },
+       {  270, "Sniff 4 (2002-02-26)"  },
+       {  284, "Sniff 5 (2002-03-12)"  },
+       {  292, "Sniff 6 (2002-03-20)"  },
+       {  305, "Sniff 7 (2002-04-12)"  },
+       {  306, "Sniff 8 (2002-04-12)"  },
+       {  343, "Sniff 9 (2002-05-02)"  },
+       {  346, "Sniff 10 (2002-05-03)" },
+       {  355, "Sniff 11 (2002-05-16)" },
+       {  256, "Sniff 11 (2002-05-16)" },
+       {  390, "Sniff 12 (2002-06-26)" },
+       {  450, "Sniff 13 (2002-08-16)" },
+       {  451, "Sniff 13 (2002-08-16)" },
+       {  533, "Sniff 14 (2002-10-11)" },
+       {  580, "Sniff 15 (2002-11-14)" },
+       {  623, "Sniff 16 (2002-12-12)" },
+       {  678, "Sniff 17 (2003-01-29)" },
+       {  847, "Sniff 18 (2003-04-17)" },
+       {  876, "Sniff 19 (2003-06-10)" },
+       {  997, "Sniff 22 (2003-09-05)" },
+       { 1027, "Sniff 23 (2003-10-03)" },
+       { 1029, "Sniff 24 (2003-10-03)" },
+       { 1112, "Sniff 25 (2003-12-03)" },
+       { 1113, "Sniff 25 (2003-12-03)" },
+       { 1133, "Sniff 26 (2003-12-18)" },
+       { 1134, "Sniff 26 (2003-12-18)" },
+       { 1223, "Sniff 27 (2004-03-08)" },
+       { 1224, "Sniff 27 (2004-03-08)" },
+       { 1319, "Sniff 31 (2004-04-22)" },
+       { 1320, "Sniff 31 (2004-04-22)" },
+       { 1427, "Sniff 34 (2004-06-16)" },
+       { 1508, "Sniff 35 (2004-07-19)" },
+       { 1509, "Sniff 35 (2004-07-19)" },
+       { 1587, "Sniff 36 (2004-08-18)" },
+       { 1588, "Sniff 36 (2004-08-18)" },
+       { 1641, "Sniff 37 (2004-09-16)" },
+       { 1642, "Sniff 37 (2004-09-16)" },
+       { 1699, "Sniff 38 (2004-10-07)" },
+       { 1700, "Sniff 38 (2004-10-07)" },
+       { 1752, "Sniff 39 (2004-11-02)" },
+       { 1753, "Sniff 39 (2004-11-02)" },
+       { 1759, "Sniff 40 (2004-11-03)" },
+       { 1760, "Sniff 40 (2004-11-03)" },
+       { 1761, "Sniff 40 (2004-11-03)" },
+       { 2009, "Sniff 41 (2005-04-06)" },
+       { 2010, "Sniff 41 (2005-04-06)" },
+       { 2011, "Sniff 41 (2005-04-06)" },
+       { 2016, "Sniff 42 (2005-04-11)" },
+       { 2017, "Sniff 42 (2005-04-11)" },
+       { 2018, "Sniff 42 (2005-04-11)" },
+       { 2023, "Sniff 43 (2005-04-14)" },
+       { 2024, "Sniff 43 (2005-04-14)" },
+       { 2025, "Sniff 43 (2005-04-14)" },
+       { 2032, "Sniff 44 (2005-04-18)" },
+       { 2033, "Sniff 44 (2005-04-18)" },
+       { 2034, "Sniff 44 (2005-04-18)" },
+       { 2288, "Sniff 45 (2005-07-08)" },
+       { 2289, "Sniff 45 (2005-07-08)" },
+       { 2290, "Sniff 45 (2005-07-08)" },
+       { 2388, "Sniff 46 (2005-08-17)" },
+       { 2389, "Sniff 46 (2005-08-17)" },
+       { 2390, "Sniff 46 (2005-08-17)" },
+       { 2869, "Sniff 47 (2006-02-15)" },
+       { 2870, "Sniff 47 (2006-02-15)" },
+       { 2871, "Sniff 47 (2006-02-15)" },
+       { 3214, "Sniff 48 (2006-05-16)" },
+       { 3215, "Sniff 48 (2006-05-16)" },
+       { 3216, "Sniff 48 (2006-05-16)" },
+       { 3356, "Sniff 49 (2006-07-17)" },
+       { 3529, "Sniff 50 (2006-09-21)" },
+       { 3546, "Sniff 51 (2006-09-29)" },
+       { 3683, "Sniff 52 (2006-11-03)" },
+       {    0, }
+};
+
+char *csr_builddeftostr(uint16_t def)
+{
+       switch (def) {
+       case 0x0000:
+               return "NONE";
+       case 0x0001:
+               return "CHIP_BASE_BC01";
+       case 0x0002:
+               return "CHIP_BASE_BC02";
+       case 0x0003:
+               return "CHIP_BC01B";
+       case 0x0004:
+               return "CHIP_BC02_EXTERNAL";
+       case 0x0005:
+               return "BUILD_HCI";
+       case 0x0006:
+               return "BUILD_RFCOMM";
+       case 0x0007:
+               return "BT_VER_1_1";
+       case 0x0008:
+               return "TRANSPORT_ALL";
+       case 0x0009:
+               return "TRANSPORT_BCSP";
+       case 0x000a:
+               return "TRANSPORT_H4";
+       case 0x000b:
+               return "TRANSPORT_USB";
+       case 0x000c:
+               return "MAX_CRYPT_KEY_LEN_56";
+       case 0x000d:
+               return "MAX_CRYPT_KEY_LEN_128";
+       case 0x000e:
+               return "TRANSPORT_USER";
+       case 0x000f:
+               return "CHIP_BC02_KATO";
+       case 0x0010:
+               return "TRANSPORT_NONE";
+       case 0x0012:
+               return "REQUIRE_8MBIT";
+       case 0x0013:
+               return "RADIOTEST";
+       case 0x0014:
+               return "RADIOTEST_LITE";
+       case 0x0015:
+               return "INSTALL_FLASH";
+       case 0x0016:
+               return "INSTALL_EEPROM";
+       case 0x0017:
+               return "INSTALL_COMBO_DOT11";
+       case 0x0018:
+               return "LOWPOWER_TX";
+       case 0x0019:
+               return "TRANSPORT_TWUTL";
+       case 0x001a:
+               return "COMPILER_GCC";
+       case 0x001b:
+               return "CHIP_BC02_CLOUSEAU";
+       case 0x001c:
+               return "CHIP_BC02_TOULOUSE";
+       case 0x001d:
+               return "CHIP_BASE_BC3";
+       case 0x001e:
+               return "CHIP_BC3_NICKNACK";
+       case 0x001f:
+               return "CHIP_BC3_KALIMBA";
+       case 0x0020:
+               return "INSTALL_HCI_MODULE";
+       case 0x0021:
+               return "INSTALL_L2CAP_MODULE";
+       case 0x0022:
+               return "INSTALL_DM_MODULE";
+       case 0x0023:
+               return "INSTALL_SDP_MODULE";
+       case 0x0024:
+               return "INSTALL_RFCOMM_MODULE";
+       case 0x0025:
+               return "INSTALL_HIDIO_MODULE";
+       case 0x0026:
+               return "INSTALL_PAN_MODULE";
+       case 0x0027:
+               return "INSTALL_IPV4_MODULE";
+       case 0x0028:
+               return "INSTALL_IPV6_MODULE";
+       case 0x0029:
+               return "INSTALL_TCP_MODULE";
+       case 0x002a:
+               return "BT_VER_1_2";
+       case 0x002b:
+               return "INSTALL_UDP_MODULE";
+       case 0x002c:
+               return "REQUIRE_0_WAIT_STATES";
+       case 0x002d:
+               return "CHIP_BC3_PADDYWACK";
+       case 0x002e:
+               return "CHIP_BC4_COYOTE";
+       case 0x002f:
+               return "CHIP_BC4_ODDJOB";
+       case 0x0030:
+               return "TRANSPORT_H4DS";
+       case 0x0031:
+               return "CHIP_BASE_BC4";
+       default:
+               return "UNKNOWN";
+       }
+}
+
+char *csr_buildidtostr(uint16_t id)
+{
+       static char str[12];
+       int i;
+
+       for (i = 0; csr_map[i].id; i++)
+               if (csr_map[i].id == id)
+                       return csr_map[i].str;
+
+       snprintf(str, 11, "Build %d", id);
+       return str;
+}
+
+char *csr_chipvertostr(uint16_t ver, uint16_t rev)
+{
+       switch (ver) {
+       case 0x00:
+               return "BlueCore01a";
+       case 0x01:
+               switch (rev) {
+               case 0x64:
+                       return "BlueCore01b (ES)";
+               case 0x65:
+               default:
+                       return "BlueCore01b";
+               }
+       case 0x02:
+               switch (rev) {
+               case 0x89:
+                       return "BlueCore02-External (ES2)";
+               case 0x8a:
+                       return "BlueCore02-External";
+               case 0x28:
+                       return "BlueCore02-ROM/Audio/Flash";
+               default:
+                       return "BlueCore02";
+               }
+       case 0x03:
+               switch (rev) {
+               case 0x43:
+                       return "BlueCore3-MM";
+               case 0x15:
+                       return "BlueCore3-ROM";
+               case 0xe2:
+                       return "BlueCore3-Flash";
+               case 0x26:
+                       return "BlueCore4-External";
+               case 0x30:
+                       return "BlueCore4-ROM";
+               default:
+                       return "BlueCore3 or BlueCore4";
+               }
+       default:
+               return "Unknown";
+       }
+}
+
+char *csr_pskeytostr(uint16_t pskey)
+{
+       switch (pskey) {
+       case CSR_PSKEY_BDADDR:
+               return "Bluetooth address";
+       case CSR_PSKEY_COUNTRYCODE:
+               return "Country code";
+       case CSR_PSKEY_CLASSOFDEVICE:
+               return "Class of device";
+       case CSR_PSKEY_DEVICE_DRIFT:
+               return "Device drift";
+       case CSR_PSKEY_DEVICE_JITTER:
+               return "Device jitter";
+       case CSR_PSKEY_MAX_ACLS:
+               return "Maximum ACL links";
+       case CSR_PSKEY_MAX_SCOS:
+               return "Maximum SCO links";
+       case CSR_PSKEY_MAX_REMOTE_MASTERS:
+               return "Maximum remote masters";
+       case CSR_PSKEY_ENABLE_MASTERY_WITH_SLAVERY:
+               return "Support master and slave roles simultaneously";
+       case CSR_PSKEY_H_HC_FC_MAX_ACL_PKT_LEN:
+               return "Maximum HCI ACL packet length";
+       case CSR_PSKEY_H_HC_FC_MAX_SCO_PKT_LEN:
+               return "Maximum HCI SCO packet length";
+       case CSR_PSKEY_H_HC_FC_MAX_ACL_PKTS:
+               return "Maximum number of HCI ACL packets";
+       case CSR_PSKEY_H_HC_FC_MAX_SCO_PKTS:
+               return "Maximum number of HCI SCO packets";
+       case CSR_PSKEY_LC_FC_BUFFER_LOW_WATER_MARK:
+               return "Flow control low water mark";
+       case CSR_PSKEY_LC_MAX_TX_POWER:
+               return "Maximum transmit power";
+       case CSR_PSKEY_TX_GAIN_RAMP:
+               return "Transmit gain ramp rate";
+       case CSR_PSKEY_LC_POWER_TABLE:
+               return "Radio power table";
+       case CSR_PSKEY_LC_PEER_POWER_PERIOD:
+               return "Peer transmit power control interval";
+       case CSR_PSKEY_LC_FC_POOLS_LOW_WATER_MARK:
+               return "Flow control pool low water mark";
+       case CSR_PSKEY_LC_DEFAULT_TX_POWER:
+               return "Default transmit power";
+       case CSR_PSKEY_LC_RSSI_GOLDEN_RANGE:
+               return "RSSI at bottom of golden receive range";
+       case CSR_PSKEY_LC_COMBO_DISABLE_PIO_MASK:
+               return "Combo: PIO lines and logic to disable transmit";
+       case CSR_PSKEY_LC_COMBO_PRIORITY_PIO_MASK:
+               return "Combo: priority activity PIO lines and logic";
+       case CSR_PSKEY_LC_COMBO_DOT11_CHANNEL_PIO_BASE:
+               return "Combo: 802.11b channel number base PIO line";
+       case CSR_PSKEY_LC_COMBO_DOT11_BLOCK_CHANNELS:
+               return "Combo: channels to block either side of 802.11b";
+       case CSR_PSKEY_LC_MAX_TX_POWER_NO_RSSI:
+               return "Maximum transmit power when peer has no RSSI";
+       case CSR_PSKEY_LC_CONNECTION_RX_WINDOW:
+               return "Receive window size during connections";
+       case CSR_PSKEY_LC_COMBO_DOT11_TX_PROTECTION_MODE:
+               return "Combo: which TX packets shall we protect";
+       case CSR_PSKEY_LC_ENHANCED_POWER_TABLE:
+               return "Radio power table";
+       case CSR_PSKEY_LC_WIDEBAND_RSSI_CONFIG:
+               return "RSSI configuration for use with wideband RSSI";
+       case CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_LEAD:
+               return "Combo: How much notice will we give the Combo Card";
+       case CSR_PSKEY_BT_CLOCK_INIT:
+               return "Initial value of Bluetooth clock";
+       case CSR_PSKEY_TX_MR_MOD_DELAY:
+               return "TX Mod delay";
+       case CSR_PSKEY_RX_MR_SYNC_TIMING:
+               return "RX MR Sync Timing";
+       case CSR_PSKEY_RX_MR_SYNC_CONFIG:
+               return "RX MR Sync Configuration";
+       case CSR_PSKEY_LC_LOST_SYNC_SLOTS:
+               return "Time in ms for lost sync in low power modes";
+       case CSR_PSKEY_RX_MR_SAMP_CONFIG:
+               return "RX MR Sync Configuration";
+       case CSR_PSKEY_AGC_HYST_LEVELS:
+               return "AGC hysteresis levels";
+       case CSR_PSKEY_RX_LEVEL_LOW_SIGNAL:
+               return "ANA_RX_LVL at low signal strengths";
+       case CSR_PSKEY_AGC_IQ_LVL_VALUES:
+               return "ANA_IQ_LVL values for AGC algorithmn";
+       case CSR_PSKEY_MR_FTRIM_OFFSET_12DB:
+               return "ANA_RX_FTRIM offset when using 12 dB IF atten ";
+       case CSR_PSKEY_MR_FTRIM_OFFSET_6DB:
+               return "ANA_RX_FTRIM offset when using 6 dB IF atten ";
+       case CSR_PSKEY_NO_CAL_ON_BOOT:
+               return "Do not calibrate radio on boot";
+       case CSR_PSKEY_RSSI_HI_TARGET:
+               return "RSSI high target";
+       case CSR_PSKEY_PREFERRED_MIN_ATTENUATION:
+               return "Preferred minimum attenuator setting";
+       case CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_OVERRIDE:
+               return "Combo: Treat all packets as high priority";
+       case CSR_PSKEY_LC_MULTISLOT_HOLDOFF:
+               return "Time till single slot packets are used for resync";
+       case CSR_PSKEY_FREE_KEY_PIGEON_HOLE:
+               return "Link key store bitfield";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR0:
+               return "Bluetooth address + link key 0";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR1:
+               return "Bluetooth address + link key 1";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR2:
+               return "Bluetooth address + link key 2";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR3:
+               return "Bluetooth address + link key 3";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR4:
+               return "Bluetooth address + link key 4";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR5:
+               return "Bluetooth address + link key 5";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR6:
+               return "Bluetooth address + link key 6";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR7:
+               return "Bluetooth address + link key 7";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR8:
+               return "Bluetooth address + link key 8";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR9:
+               return "Bluetooth address + link key 9";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR10:
+               return "Bluetooth address + link key 10";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR11:
+               return "Bluetooth address + link key 11";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR12:
+               return "Bluetooth address + link key 12";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR13:
+               return "Bluetooth address + link key 13";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR14:
+               return "Bluetooth address + link key 14";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR15:
+               return "Bluetooth address + link key 15";
+       case CSR_PSKEY_ENC_KEY_LMIN:
+               return "Minimum encryption key length";
+       case CSR_PSKEY_ENC_KEY_LMAX:
+               return "Maximum encryption key length";
+       case CSR_PSKEY_LOCAL_SUPPORTED_FEATURES:
+               return "Local supported features block";
+       case CSR_PSKEY_LM_USE_UNIT_KEY:
+               return "Allow use of unit key for authentication?";
+       case CSR_PSKEY_HCI_NOP_DISABLE:
+               return "Disable the HCI Command_Status event on boot";
+       case CSR_PSKEY_LM_MAX_EVENT_FILTERS:
+               return "Maximum number of event filters";
+       case CSR_PSKEY_LM_USE_ENC_MODE_BROADCAST:
+               return "Allow LM to use enc_mode=2";
+       case CSR_PSKEY_LM_TEST_SEND_ACCEPTED_TWICE:
+               return "LM sends two LMP_accepted messages in test mode";
+       case CSR_PSKEY_LM_MAX_PAGE_HOLD_TIME:
+               return "Maximum time we hold a device around page";
+       case CSR_PSKEY_AFH_ADAPTATION_RESPONSE_TIME:
+               return "LM period for AFH adaption";
+       case CSR_PSKEY_AFH_OPTIONS:
+               return "Options to configure AFH";
+       case CSR_PSKEY_AFH_RSSI_RUN_PERIOD:
+               return "AFH RSSI reading period";
+       case CSR_PSKEY_AFH_REENABLE_CHANNEL_TIME:
+               return "AFH good channel adding time";
+       case CSR_PSKEY_NO_DROP_ON_ACR_MS_FAIL:
+               return "Complete link if acr barge-in role switch refused";
+       case CSR_PSKEY_MAX_PRIVATE_KEYS:
+               return "Max private link keys stored";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR0:
+               return "Bluetooth address + link key 0";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR1:
+               return "Bluetooth address + link key 1";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR2:
+               return "Bluetooth address + link key 2";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR3:
+               return "Bluetooth address + link key 3";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR4:
+               return "Bluetooth address + link key 4";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR5:
+               return "Bluetooth address + link key 5";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR6:
+               return "Bluetooth address + link key 6";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR7:
+               return "Bluetooth address + link key 7";
+       case CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS:
+               return "Local supported commands";
+       case CSR_PSKEY_LM_MAX_ABSENCE_INDEX:
+               return "Maximum absence index allowed";
+       case CSR_PSKEY_DEVICE_NAME:
+               return "Local device's \"user friendly\" name";
+       case CSR_PSKEY_AFH_RSSI_THRESHOLD:
+               return "AFH RSSI threshold";
+       case CSR_PSKEY_LM_CASUAL_SCAN_INTERVAL:
+               return "Scan interval in slots for casual scanning";
+       case CSR_PSKEY_AFH_MIN_MAP_CHANGE:
+               return "The minimum amount to change an AFH map by";
+       case CSR_PSKEY_AFH_RSSI_LP_RUN_PERIOD:
+               return "AFH RSSI reading period when in low power mode";
+       case CSR_PSKEY_HCI_LMP_LOCAL_VERSION:
+               return "The HCI and LMP version reported locally";
+       case CSR_PSKEY_LMP_REMOTE_VERSION:
+               return "The LMP version reported remotely";
+       case CSR_PSKEY_HOLD_ERROR_MESSAGE_NUMBER:
+               return "Maximum number of queued HCI Hardware Error Events";
+       case CSR_PSKEY_DFU_ATTRIBUTES:
+               return "DFU attributes";
+       case CSR_PSKEY_DFU_DETACH_TO:
+               return "DFU detach timeout";
+       case CSR_PSKEY_DFU_TRANSFER_SIZE:
+               return "DFU transfer size";
+       case CSR_PSKEY_DFU_ENABLE:
+               return "DFU enable";
+       case CSR_PSKEY_DFU_LIN_REG_ENABLE:
+               return "Linear Regulator enabled at boot in DFU mode";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_MSB:
+               return "DFU encryption VM application public key MSB";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_LSB:
+               return "DFU encryption VM application public key LSB";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_M_DASH:
+               return "DFU encryption VM application M dash";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_R2N_MSB:
+               return "DFU encryption VM application public key R2N MSB";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_R2N_LSB:
+               return "DFU encryption VM application public key R2N LSB";
+       case CSR_PSKEY_BCSP_LM_PS_BLOCK:
+               return "BCSP link establishment block";
+       case CSR_PSKEY_HOSTIO_FC_PS_BLOCK:
+               return "HCI flow control block";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO0:
+               return "Host transport channel 0 settings (BCSP ACK)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO1:
+               return "Host transport channel 1 settings (BCSP-LE)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO2:
+               return "Host transport channel 2 settings (BCCMD)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO3:
+               return "Host transport channel 3 settings (HQ)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO4:
+               return "Host transport channel 4 settings (DM)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO5:
+               return "Host transport channel 5 settings (HCI CMD/EVT)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO6:
+               return "Host transport channel 6 settings (HCI ACL)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO7:
+               return "Host transport channel 7 settings (HCI SCO)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO8:
+               return "Host transport channel 8 settings (L2CAP)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO9:
+               return "Host transport channel 9 settings (RFCOMM)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO10:
+               return "Host transport channel 10 settings (SDP)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO11:
+               return "Host transport channel 11 settings (TEST)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO12:
+               return "Host transport channel 12 settings (DFU)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO13:
+               return "Host transport channel 13 settings (VM)";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO14:
+               return "Host transport channel 14 settings";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO15:
+               return "Host transport channel 15 settings";
+       case CSR_PSKEY_HOSTIO_UART_RESET_TIMEOUT:
+               return "UART reset counter timeout";
+       case CSR_PSKEY_HOSTIO_USE_HCI_EXTN:
+               return "Use hci_extn to route non-hci channels";
+       case CSR_PSKEY_HOSTIO_USE_HCI_EXTN_CCFC:
+               return "Use command-complete flow control for hci_extn";
+       case CSR_PSKEY_HOSTIO_HCI_EXTN_PAYLOAD_SIZE:
+               return "Maximum hci_extn payload size";
+       case CSR_PSKEY_BCSP_LM_CNF_CNT_LIMIT:
+               return "BCSP link establishment conf message count";
+       case CSR_PSKEY_HOSTIO_MAP_SCO_PCM:
+               return "Map SCO over PCM";
+       case CSR_PSKEY_HOSTIO_AWKWARD_PCM_SYNC:
+               return "PCM interface synchronisation is difficult";
+       case CSR_PSKEY_HOSTIO_BREAK_POLL_PERIOD:
+               return "Break poll period (microseconds)";
+       case CSR_PSKEY_HOSTIO_MIN_UART_HCI_SCO_SIZE:
+               return "Minimum SCO packet size sent to host over UART HCI";
+       case CSR_PSKEY_HOSTIO_MAP_SCO_CODEC:
+               return "Map SCO over the built-in codec";
+       case CSR_PSKEY_PCM_CVSD_TX_HI_FREQ_BOOST:
+               return "High frequency boost for PCM when transmitting CVSD";
+       case CSR_PSKEY_PCM_CVSD_RX_HI_FREQ_BOOST:
+               return "High frequency boost for PCM when receiving CVSD";
+       case CSR_PSKEY_PCM_CONFIG32:
+               return "PCM interface settings bitfields";
+       case CSR_PSKEY_USE_OLD_BCSP_LE:
+               return "Use the old version of BCSP link establishment";
+       case CSR_PSKEY_PCM_CVSD_USE_NEW_FILTER:
+               return "CVSD uses the new filter if available";
+       case CSR_PSKEY_PCM_FORMAT:
+               return "PCM data format";
+       case CSR_PSKEY_CODEC_OUT_GAIN:
+               return "Audio output gain when using built-in codec";
+       case CSR_PSKEY_CODEC_IN_GAIN:
+               return "Audio input gain when using built-in codec";
+       case CSR_PSKEY_CODEC_PIO:
+               return "PIO to enable when built-in codec is enabled";
+       case CSR_PSKEY_PCM_LOW_JITTER_CONFIG:
+               return "PCM interface settings for low jitter master mode";
+       case CSR_PSKEY_HOSTIO_SCO_PCM_THRESHOLDS:
+               return "Thresholds for SCO PCM buffers";
+       case CSR_PSKEY_HOSTIO_SCO_HCI_THRESHOLDS:
+               return "Thresholds for SCO HCI buffers";
+       case CSR_PSKEY_HOSTIO_MAP_SCO_PCM_SLOT:
+               return "Route SCO data to specified slot in pcm frame";
+       case CSR_PSKEY_UART_BAUDRATE:
+               return "UART Baud rate";
+       case CSR_PSKEY_UART_CONFIG_BCSP:
+               return "UART configuration when using BCSP";
+       case CSR_PSKEY_UART_CONFIG_H4:
+               return "UART configuration when using H4";
+       case CSR_PSKEY_UART_CONFIG_H5:
+               return "UART configuration when using H5";
+       case CSR_PSKEY_UART_CONFIG_USR:
+               return "UART configuration when under VM control";
+       case CSR_PSKEY_UART_TX_CRCS:
+               return "Use CRCs for BCSP or H5";
+       case CSR_PSKEY_UART_ACK_TIMEOUT:
+               return "Acknowledgement timeout for BCSP and H5";
+       case CSR_PSKEY_UART_TX_MAX_ATTEMPTS:
+               return "Max times to send reliable BCSP or H5 message";
+       case CSR_PSKEY_UART_TX_WINDOW_SIZE:
+               return "Transmit window size for BCSP and H5";
+       case CSR_PSKEY_UART_HOST_WAKE:
+               return "UART host wakeup";
+       case CSR_PSKEY_HOSTIO_THROTTLE_TIMEOUT:
+               return "Host interface performance control.";
+       case CSR_PSKEY_PCM_ALWAYS_ENABLE:
+               return "PCM port is always enable when chip is running";
+       case CSR_PSKEY_UART_HOST_WAKE_SIGNAL:
+               return "Signal to use for uart host wakeup protocol";
+       case CSR_PSKEY_UART_CONFIG_H4DS:
+               return "UART configuration when using H4DS";
+       case CSR_PSKEY_H4DS_WAKE_DURATION:
+               return "How long to spend waking the host when using H4DS";
+       case CSR_PSKEY_H4DS_MAXWU:
+               return "Maximum number of H4DS Wake-Up messages to send";
+       case CSR_PSKEY_H4DS_LE_TIMER_PERIOD:
+               return "H4DS Link Establishment Tsync and Tconf period";
+       case CSR_PSKEY_H4DS_TWU_TIMER_PERIOD:
+               return "H4DS Twu timer period";
+       case CSR_PSKEY_H4DS_UART_IDLE_TIMER_PERIOD:
+               return "H4DS Tuart_idle timer period";
+       case CSR_PSKEY_ANA_FTRIM:
+               return "Crystal frequency trim";
+       case CSR_PSKEY_WD_TIMEOUT:
+               return "Watchdog timeout (microseconds)";
+       case CSR_PSKEY_WD_PERIOD:
+               return "Watchdog period (microseconds)";
+       case CSR_PSKEY_HOST_INTERFACE:
+               return "Host interface";
+       case CSR_PSKEY_HQ_HOST_TIMEOUT:
+               return "HQ host command timeout";
+       case CSR_PSKEY_HQ_ACTIVE:
+               return "Enable host query task?";
+       case CSR_PSKEY_BCCMD_SECURITY_ACTIVE:
+               return "Enable configuration security";
+       case CSR_PSKEY_ANA_FREQ:
+               return "Crystal frequency";
+       case CSR_PSKEY_PIO_PROTECT_MASK:
+               return "Access to PIO pins";
+       case CSR_PSKEY_PMALLOC_SIZES:
+               return "pmalloc sizes array";
+       case CSR_PSKEY_UART_BAUD_RATE:
+               return "UART Baud rate (pre 18)";
+       case CSR_PSKEY_UART_CONFIG:
+               return "UART configuration bitfield";
+       case CSR_PSKEY_STUB:
+               return "Stub";
+       case CSR_PSKEY_TXRX_PIO_CONTROL:
+               return "TX and RX PIO control";
+       case CSR_PSKEY_ANA_RX_LEVEL:
+               return "ANA_RX_LVL register initial value";
+       case CSR_PSKEY_ANA_RX_FTRIM:
+               return "ANA_RX_FTRIM register initial value";
+       case CSR_PSKEY_PSBC_DATA_VERSION:
+               return "Persistent store version";
+       case CSR_PSKEY_PCM0_ATTENUATION:
+               return "Volume control on PCM channel 0";
+       case CSR_PSKEY_LO_LVL_MAX:
+               return "Maximum value of LO level control register";
+       case CSR_PSKEY_LO_ADC_AMPL_MIN:
+               return "Minimum value of the LO amplitude measured on the ADC";
+       case CSR_PSKEY_LO_ADC_AMPL_MAX:
+               return "Maximum value of the LO amplitude measured on the ADC";
+       case CSR_PSKEY_IQ_TRIM_CHANNEL:
+               return "IQ calibration channel";
+       case CSR_PSKEY_IQ_TRIM_GAIN:
+               return "IQ calibration gain";
+       case CSR_PSKEY_IQ_TRIM_ENABLE:
+               return "IQ calibration enable";
+       case CSR_PSKEY_TX_OFFSET_HALF_MHZ:
+               return "Transmit offset";
+       case CSR_PSKEY_GBL_MISC_ENABLES:
+               return "Global miscellaneous hardware enables";
+       case CSR_PSKEY_UART_SLEEP_TIMEOUT:
+               return "Time in ms to deep sleep if nothing received";
+       case CSR_PSKEY_DEEP_SLEEP_STATE:
+               return "Deep sleep state usage";
+       case CSR_PSKEY_IQ_ENABLE_PHASE_TRIM:
+               return "IQ phase enable";
+       case CSR_PSKEY_HCI_HANDLE_FREEZE_PERIOD:
+               return "Time for which HCI handle is frozen after link removal";
+       case CSR_PSKEY_MAX_FROZEN_HCI_HANDLES:
+               return "Maximum number of frozen HCI handles";
+       case CSR_PSKEY_PAGETABLE_DESTRUCTION_DELAY:
+               return "Delay from freezing buf handle to deleting page table";
+       case CSR_PSKEY_IQ_TRIM_PIO_SETTINGS:
+               return "IQ PIO settings";
+       case CSR_PSKEY_USE_EXTERNAL_CLOCK:
+               return "Device uses an external clock";
+       case CSR_PSKEY_DEEP_SLEEP_WAKE_CTS:
+               return "Exit deep sleep on CTS line activity";
+       case CSR_PSKEY_FC_HC2H_FLUSH_DELAY:
+               return "Delay from disconnect to flushing HC->H FC tokens";
+       case CSR_PSKEY_RX_HIGHSIDE:
+               return "Disable the HIGHSIDE bit in ANA_CONFIG";
+       case CSR_PSKEY_TX_PRE_LVL:
+               return "TX pre-amplifier level";
+       case CSR_PSKEY_RX_SINGLE_ENDED:
+               return "RX single ended";
+       case CSR_PSKEY_TX_FILTER_CONFIG:
+               return "TX filter configuration";
+       case CSR_PSKEY_CLOCK_REQUEST_ENABLE:
+               return "External clock request enable";
+       case CSR_PSKEY_RX_MIN_ATTEN:
+               return "Minimum attenuation allowed for receiver";
+       case CSR_PSKEY_XTAL_TARGET_AMPLITUDE:
+               return "Crystal target amplitude";
+       case CSR_PSKEY_PCM_MIN_CPU_CLOCK:
+               return "Minimum CPU clock speed with PCM port running";
+       case CSR_PSKEY_HOST_INTERFACE_PIO_USB:
+               return "USB host interface selection PIO line";
+       case CSR_PSKEY_CPU_IDLE_MODE:
+               return "CPU idle mode when radio is active";
+       case CSR_PSKEY_DEEP_SLEEP_CLEAR_RTS:
+               return "Deep sleep clears the UART RTS line";
+       case CSR_PSKEY_RF_RESONANCE_TRIM:
+               return "Frequency trim for IQ and LNA resonant circuits";
+       case CSR_PSKEY_DEEP_SLEEP_PIO_WAKE:
+               return "PIO line to wake the chip from deep sleep";
+       case CSR_PSKEY_DRAIN_BORE_TIMERS:
+               return "Energy consumption measurement settings";
+       case CSR_PSKEY_DRAIN_TX_POWER_BASE:
+               return "Energy consumption measurement settings";
+       case CSR_PSKEY_MODULE_ID:
+               return "Module serial number";
+       case CSR_PSKEY_MODULE_DESIGN:
+               return "Module design ID";
+       case CSR_PSKEY_MODULE_SECURITY_CODE:
+               return "Module security code";
+       case CSR_PSKEY_VM_DISABLE:
+               return "VM disable";
+       case CSR_PSKEY_MOD_MANUF0:
+               return "Module manufactuer data 0";
+       case CSR_PSKEY_MOD_MANUF1:
+               return "Module manufactuer data 1";
+       case CSR_PSKEY_MOD_MANUF2:
+               return "Module manufactuer data 2";
+       case CSR_PSKEY_MOD_MANUF3:
+               return "Module manufactuer data 3";
+       case CSR_PSKEY_MOD_MANUF4:
+               return "Module manufactuer data 4";
+       case CSR_PSKEY_MOD_MANUF5:
+               return "Module manufactuer data 5";
+       case CSR_PSKEY_MOD_MANUF6:
+               return "Module manufactuer data 6";
+       case CSR_PSKEY_MOD_MANUF7:
+               return "Module manufactuer data 7";
+       case CSR_PSKEY_MOD_MANUF8:
+               return "Module manufactuer data 8";
+       case CSR_PSKEY_MOD_MANUF9:
+               return "Module manufactuer data 9";
+       case CSR_PSKEY_DUT_VM_DISABLE:
+               return "VM disable when entering radiotest modes";
+       case CSR_PSKEY_USR0:
+               return "User configuration data 0";
+       case CSR_PSKEY_USR1:
+               return "User configuration data 1";
+       case CSR_PSKEY_USR2:
+               return "User configuration data 2";
+       case CSR_PSKEY_USR3:
+               return "User configuration data 3";
+       case CSR_PSKEY_USR4:
+               return "User configuration data 4";
+       case CSR_PSKEY_USR5:
+               return "User configuration data 5";
+       case CSR_PSKEY_USR6:
+               return "User configuration data 6";
+       case CSR_PSKEY_USR7:
+               return "User configuration data 7";
+       case CSR_PSKEY_USR8:
+               return "User configuration data 8";
+       case CSR_PSKEY_USR9:
+               return "User configuration data 9";
+       case CSR_PSKEY_USR10:
+               return "User configuration data 10";
+       case CSR_PSKEY_USR11:
+               return "User configuration data 11";
+       case CSR_PSKEY_USR12:
+               return "User configuration data 12";
+       case CSR_PSKEY_USR13:
+               return "User configuration data 13";
+       case CSR_PSKEY_USR14:
+               return "User configuration data 14";
+       case CSR_PSKEY_USR15:
+               return "User configuration data 15";
+       case CSR_PSKEY_USR16:
+               return "User configuration data 16";
+       case CSR_PSKEY_USR17:
+               return "User configuration data 17";
+       case CSR_PSKEY_USR18:
+               return "User configuration data 18";
+       case CSR_PSKEY_USR19:
+               return "User configuration data 19";
+       case CSR_PSKEY_USR20:
+               return "User configuration data 20";
+       case CSR_PSKEY_USR21:
+               return "User configuration data 21";
+       case CSR_PSKEY_USR22:
+               return "User configuration data 22";
+       case CSR_PSKEY_USR23:
+               return "User configuration data 23";
+       case CSR_PSKEY_USR24:
+               return "User configuration data 24";
+       case CSR_PSKEY_USR25:
+               return "User configuration data 25";
+       case CSR_PSKEY_USR26:
+               return "User configuration data 26";
+       case CSR_PSKEY_USR27:
+               return "User configuration data 27";
+       case CSR_PSKEY_USR28:
+               return "User configuration data 28";
+       case CSR_PSKEY_USR29:
+               return "User configuration data 29";
+       case CSR_PSKEY_USR30:
+               return "User configuration data 30";
+       case CSR_PSKEY_USR31:
+               return "User configuration data 31";
+       case CSR_PSKEY_USR32:
+               return "User configuration data 32";
+       case CSR_PSKEY_USR33:
+               return "User configuration data 33";
+       case CSR_PSKEY_USR34:
+               return "User configuration data 34";
+       case CSR_PSKEY_USR35:
+               return "User configuration data 35";
+       case CSR_PSKEY_USR36:
+               return "User configuration data 36";
+       case CSR_PSKEY_USR37:
+               return "User configuration data 37";
+       case CSR_PSKEY_USR38:
+               return "User configuration data 38";
+       case CSR_PSKEY_USR39:
+               return "User configuration data 39";
+       case CSR_PSKEY_USR40:
+               return "User configuration data 40";
+       case CSR_PSKEY_USR41:
+               return "User configuration data 41";
+       case CSR_PSKEY_USR42:
+               return "User configuration data 42";
+       case CSR_PSKEY_USR43:
+               return "User configuration data 43";
+       case CSR_PSKEY_USR44:
+               return "User configuration data 44";
+       case CSR_PSKEY_USR45:
+               return "User configuration data 45";
+       case CSR_PSKEY_USR46:
+               return "User configuration data 46";
+       case CSR_PSKEY_USR47:
+               return "User configuration data 47";
+       case CSR_PSKEY_USR48:
+               return "User configuration data 48";
+       case CSR_PSKEY_USR49:
+               return "User configuration data 49";
+       case CSR_PSKEY_USB_VERSION:
+               return "USB specification version number";
+       case CSR_PSKEY_USB_DEVICE_CLASS_CODES:
+               return "USB device class codes";
+       case CSR_PSKEY_USB_VENDOR_ID:
+               return "USB vendor identifier";
+       case CSR_PSKEY_USB_PRODUCT_ID:
+               return "USB product identifier";
+       case CSR_PSKEY_USB_MANUF_STRING:
+               return "USB manufacturer string";
+       case CSR_PSKEY_USB_PRODUCT_STRING:
+               return "USB product string";
+       case CSR_PSKEY_USB_SERIAL_NUMBER_STRING:
+               return "USB serial number string";
+       case CSR_PSKEY_USB_CONFIG_STRING:
+               return "USB configuration string";
+       case CSR_PSKEY_USB_ATTRIBUTES:
+               return "USB attributes bitmap";
+       case CSR_PSKEY_USB_MAX_POWER:
+               return "USB device maximum power consumption";
+       case CSR_PSKEY_USB_BT_IF_CLASS_CODES:
+               return "USB Bluetooth interface class codes";
+       case CSR_PSKEY_USB_LANGID:
+               return "USB language strings supported";
+       case CSR_PSKEY_USB_DFU_CLASS_CODES:
+               return "USB DFU class codes block";
+       case CSR_PSKEY_USB_DFU_PRODUCT_ID:
+               return "USB DFU product ID";
+       case CSR_PSKEY_USB_PIO_DETACH:
+               return "USB detach/attach PIO line";
+       case CSR_PSKEY_USB_PIO_WAKEUP:
+               return "USB wakeup PIO line";
+       case CSR_PSKEY_USB_PIO_PULLUP:
+               return "USB D+ pullup PIO line";
+       case CSR_PSKEY_USB_PIO_VBUS:
+               return "USB VBus detection PIO Line";
+       case CSR_PSKEY_USB_PIO_WAKE_TIMEOUT:
+               return "Timeout for assertion of USB PIO wake signal";
+       case CSR_PSKEY_USB_PIO_RESUME:
+               return "PIO signal used in place of bus resume";
+       case CSR_PSKEY_USB_BT_SCO_IF_CLASS_CODES:
+               return "USB Bluetooth SCO interface class codes";
+       case CSR_PSKEY_USB_SUSPEND_PIO_LEVEL:
+               return "USB PIO levels to set when suspended";
+       case CSR_PSKEY_USB_SUSPEND_PIO_DIR:
+               return "USB PIO I/O directions to set when suspended";
+       case CSR_PSKEY_USB_SUSPEND_PIO_MASK:
+               return "USB PIO lines to be set forcibly in suspend";
+       case CSR_PSKEY_USB_ENDPOINT_0_MAX_PACKET_SIZE:
+               return "The maxmimum packet size for USB endpoint 0";
+       case CSR_PSKEY_USB_CONFIG:
+               return "USB config params for new chips (>bc2)";
+       case CSR_PSKEY_RADIOTEST_ATTEN_INIT:
+               return "Radio test initial attenuator";
+       case CSR_PSKEY_RADIOTEST_FIRST_TRIM_TIME:
+               return "IQ first calibration period in test";
+       case CSR_PSKEY_RADIOTEST_SUBSEQUENT_TRIM_TIME:
+               return "IQ subsequent calibration period in test";
+       case CSR_PSKEY_RADIOTEST_LO_LVL_TRIM_ENABLE:
+               return "LO_LVL calibration enable";
+       case CSR_PSKEY_RADIOTEST_DISABLE_MODULATION:
+               return "Disable modulation during radiotest transmissions";
+       case CSR_PSKEY_RFCOMM_FCON_THRESHOLD:
+               return "RFCOMM aggregate flow control on threshold";
+       case CSR_PSKEY_RFCOMM_FCOFF_THRESHOLD:
+               return "RFCOMM aggregate flow control off threshold";
+       case CSR_PSKEY_IPV6_STATIC_ADDR:
+               return "Static IPv6 address";
+       case CSR_PSKEY_IPV4_STATIC_ADDR:
+               return "Static IPv4 address";
+       case CSR_PSKEY_IPV6_STATIC_PREFIX_LEN:
+               return "Static IPv6 prefix length";
+       case CSR_PSKEY_IPV6_STATIC_ROUTER_ADDR:
+               return "Static IPv6 router address";
+       case CSR_PSKEY_IPV4_STATIC_SUBNET_MASK:
+               return "Static IPv4 subnet mask";
+       case CSR_PSKEY_IPV4_STATIC_ROUTER_ADDR:
+               return "Static IPv4 router address";
+       case CSR_PSKEY_MDNS_NAME:
+               return "Multicast DNS name";
+       case CSR_PSKEY_FIXED_PIN:
+               return "Fixed PIN";
+       case CSR_PSKEY_MDNS_PORT:
+               return "Multicast DNS port";
+       case CSR_PSKEY_MDNS_TTL:
+               return "Multicast DNS TTL";
+       case CSR_PSKEY_MDNS_IPV4_ADDR:
+               return "Multicast DNS IPv4 address";
+       case CSR_PSKEY_ARP_CACHE_TIMEOUT:
+               return "ARP cache timeout";
+       case CSR_PSKEY_HFP_POWER_TABLE:
+               return "HFP power table";
+       case CSR_PSKEY_DRAIN_BORE_TIMER_COUNTERS:
+               return "Energy consumption estimation timer counters";
+       case CSR_PSKEY_DRAIN_BORE_COUNTERS:
+               return "Energy consumption estimation counters";
+       case CSR_PSKEY_LOOP_FILTER_TRIM:
+               return "Trim value to optimise loop filter";
+       case CSR_PSKEY_DRAIN_BORE_CURRENT_PEAK:
+               return "Energy consumption estimation current peak";
+       case CSR_PSKEY_VM_E2_CACHE_LIMIT:
+               return "Maximum RAM for caching EEPROM VM application";
+       case CSR_PSKEY_FORCE_16MHZ_REF_PIO:
+               return "PIO line to force 16 MHz reference to be assumed";
+       case CSR_PSKEY_CDMA_LO_REF_LIMITS:
+               return "Local oscillator frequency reference limits for CDMA";
+       case CSR_PSKEY_CDMA_LO_ERROR_LIMITS:
+               return "Local oscillator frequency error limits for CDMA";
+       case CSR_PSKEY_CLOCK_STARTUP_DELAY:
+               return "Clock startup delay in milliseconds";
+       case CSR_PSKEY_DEEP_SLEEP_CORRECTION_FACTOR:
+               return "Deep sleep clock correction factor";
+       case CSR_PSKEY_TEMPERATURE_CALIBRATION:
+               return "Temperature in deg C for a given internal setting";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA:
+               return "Temperature for given internal PA adjustment";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL:
+               return "Temperature for a given TX_PRE_LVL adjustment";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB:
+               return "Temperature for a given TX_BB adjustment";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_ANA_FTRIM:
+               return "Temperature for given crystal trim adjustment";
+       case CSR_PSKEY_TEST_DELTA_OFFSET:
+               return "Frequency offset applied to synthesiser in test mode";
+       case CSR_PSKEY_RX_DYNAMIC_LVL_OFFSET:
+               return "Receiver dynamic level offset depending on channel";
+       case CSR_PSKEY_TEST_FORCE_OFFSET:
+               return "Force use of exact value in PSKEY_TEST_DELTA_OFFSET";
+       case CSR_PSKEY_RF_TRAP_BAD_DIVISION_RATIOS:
+               return "Trap bad division ratios in radio frequency tables";
+       case CSR_PSKEY_RADIOTEST_CDMA_LO_REF_LIMITS:
+               return "LO frequency reference limits for CDMA in radiotest";
+       case CSR_PSKEY_INITIAL_BOOTMODE:
+               return "Initial device bootmode";
+       case CSR_PSKEY_ONCHIP_HCI_CLIENT:
+               return "HCI traffic routed internally";
+       case CSR_PSKEY_RX_ATTEN_BACKOFF:
+               return "Receiver attenuation back-off";
+       case CSR_PSKEY_RX_ATTEN_UPDATE_RATE:
+               return "Receiver attenuation update rate";
+       case CSR_PSKEY_SYNTH_TXRX_THRESHOLDS:
+               return "Local oscillator tuning voltage limits for tx and rx";
+       case CSR_PSKEY_MIN_WAIT_STATES:
+               return "Flash wait state indicator";
+       case CSR_PSKEY_RSSI_CORRECTION:
+               return "RSSI correction factor.";
+       case CSR_PSKEY_SCHED_THROTTLE_TIMEOUT:
+               return "Scheduler performance control.";
+       case CSR_PSKEY_DEEP_SLEEP_USE_EXTERNAL_CLOCK:
+               return "Deep sleep uses external 32 kHz clock source";
+       case CSR_PSKEY_TRIM_RADIO_FILTERS:
+               return "Trim rx and tx radio filters if true.";
+       case CSR_PSKEY_TRANSMIT_OFFSET:
+               return "Transmit offset in units of 62.5 kHz";
+       case CSR_PSKEY_USB_VM_CONTROL:
+               return "VM application will supply USB descriptors";
+       case CSR_PSKEY_MR_ANA_RX_FTRIM:
+               return "Medium rate value for the ANA_RX_FTRIM register";
+       case CSR_PSKEY_I2C_CONFIG:
+               return "I2C configuration";
+       case CSR_PSKEY_IQ_LVL_RX:
+               return "IQ demand level for reception";
+       case CSR_PSKEY_MR_TX_FILTER_CONFIG:
+               return "TX filter configuration used for enhanced data rate";
+       case CSR_PSKEY_MR_TX_CONFIG2:
+               return "TX filter configuration used for enhanced data rate";
+       case CSR_PSKEY_USB_DONT_RESET_BOOTMODE_ON_HOST_RESET:
+               return "Don't reset bootmode if USB host resets";
+       case CSR_PSKEY_LC_USE_THROTTLING:
+               return "Adjust packet selection on packet error rate";
+       case CSR_PSKEY_CHARGER_TRIM:
+               return "Trim value for the current charger";
+       case CSR_PSKEY_CLOCK_REQUEST_FEATURES:
+               return "Clock request is tristated if enabled";
+       case CSR_PSKEY_TRANSMIT_OFFSET_CLASS1:
+               return "Transmit offset / 62.5 kHz for class 1 radios";
+       case CSR_PSKEY_TX_AVOID_PA_CLASS1_PIO:
+               return "PIO line asserted in class1 operation to avoid PA";
+       case CSR_PSKEY_MR_PIO_CONFIG:
+               return "PIO line asserted in class1 operation to avoid PA";
+       case CSR_PSKEY_UART_CONFIG2:
+               return "The UART Sampling point";
+       case CSR_PSKEY_CLASS1_IQ_LVL:
+               return "IQ demand level for class 1 power level";
+       case CSR_PSKEY_CLASS1_TX_CONFIG2:
+               return "TX filter configuration used for class 1 tx power";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA_CLASS1:
+               return "Temperature for given internal PA adjustment";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_EXTERNAL_PA_CLASS1:
+               return "Temperature for given internal PA adjustment";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL_MR:
+               return "Temperature adjustment for TX_PRE_LVL in EDR";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_HEADER:
+               return "Temperature for a given TX_BB in EDR header";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_PAYLOAD:
+               return "Temperature for a given TX_BB in EDR payload";
+       case CSR_PSKEY_RX_MR_EQ_TAPS:
+               return "Adjust receiver configuration for EDR";
+       case CSR_PSKEY_TX_PRE_LVL_CLASS1:
+               return "TX pre-amplifier level in class 1 operation";
+       case CSR_PSKEY_ANALOGUE_ATTENUATOR:
+               return "TX analogue attenuator setting";
+       case CSR_PSKEY_MR_RX_FILTER_TRIM:
+               return "Trim for receiver used in EDR.";
+       case CSR_PSKEY_MR_RX_FILTER_RESPONSE:
+               return "Filter response for receiver used in EDR.";
+       case CSR_PSKEY_PIO_WAKEUP_STATE:
+               return "PIO deep sleep wake up state ";
+       case CSR_PSKEY_MR_TX_IF_ATTEN_OFF_TEMP:
+               return "TX IF atten off temperature when using EDR.";
+       case CSR_PSKEY_LO_DIV_LATCH_BYPASS:
+               return "Bypass latch for LO dividers";
+       case CSR_PSKEY_LO_VCO_STANDBY:
+               return "Use standby mode for the LO VCO";
+       case CSR_PSKEY_SLOW_CLOCK_FILTER_SHIFT:
+               return "Slow clock sampling filter constant";
+       case CSR_PSKEY_SLOW_CLOCK_FILTER_DIVIDER:
+               return "Slow clock filter fractional threshold";
+       case CSR_PSKEY_USB_ATTRIBUTES_POWER:
+               return "USB self powered";
+       case CSR_PSKEY_USB_ATTRIBUTES_WAKEUP:
+               return "USB responds to wake-up";
+       case CSR_PSKEY_DFU_ATTRIBUTES_MANIFESTATION_TOLERANT:
+               return "DFU manifestation tolerant";
+       case CSR_PSKEY_DFU_ATTRIBUTES_CAN_UPLOAD:
+               return "DFU can upload";
+       case CSR_PSKEY_DFU_ATTRIBUTES_CAN_DOWNLOAD:
+               return "DFU can download";
+       case CSR_PSKEY_UART_CONFIG_STOP_BITS:
+               return "UART: stop bits";
+       case CSR_PSKEY_UART_CONFIG_PARITY_BIT:
+               return "UART: parity bit";
+       case CSR_PSKEY_UART_CONFIG_FLOW_CTRL_EN:
+               return "UART: hardware flow control";
+       case CSR_PSKEY_UART_CONFIG_RTS_AUTO_EN:
+               return "UART: RTS auto-enabled";
+       case CSR_PSKEY_UART_CONFIG_RTS:
+               return "UART: RTS asserted";
+       case CSR_PSKEY_UART_CONFIG_TX_ZERO_EN:
+               return "UART: TX zero enable";
+       case CSR_PSKEY_UART_CONFIG_NON_BCSP_EN:
+               return "UART: enable BCSP-specific hardware";
+       case CSR_PSKEY_UART_CONFIG_RX_RATE_DELAY:
+               return "UART: RX rate delay";
+       case CSR_PSKEY_UART_SEQ_TIMEOUT:
+               return "UART: BCSP ack timeout";
+       case CSR_PSKEY_UART_SEQ_RETRIES:
+               return "UART: retry limit in sequencing layer";
+       case CSR_PSKEY_UART_SEQ_WINSIZE:
+               return "UART: BCSP transmit window size";
+       case CSR_PSKEY_UART_USE_CRC_ON_TX:
+               return "UART: use BCSP CRCs";
+       case CSR_PSKEY_UART_HOST_INITIAL_STATE:
+               return "UART: initial host state";
+       case CSR_PSKEY_UART_HOST_ATTENTION_SPAN:
+               return "UART: host attention span";
+       case CSR_PSKEY_UART_HOST_WAKEUP_TIME:
+               return "UART: host wakeup time";
+       case CSR_PSKEY_UART_HOST_WAKEUP_WAIT:
+               return "UART: host wakeup wait";
+       case CSR_PSKEY_BCSP_LM_MODE:
+               return "BCSP link establishment mode";
+       case CSR_PSKEY_BCSP_LM_SYNC_RETRIES:
+               return "BCSP link establishment sync retries";
+       case CSR_PSKEY_BCSP_LM_TSHY:
+               return "BCSP link establishment Tshy";
+       case CSR_PSKEY_UART_DFU_CONFIG_STOP_BITS:
+               return "DFU mode UART: stop bits";
+       case CSR_PSKEY_UART_DFU_CONFIG_PARITY_BIT:
+               return "DFU mode UART: parity bit";
+       case CSR_PSKEY_UART_DFU_CONFIG_FLOW_CTRL_EN:
+               return "DFU mode UART: hardware flow control";
+       case CSR_PSKEY_UART_DFU_CONFIG_RTS_AUTO_EN:
+               return "DFU mode UART: RTS auto-enabled";
+       case CSR_PSKEY_UART_DFU_CONFIG_RTS:
+               return "DFU mode UART: RTS asserted";
+       case CSR_PSKEY_UART_DFU_CONFIG_TX_ZERO_EN:
+               return "DFU mode UART: TX zero enable";
+       case CSR_PSKEY_UART_DFU_CONFIG_NON_BCSP_EN:
+               return "DFU mode UART: enable BCSP-specific hardware";
+       case CSR_PSKEY_UART_DFU_CONFIG_RX_RATE_DELAY:
+               return "DFU mode UART: RX rate delay";
+       case CSR_PSKEY_AMUX_AIO0:
+               return "Multiplexer for AIO 0";
+       case CSR_PSKEY_AMUX_AIO1:
+               return "Multiplexer for AIO 1";
+       case CSR_PSKEY_AMUX_AIO2:
+               return "Multiplexer for AIO 2";
+       case CSR_PSKEY_AMUX_AIO3:
+               return "Multiplexer for AIO 3";
+       case CSR_PSKEY_LOCAL_NAME_SIMPLIFIED:
+               return "Local Name (simplified)";
+       case CSR_PSKEY_EXTENDED_STUB:
+               return "Extended stub";
+       default:
+               return "Unknown";
+       }
+}
+
+char *csr_pskeytoval(uint16_t pskey)
+{
+       switch (pskey) {
+       case CSR_PSKEY_BDADDR:
+               return "BDADDR";
+       case CSR_PSKEY_COUNTRYCODE:
+               return "COUNTRYCODE";
+       case CSR_PSKEY_CLASSOFDEVICE:
+               return "CLASSOFDEVICE";
+       case CSR_PSKEY_DEVICE_DRIFT:
+               return "DEVICE_DRIFT";
+       case CSR_PSKEY_DEVICE_JITTER:
+               return "DEVICE_JITTER";
+       case CSR_PSKEY_MAX_ACLS:
+               return "MAX_ACLS";
+       case CSR_PSKEY_MAX_SCOS:
+               return "MAX_SCOS";
+       case CSR_PSKEY_MAX_REMOTE_MASTERS:
+               return "MAX_REMOTE_MASTERS";
+       case CSR_PSKEY_ENABLE_MASTERY_WITH_SLAVERY:
+               return "ENABLE_MASTERY_WITH_SLAVERY";
+       case CSR_PSKEY_H_HC_FC_MAX_ACL_PKT_LEN:
+               return "H_HC_FC_MAX_ACL_PKT_LEN";
+       case CSR_PSKEY_H_HC_FC_MAX_SCO_PKT_LEN:
+               return "H_HC_FC_MAX_SCO_PKT_LEN";
+       case CSR_PSKEY_H_HC_FC_MAX_ACL_PKTS:
+               return "H_HC_FC_MAX_ACL_PKTS";
+       case CSR_PSKEY_H_HC_FC_MAX_SCO_PKTS:
+               return "H_HC_FC_MAX_SCO_PKTS";
+       case CSR_PSKEY_LC_FC_BUFFER_LOW_WATER_MARK:
+               return "LC_FC_BUFFER_LOW_WATER_MARK";
+       case CSR_PSKEY_LC_MAX_TX_POWER:
+               return "LC_MAX_TX_POWER";
+       case CSR_PSKEY_TX_GAIN_RAMP:
+               return "TX_GAIN_RAMP";
+       case CSR_PSKEY_LC_POWER_TABLE:
+               return "LC_POWER_TABLE";
+       case CSR_PSKEY_LC_PEER_POWER_PERIOD:
+               return "LC_PEER_POWER_PERIOD";
+       case CSR_PSKEY_LC_FC_POOLS_LOW_WATER_MARK:
+               return "LC_FC_POOLS_LOW_WATER_MARK";
+       case CSR_PSKEY_LC_DEFAULT_TX_POWER:
+               return "LC_DEFAULT_TX_POWER";
+       case CSR_PSKEY_LC_RSSI_GOLDEN_RANGE:
+               return "LC_RSSI_GOLDEN_RANGE";
+       case CSR_PSKEY_LC_COMBO_DISABLE_PIO_MASK:
+               return "LC_COMBO_DISABLE_PIO_MASK";
+       case CSR_PSKEY_LC_COMBO_PRIORITY_PIO_MASK:
+               return "LC_COMBO_PRIORITY_PIO_MASK";
+       case CSR_PSKEY_LC_COMBO_DOT11_CHANNEL_PIO_BASE:
+               return "LC_COMBO_DOT11_CHANNEL_PIO_BASE";
+       case CSR_PSKEY_LC_COMBO_DOT11_BLOCK_CHANNELS:
+               return "LC_COMBO_DOT11_BLOCK_CHANNELS";
+       case CSR_PSKEY_LC_MAX_TX_POWER_NO_RSSI:
+               return "LC_MAX_TX_POWER_NO_RSSI";
+       case CSR_PSKEY_LC_CONNECTION_RX_WINDOW:
+               return "LC_CONNECTION_RX_WINDOW";
+       case CSR_PSKEY_LC_COMBO_DOT11_TX_PROTECTION_MODE:
+               return "LC_COMBO_DOT11_TX_PROTECTION_MODE";
+       case CSR_PSKEY_LC_ENHANCED_POWER_TABLE:
+               return "LC_ENHANCED_POWER_TABLE";
+       case CSR_PSKEY_LC_WIDEBAND_RSSI_CONFIG:
+               return "LC_WIDEBAND_RSSI_CONFIG";
+       case CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_LEAD:
+               return "LC_COMBO_DOT11_PRIORITY_LEAD";
+       case CSR_PSKEY_BT_CLOCK_INIT:
+               return "BT_CLOCK_INIT";
+       case CSR_PSKEY_TX_MR_MOD_DELAY:
+               return "TX_MR_MOD_DELAY";
+       case CSR_PSKEY_RX_MR_SYNC_TIMING:
+               return "RX_MR_SYNC_TIMING";
+       case CSR_PSKEY_RX_MR_SYNC_CONFIG:
+               return "RX_MR_SYNC_CONFIG";
+       case CSR_PSKEY_LC_LOST_SYNC_SLOTS:
+               return "LC_LOST_SYNC_SLOTS";
+       case CSR_PSKEY_RX_MR_SAMP_CONFIG:
+               return "RX_MR_SAMP_CONFIG";
+       case CSR_PSKEY_AGC_HYST_LEVELS:
+               return "AGC_HYST_LEVELS";
+       case CSR_PSKEY_RX_LEVEL_LOW_SIGNAL:
+               return "RX_LEVEL_LOW_SIGNAL";
+       case CSR_PSKEY_AGC_IQ_LVL_VALUES:
+               return "AGC_IQ_LVL_VALUES";
+       case CSR_PSKEY_MR_FTRIM_OFFSET_12DB:
+               return "MR_FTRIM_OFFSET_12DB";
+       case CSR_PSKEY_MR_FTRIM_OFFSET_6DB:
+               return "MR_FTRIM_OFFSET_6DB";
+       case CSR_PSKEY_NO_CAL_ON_BOOT:
+               return "NO_CAL_ON_BOOT";
+       case CSR_PSKEY_RSSI_HI_TARGET:
+               return "RSSI_HI_TARGET";
+       case CSR_PSKEY_PREFERRED_MIN_ATTENUATION:
+               return "PREFERRED_MIN_ATTENUATION";
+       case CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_OVERRIDE:
+               return "LC_COMBO_DOT11_PRIORITY_OVERRIDE";
+       case CSR_PSKEY_LC_MULTISLOT_HOLDOFF:
+               return "LC_MULTISLOT_HOLDOFF";
+       case CSR_PSKEY_FREE_KEY_PIGEON_HOLE:
+               return "FREE_KEY_PIGEON_HOLE";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR0:
+               return "LINK_KEY_BD_ADDR0";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR1:
+               return "LINK_KEY_BD_ADDR1";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR2:
+               return "LINK_KEY_BD_ADDR2";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR3:
+               return "LINK_KEY_BD_ADDR3";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR4:
+               return "LINK_KEY_BD_ADDR4";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR5:
+               return "LINK_KEY_BD_ADDR5";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR6:
+               return "LINK_KEY_BD_ADDR6";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR7:
+               return "LINK_KEY_BD_ADDR7";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR8:
+               return "LINK_KEY_BD_ADDR8";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR9:
+               return "LINK_KEY_BD_ADDR9";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR10:
+               return "LINK_KEY_BD_ADDR10";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR11:
+               return "LINK_KEY_BD_ADDR11";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR12:
+               return "LINK_KEY_BD_ADDR12";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR13:
+               return "LINK_KEY_BD_ADDR13";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR14:
+               return "LINK_KEY_BD_ADDR14";
+       case CSR_PSKEY_LINK_KEY_BD_ADDR15:
+               return "LINK_KEY_BD_ADDR15";
+       case CSR_PSKEY_ENC_KEY_LMIN:
+               return "ENC_KEY_LMIN";
+       case CSR_PSKEY_ENC_KEY_LMAX:
+               return "ENC_KEY_LMAX";
+       case CSR_PSKEY_LOCAL_SUPPORTED_FEATURES:
+               return "LOCAL_SUPPORTED_FEATURES";
+       case CSR_PSKEY_LM_USE_UNIT_KEY:
+               return "LM_USE_UNIT_KEY";
+       case CSR_PSKEY_HCI_NOP_DISABLE:
+               return "HCI_NOP_DISABLE";
+       case CSR_PSKEY_LM_MAX_EVENT_FILTERS:
+               return "LM_MAX_EVENT_FILTERS";
+       case CSR_PSKEY_LM_USE_ENC_MODE_BROADCAST:
+               return "LM_USE_ENC_MODE_BROADCAST";
+       case CSR_PSKEY_LM_TEST_SEND_ACCEPTED_TWICE:
+               return "LM_TEST_SEND_ACCEPTED_TWICE";
+       case CSR_PSKEY_LM_MAX_PAGE_HOLD_TIME:
+               return "LM_MAX_PAGE_HOLD_TIME";
+       case CSR_PSKEY_AFH_ADAPTATION_RESPONSE_TIME:
+               return "AFH_ADAPTATION_RESPONSE_TIME";
+       case CSR_PSKEY_AFH_OPTIONS:
+               return "AFH_OPTIONS";
+       case CSR_PSKEY_AFH_RSSI_RUN_PERIOD:
+               return "AFH_RSSI_RUN_PERIOD";
+       case CSR_PSKEY_AFH_REENABLE_CHANNEL_TIME:
+               return "AFH_REENABLE_CHANNEL_TIME";
+       case CSR_PSKEY_NO_DROP_ON_ACR_MS_FAIL:
+               return "NO_DROP_ON_ACR_MS_FAIL";
+       case CSR_PSKEY_MAX_PRIVATE_KEYS:
+               return "MAX_PRIVATE_KEYS";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR0:
+               return "PRIVATE_LINK_KEY_BD_ADDR0";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR1:
+               return "PRIVATE_LINK_KEY_BD_ADDR1";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR2:
+               return "PRIVATE_LINK_KEY_BD_ADDR2";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR3:
+               return "PRIVATE_LINK_KEY_BD_ADDR3";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR4:
+               return "PRIVATE_LINK_KEY_BD_ADDR4";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR5:
+               return "PRIVATE_LINK_KEY_BD_ADDR5";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR6:
+               return "PRIVATE_LINK_KEY_BD_ADDR6";
+       case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR7:
+               return "PRIVATE_LINK_KEY_BD_ADDR7";
+       case CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS:
+               return "LOCAL_SUPPORTED_COMMANDS";
+       case CSR_PSKEY_LM_MAX_ABSENCE_INDEX:
+               return "LM_MAX_ABSENCE_INDEX";
+       case CSR_PSKEY_DEVICE_NAME:
+               return "DEVICE_NAME";
+       case CSR_PSKEY_AFH_RSSI_THRESHOLD:
+               return "AFH_RSSI_THRESHOLD";
+       case CSR_PSKEY_LM_CASUAL_SCAN_INTERVAL:
+               return "LM_CASUAL_SCAN_INTERVAL";
+       case CSR_PSKEY_AFH_MIN_MAP_CHANGE:
+               return "AFH_MIN_MAP_CHANGE";
+       case CSR_PSKEY_AFH_RSSI_LP_RUN_PERIOD:
+               return "AFH_RSSI_LP_RUN_PERIOD";
+       case CSR_PSKEY_HCI_LMP_LOCAL_VERSION:
+               return "HCI_LMP_LOCAL_VERSION";
+       case CSR_PSKEY_LMP_REMOTE_VERSION:
+               return "LMP_REMOTE_VERSION";
+       case CSR_PSKEY_HOLD_ERROR_MESSAGE_NUMBER:
+               return "HOLD_ERROR_MESSAGE_NUMBER";
+       case CSR_PSKEY_DFU_ATTRIBUTES:
+               return "DFU_ATTRIBUTES";
+       case CSR_PSKEY_DFU_DETACH_TO:
+               return "DFU_DETACH_TO";
+       case CSR_PSKEY_DFU_TRANSFER_SIZE:
+               return "DFU_TRANSFER_SIZE";
+       case CSR_PSKEY_DFU_ENABLE:
+               return "DFU_ENABLE";
+       case CSR_PSKEY_DFU_LIN_REG_ENABLE:
+               return "DFU_LIN_REG_ENABLE";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_MSB:
+               return "DFUENC_VMAPP_PK_MODULUS_MSB";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_LSB:
+               return "DFUENC_VMAPP_PK_MODULUS_LSB";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_M_DASH:
+               return "DFUENC_VMAPP_PK_M_DASH";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_R2N_MSB:
+               return "DFUENC_VMAPP_PK_R2N_MSB";
+       case CSR_PSKEY_DFUENC_VMAPP_PK_R2N_LSB:
+               return "DFUENC_VMAPP_PK_R2N_LSB";
+       case CSR_PSKEY_BCSP_LM_PS_BLOCK:
+               return "BCSP_LM_PS_BLOCK";
+       case CSR_PSKEY_HOSTIO_FC_PS_BLOCK:
+               return "HOSTIO_FC_PS_BLOCK";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO0:
+               return "HOSTIO_PROTOCOL_INFO0";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO1:
+               return "HOSTIO_PROTOCOL_INFO1";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO2:
+               return "HOSTIO_PROTOCOL_INFO2";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO3:
+               return "HOSTIO_PROTOCOL_INFO3";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO4:
+               return "HOSTIO_PROTOCOL_INFO4";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO5:
+               return "HOSTIO_PROTOCOL_INFO5";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO6:
+               return "HOSTIO_PROTOCOL_INFO6";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO7:
+               return "HOSTIO_PROTOCOL_INFO7";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO8:
+               return "HOSTIO_PROTOCOL_INFO8";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO9:
+               return "HOSTIO_PROTOCOL_INFO9";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO10:
+               return "HOSTIO_PROTOCOL_INFO10";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO11:
+               return "HOSTIO_PROTOCOL_INFO11";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO12:
+               return "HOSTIO_PROTOCOL_INFO12";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO13:
+               return "HOSTIO_PROTOCOL_INFO13";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO14:
+               return "HOSTIO_PROTOCOL_INFO14";
+       case CSR_PSKEY_HOSTIO_PROTOCOL_INFO15:
+               return "HOSTIO_PROTOCOL_INFO15";
+       case CSR_PSKEY_HOSTIO_UART_RESET_TIMEOUT:
+               return "HOSTIO_UART_RESET_TIMEOUT";
+       case CSR_PSKEY_HOSTIO_USE_HCI_EXTN:
+               return "HOSTIO_USE_HCI_EXTN";
+       case CSR_PSKEY_HOSTIO_USE_HCI_EXTN_CCFC:
+               return "HOSTIO_USE_HCI_EXTN_CCFC";
+       case CSR_PSKEY_HOSTIO_HCI_EXTN_PAYLOAD_SIZE:
+               return "HOSTIO_HCI_EXTN_PAYLOAD_SIZE";
+       case CSR_PSKEY_BCSP_LM_CNF_CNT_LIMIT:
+               return "BCSP_LM_CNF_CNT_LIMIT";
+       case CSR_PSKEY_HOSTIO_MAP_SCO_PCM:
+               return "HOSTIO_MAP_SCO_PCM";
+       case CSR_PSKEY_HOSTIO_AWKWARD_PCM_SYNC:
+               return "HOSTIO_AWKWARD_PCM_SYNC";
+       case CSR_PSKEY_HOSTIO_BREAK_POLL_PERIOD:
+               return "HOSTIO_BREAK_POLL_PERIOD";
+       case CSR_PSKEY_HOSTIO_MIN_UART_HCI_SCO_SIZE:
+               return "HOSTIO_MIN_UART_HCI_SCO_SIZE";
+       case CSR_PSKEY_HOSTIO_MAP_SCO_CODEC:
+               return "HOSTIO_MAP_SCO_CODEC";
+       case CSR_PSKEY_PCM_CVSD_TX_HI_FREQ_BOOST:
+               return "PCM_CVSD_TX_HI_FREQ_BOOST";
+       case CSR_PSKEY_PCM_CVSD_RX_HI_FREQ_BOOST:
+               return "PCM_CVSD_RX_HI_FREQ_BOOST";
+       case CSR_PSKEY_PCM_CONFIG32:
+               return "PCM_CONFIG32";
+       case CSR_PSKEY_USE_OLD_BCSP_LE:
+               return "USE_OLD_BCSP_LE";
+       case CSR_PSKEY_PCM_CVSD_USE_NEW_FILTER:
+               return "PCM_CVSD_USE_NEW_FILTER";
+       case CSR_PSKEY_PCM_FORMAT:
+               return "PCM_FORMAT";
+       case CSR_PSKEY_CODEC_OUT_GAIN:
+               return "CODEC_OUT_GAIN";
+       case CSR_PSKEY_CODEC_IN_GAIN:
+               return "CODEC_IN_GAIN";
+       case CSR_PSKEY_CODEC_PIO:
+               return "CODEC_PIO";
+       case CSR_PSKEY_PCM_LOW_JITTER_CONFIG:
+               return "PCM_LOW_JITTER_CONFIG";
+       case CSR_PSKEY_HOSTIO_SCO_PCM_THRESHOLDS:
+               return "HOSTIO_SCO_PCM_THRESHOLDS";
+       case CSR_PSKEY_HOSTIO_SCO_HCI_THRESHOLDS:
+               return "HOSTIO_SCO_HCI_THRESHOLDS";
+       case CSR_PSKEY_HOSTIO_MAP_SCO_PCM_SLOT:
+               return "HOSTIO_MAP_SCO_PCM_SLOT";
+       case CSR_PSKEY_UART_BAUDRATE:
+               return "UART_BAUDRATE";
+       case CSR_PSKEY_UART_CONFIG_BCSP:
+               return "UART_CONFIG_BCSP";
+       case CSR_PSKEY_UART_CONFIG_H4:
+               return "UART_CONFIG_H4";
+       case CSR_PSKEY_UART_CONFIG_H5:
+               return "UART_CONFIG_H5";
+       case CSR_PSKEY_UART_CONFIG_USR:
+               return "UART_CONFIG_USR";
+       case CSR_PSKEY_UART_TX_CRCS:
+               return "UART_TX_CRCS";
+       case CSR_PSKEY_UART_ACK_TIMEOUT:
+               return "UART_ACK_TIMEOUT";
+       case CSR_PSKEY_UART_TX_MAX_ATTEMPTS:
+               return "UART_TX_MAX_ATTEMPTS";
+       case CSR_PSKEY_UART_TX_WINDOW_SIZE:
+               return "UART_TX_WINDOW_SIZE";
+       case CSR_PSKEY_UART_HOST_WAKE:
+               return "UART_HOST_WAKE";
+       case CSR_PSKEY_HOSTIO_THROTTLE_TIMEOUT:
+               return "HOSTIO_THROTTLE_TIMEOUT";
+       case CSR_PSKEY_PCM_ALWAYS_ENABLE:
+               return "PCM_ALWAYS_ENABLE";
+       case CSR_PSKEY_UART_HOST_WAKE_SIGNAL:
+               return "UART_HOST_WAKE_SIGNAL";
+       case CSR_PSKEY_UART_CONFIG_H4DS:
+               return "UART_CONFIG_H4DS";
+       case CSR_PSKEY_H4DS_WAKE_DURATION:
+               return "H4DS_WAKE_DURATION";
+       case CSR_PSKEY_H4DS_MAXWU:
+               return "H4DS_MAXWU";
+       case CSR_PSKEY_H4DS_LE_TIMER_PERIOD:
+               return "H4DS_LE_TIMER_PERIOD";
+       case CSR_PSKEY_H4DS_TWU_TIMER_PERIOD:
+               return "H4DS_TWU_TIMER_PERIOD";
+       case CSR_PSKEY_H4DS_UART_IDLE_TIMER_PERIOD:
+               return "H4DS_UART_IDLE_TIMER_PERIOD";
+       case CSR_PSKEY_ANA_FTRIM:
+               return "ANA_FTRIM";
+       case CSR_PSKEY_WD_TIMEOUT:
+               return "WD_TIMEOUT";
+       case CSR_PSKEY_WD_PERIOD:
+               return "WD_PERIOD";
+       case CSR_PSKEY_HOST_INTERFACE:
+               return "HOST_INTERFACE";
+       case CSR_PSKEY_HQ_HOST_TIMEOUT:
+               return "HQ_HOST_TIMEOUT";
+       case CSR_PSKEY_HQ_ACTIVE:
+               return "HQ_ACTIVE";
+       case CSR_PSKEY_BCCMD_SECURITY_ACTIVE:
+               return "BCCMD_SECURITY_ACTIVE";
+       case CSR_PSKEY_ANA_FREQ:
+               return "ANA_FREQ";
+       case CSR_PSKEY_PIO_PROTECT_MASK:
+               return "PIO_PROTECT_MASK";
+       case CSR_PSKEY_PMALLOC_SIZES:
+               return "PMALLOC_SIZES";
+       case CSR_PSKEY_UART_BAUD_RATE:
+               return "UART_BAUD_RATE";
+       case CSR_PSKEY_UART_CONFIG:
+               return "UART_CONFIG";
+       case CSR_PSKEY_STUB:
+               return "STUB";
+       case CSR_PSKEY_TXRX_PIO_CONTROL:
+               return "TXRX_PIO_CONTROL";
+       case CSR_PSKEY_ANA_RX_LEVEL:
+               return "ANA_RX_LEVEL";
+       case CSR_PSKEY_ANA_RX_FTRIM:
+               return "ANA_RX_FTRIM";
+       case CSR_PSKEY_PSBC_DATA_VERSION:
+               return "PSBC_DATA_VERSION";
+       case CSR_PSKEY_PCM0_ATTENUATION:
+               return "PCM0_ATTENUATION";
+       case CSR_PSKEY_LO_LVL_MAX:
+               return "LO_LVL_MAX";
+       case CSR_PSKEY_LO_ADC_AMPL_MIN:
+               return "LO_ADC_AMPL_MIN";
+       case CSR_PSKEY_LO_ADC_AMPL_MAX:
+               return "LO_ADC_AMPL_MAX";
+       case CSR_PSKEY_IQ_TRIM_CHANNEL:
+               return "IQ_TRIM_CHANNEL";
+       case CSR_PSKEY_IQ_TRIM_GAIN:
+               return "IQ_TRIM_GAIN";
+       case CSR_PSKEY_IQ_TRIM_ENABLE:
+               return "IQ_TRIM_ENABLE";
+       case CSR_PSKEY_TX_OFFSET_HALF_MHZ:
+               return "TX_OFFSET_HALF_MHZ";
+       case CSR_PSKEY_GBL_MISC_ENABLES:
+               return "GBL_MISC_ENABLES";
+       case CSR_PSKEY_UART_SLEEP_TIMEOUT:
+               return "UART_SLEEP_TIMEOUT";
+       case CSR_PSKEY_DEEP_SLEEP_STATE:
+               return "DEEP_SLEEP_STATE";
+       case CSR_PSKEY_IQ_ENABLE_PHASE_TRIM:
+               return "IQ_ENABLE_PHASE_TRIM";
+       case CSR_PSKEY_HCI_HANDLE_FREEZE_PERIOD:
+               return "HCI_HANDLE_FREEZE_PERIOD";
+       case CSR_PSKEY_MAX_FROZEN_HCI_HANDLES:
+               return "MAX_FROZEN_HCI_HANDLES";
+       case CSR_PSKEY_PAGETABLE_DESTRUCTION_DELAY:
+               return "PAGETABLE_DESTRUCTION_DELAY";
+       case CSR_PSKEY_IQ_TRIM_PIO_SETTINGS:
+               return "IQ_TRIM_PIO_SETTINGS";
+       case CSR_PSKEY_USE_EXTERNAL_CLOCK:
+               return "USE_EXTERNAL_CLOCK";
+       case CSR_PSKEY_DEEP_SLEEP_WAKE_CTS:
+               return "DEEP_SLEEP_WAKE_CTS";
+       case CSR_PSKEY_FC_HC2H_FLUSH_DELAY:
+               return "FC_HC2H_FLUSH_DELAY";
+       case CSR_PSKEY_RX_HIGHSIDE:
+               return "RX_HIGHSIDE";
+       case CSR_PSKEY_TX_PRE_LVL:
+               return "TX_PRE_LVL";
+       case CSR_PSKEY_RX_SINGLE_ENDED:
+               return "RX_SINGLE_ENDED";
+       case CSR_PSKEY_TX_FILTER_CONFIG:
+               return "TX_FILTER_CONFIG";
+       case CSR_PSKEY_CLOCK_REQUEST_ENABLE:
+               return "CLOCK_REQUEST_ENABLE";
+       case CSR_PSKEY_RX_MIN_ATTEN:
+               return "RX_MIN_ATTEN";
+       case CSR_PSKEY_XTAL_TARGET_AMPLITUDE:
+               return "XTAL_TARGET_AMPLITUDE";
+       case CSR_PSKEY_PCM_MIN_CPU_CLOCK:
+               return "PCM_MIN_CPU_CLOCK";
+       case CSR_PSKEY_HOST_INTERFACE_PIO_USB:
+               return "HOST_INTERFACE_PIO_USB";
+       case CSR_PSKEY_CPU_IDLE_MODE:
+               return "CPU_IDLE_MODE";
+       case CSR_PSKEY_DEEP_SLEEP_CLEAR_RTS:
+               return "DEEP_SLEEP_CLEAR_RTS";
+       case CSR_PSKEY_RF_RESONANCE_TRIM:
+               return "RF_RESONANCE_TRIM";
+       case CSR_PSKEY_DEEP_SLEEP_PIO_WAKE:
+               return "DEEP_SLEEP_PIO_WAKE";
+       case CSR_PSKEY_DRAIN_BORE_TIMERS:
+               return "DRAIN_BORE_TIMERS";
+       case CSR_PSKEY_DRAIN_TX_POWER_BASE:
+               return "DRAIN_TX_POWER_BASE";
+       case CSR_PSKEY_MODULE_ID:
+               return "MODULE_ID";
+       case CSR_PSKEY_MODULE_DESIGN:
+               return "MODULE_DESIGN";
+       case CSR_PSKEY_MODULE_SECURITY_CODE:
+               return "MODULE_SECURITY_CODE";
+       case CSR_PSKEY_VM_DISABLE:
+               return "VM_DISABLE";
+       case CSR_PSKEY_MOD_MANUF0:
+               return "MOD_MANUF0";
+       case CSR_PSKEY_MOD_MANUF1:
+               return "MOD_MANUF1";
+       case CSR_PSKEY_MOD_MANUF2:
+               return "MOD_MANUF2";
+       case CSR_PSKEY_MOD_MANUF3:
+               return "MOD_MANUF3";
+       case CSR_PSKEY_MOD_MANUF4:
+               return "MOD_MANUF4";
+       case CSR_PSKEY_MOD_MANUF5:
+               return "MOD_MANUF5";
+       case CSR_PSKEY_MOD_MANUF6:
+               return "MOD_MANUF6";
+       case CSR_PSKEY_MOD_MANUF7:
+               return "MOD_MANUF7";
+       case CSR_PSKEY_MOD_MANUF8:
+               return "MOD_MANUF8";
+       case CSR_PSKEY_MOD_MANUF9:
+               return "MOD_MANUF9";
+       case CSR_PSKEY_DUT_VM_DISABLE:
+               return "DUT_VM_DISABLE";
+       case CSR_PSKEY_USR0:
+               return "USR0";
+       case CSR_PSKEY_USR1:
+               return "USR1";
+       case CSR_PSKEY_USR2:
+               return "USR2";
+       case CSR_PSKEY_USR3:
+               return "USR3";
+       case CSR_PSKEY_USR4:
+               return "USR4";
+       case CSR_PSKEY_USR5:
+               return "USR5";
+       case CSR_PSKEY_USR6:
+               return "USR6";
+       case CSR_PSKEY_USR7:
+               return "USR7";
+       case CSR_PSKEY_USR8:
+               return "USR8";
+       case CSR_PSKEY_USR9:
+               return "USR9";
+       case CSR_PSKEY_USR10:
+               return "USR10";
+       case CSR_PSKEY_USR11:
+               return "USR11";
+       case CSR_PSKEY_USR12:
+               return "USR12";
+       case CSR_PSKEY_USR13:
+               return "USR13";
+       case CSR_PSKEY_USR14:
+               return "USR14";
+       case CSR_PSKEY_USR15:
+               return "USR15";
+       case CSR_PSKEY_USR16:
+               return "USR16";
+       case CSR_PSKEY_USR17:
+               return "USR17";
+       case CSR_PSKEY_USR18:
+               return "USR18";
+       case CSR_PSKEY_USR19:
+               return "USR19";
+       case CSR_PSKEY_USR20:
+               return "USR20";
+       case CSR_PSKEY_USR21:
+               return "USR21";
+       case CSR_PSKEY_USR22:
+               return "USR22";
+       case CSR_PSKEY_USR23:
+               return "USR23";
+       case CSR_PSKEY_USR24:
+               return "USR24";
+       case CSR_PSKEY_USR25:
+               return "USR25";
+       case CSR_PSKEY_USR26:
+               return "USR26";
+       case CSR_PSKEY_USR27:
+               return "USR27";
+       case CSR_PSKEY_USR28:
+               return "USR28";
+       case CSR_PSKEY_USR29:
+               return "USR29";
+       case CSR_PSKEY_USR30:
+               return "USR30";
+       case CSR_PSKEY_USR31:
+               return "USR31";
+       case CSR_PSKEY_USR32:
+               return "USR32";
+       case CSR_PSKEY_USR33:
+               return "USR33";
+       case CSR_PSKEY_USR34:
+               return "USR34";
+       case CSR_PSKEY_USR35:
+               return "USR35";
+       case CSR_PSKEY_USR36:
+               return "USR36";
+       case CSR_PSKEY_USR37:
+               return "USR37";
+       case CSR_PSKEY_USR38:
+               return "USR38";
+       case CSR_PSKEY_USR39:
+               return "USR39";
+       case CSR_PSKEY_USR40:
+               return "USR40";
+       case CSR_PSKEY_USR41:
+               return "USR41";
+       case CSR_PSKEY_USR42:
+               return "USR42";
+       case CSR_PSKEY_USR43:
+               return "USR43";
+       case CSR_PSKEY_USR44:
+               return "USR44";
+       case CSR_PSKEY_USR45:
+               return "USR45";
+       case CSR_PSKEY_USR46:
+               return "USR46";
+       case CSR_PSKEY_USR47:
+               return "USR47";
+       case CSR_PSKEY_USR48:
+               return "USR48";
+       case CSR_PSKEY_USR49:
+               return "USR49";
+       case CSR_PSKEY_USB_VERSION:
+               return "USB_VERSION";
+       case CSR_PSKEY_USB_DEVICE_CLASS_CODES:
+               return "USB_DEVICE_CLASS_CODES";
+       case CSR_PSKEY_USB_VENDOR_ID:
+               return "USB_VENDOR_ID";
+       case CSR_PSKEY_USB_PRODUCT_ID:
+               return "USB_PRODUCT_ID";
+       case CSR_PSKEY_USB_MANUF_STRING:
+               return "USB_MANUF_STRING";
+       case CSR_PSKEY_USB_PRODUCT_STRING:
+               return "USB_PRODUCT_STRING";
+       case CSR_PSKEY_USB_SERIAL_NUMBER_STRING:
+               return "USB_SERIAL_NUMBER_STRING";
+       case CSR_PSKEY_USB_CONFIG_STRING:
+               return "USB_CONFIG_STRING";
+       case CSR_PSKEY_USB_ATTRIBUTES:
+               return "USB_ATTRIBUTES";
+       case CSR_PSKEY_USB_MAX_POWER:
+               return "USB_MAX_POWER";
+       case CSR_PSKEY_USB_BT_IF_CLASS_CODES:
+               return "USB_BT_IF_CLASS_CODES";
+       case CSR_PSKEY_USB_LANGID:
+               return "USB_LANGID";
+       case CSR_PSKEY_USB_DFU_CLASS_CODES:
+               return "USB_DFU_CLASS_CODES";
+       case CSR_PSKEY_USB_DFU_PRODUCT_ID:
+               return "USB_DFU_PRODUCT_ID";
+       case CSR_PSKEY_USB_PIO_DETACH:
+               return "USB_PIO_DETACH";
+       case CSR_PSKEY_USB_PIO_WAKEUP:
+               return "USB_PIO_WAKEUP";
+       case CSR_PSKEY_USB_PIO_PULLUP:
+               return "USB_PIO_PULLUP";
+       case CSR_PSKEY_USB_PIO_VBUS:
+               return "USB_PIO_VBUS";
+       case CSR_PSKEY_USB_PIO_WAKE_TIMEOUT:
+               return "USB_PIO_WAKE_TIMEOUT";
+       case CSR_PSKEY_USB_PIO_RESUME:
+               return "USB_PIO_RESUME";
+       case CSR_PSKEY_USB_BT_SCO_IF_CLASS_CODES:
+               return "USB_BT_SCO_IF_CLASS_CODES";
+       case CSR_PSKEY_USB_SUSPEND_PIO_LEVEL:
+               return "USB_SUSPEND_PIO_LEVEL";
+       case CSR_PSKEY_USB_SUSPEND_PIO_DIR:
+               return "USB_SUSPEND_PIO_DIR";
+       case CSR_PSKEY_USB_SUSPEND_PIO_MASK:
+               return "USB_SUSPEND_PIO_MASK";
+       case CSR_PSKEY_USB_ENDPOINT_0_MAX_PACKET_SIZE:
+               return "USB_ENDPOINT_0_MAX_PACKET_SIZE";
+       case CSR_PSKEY_USB_CONFIG:
+               return "USB_CONFIG";
+       case CSR_PSKEY_RADIOTEST_ATTEN_INIT:
+               return "RADIOTEST_ATTEN_INIT";
+       case CSR_PSKEY_RADIOTEST_FIRST_TRIM_TIME:
+               return "RADIOTEST_FIRST_TRIM_TIME";
+       case CSR_PSKEY_RADIOTEST_SUBSEQUENT_TRIM_TIME:
+               return "RADIOTEST_SUBSEQUENT_TRIM_TIME";
+       case CSR_PSKEY_RADIOTEST_LO_LVL_TRIM_ENABLE:
+               return "RADIOTEST_LO_LVL_TRIM_ENABLE";
+       case CSR_PSKEY_RADIOTEST_DISABLE_MODULATION:
+               return "RADIOTEST_DISABLE_MODULATION";
+       case CSR_PSKEY_RFCOMM_FCON_THRESHOLD:
+               return "RFCOMM_FCON_THRESHOLD";
+       case CSR_PSKEY_RFCOMM_FCOFF_THRESHOLD:
+               return "RFCOMM_FCOFF_THRESHOLD";
+       case CSR_PSKEY_IPV6_STATIC_ADDR:
+               return "IPV6_STATIC_ADDR";
+       case CSR_PSKEY_IPV4_STATIC_ADDR:
+               return "IPV4_STATIC_ADDR";
+       case CSR_PSKEY_IPV6_STATIC_PREFIX_LEN:
+               return "IPV6_STATIC_PREFIX_LEN";
+       case CSR_PSKEY_IPV6_STATIC_ROUTER_ADDR:
+               return "IPV6_STATIC_ROUTER_ADDR";
+       case CSR_PSKEY_IPV4_STATIC_SUBNET_MASK:
+               return "IPV4_STATIC_SUBNET_MASK";
+       case CSR_PSKEY_IPV4_STATIC_ROUTER_ADDR:
+               return "IPV4_STATIC_ROUTER_ADDR";
+       case CSR_PSKEY_MDNS_NAME:
+               return "MDNS_NAME";
+       case CSR_PSKEY_FIXED_PIN:
+               return "FIXED_PIN";
+       case CSR_PSKEY_MDNS_PORT:
+               return "MDNS_PORT";
+       case CSR_PSKEY_MDNS_TTL:
+               return "MDNS_TTL";
+       case CSR_PSKEY_MDNS_IPV4_ADDR:
+               return "MDNS_IPV4_ADDR";
+       case CSR_PSKEY_ARP_CACHE_TIMEOUT:
+               return "ARP_CACHE_TIMEOUT";
+       case CSR_PSKEY_HFP_POWER_TABLE:
+               return "HFP_POWER_TABLE";
+       case CSR_PSKEY_DRAIN_BORE_TIMER_COUNTERS:
+               return "DRAIN_BORE_TIMER_COUNTERS";
+       case CSR_PSKEY_DRAIN_BORE_COUNTERS:
+               return "DRAIN_BORE_COUNTERS";
+       case CSR_PSKEY_LOOP_FILTER_TRIM:
+               return "LOOP_FILTER_TRIM";
+       case CSR_PSKEY_DRAIN_BORE_CURRENT_PEAK:
+               return "DRAIN_BORE_CURRENT_PEAK";
+       case CSR_PSKEY_VM_E2_CACHE_LIMIT:
+               return "VM_E2_CACHE_LIMIT";
+       case CSR_PSKEY_FORCE_16MHZ_REF_PIO:
+               return "FORCE_16MHZ_REF_PIO";
+       case CSR_PSKEY_CDMA_LO_REF_LIMITS:
+               return "CDMA_LO_REF_LIMITS";
+       case CSR_PSKEY_CDMA_LO_ERROR_LIMITS:
+               return "CDMA_LO_ERROR_LIMITS";
+       case CSR_PSKEY_CLOCK_STARTUP_DELAY:
+               return "CLOCK_STARTUP_DELAY";
+       case CSR_PSKEY_DEEP_SLEEP_CORRECTION_FACTOR:
+               return "DEEP_SLEEP_CORRECTION_FACTOR";
+       case CSR_PSKEY_TEMPERATURE_CALIBRATION:
+               return "TEMPERATURE_CALIBRATION";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA:
+               return "TEMPERATURE_VS_DELTA_INTERNAL_PA";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL:
+               return "TEMPERATURE_VS_DELTA_TX_PRE_LVL";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB:
+               return "TEMPERATURE_VS_DELTA_TX_BB";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_ANA_FTRIM:
+               return "TEMPERATURE_VS_DELTA_ANA_FTRIM";
+       case CSR_PSKEY_TEST_DELTA_OFFSET:
+               return "TEST_DELTA_OFFSET";
+       case CSR_PSKEY_RX_DYNAMIC_LVL_OFFSET:
+               return "RX_DYNAMIC_LVL_OFFSET";
+       case CSR_PSKEY_TEST_FORCE_OFFSET:
+               return "TEST_FORCE_OFFSET";
+       case CSR_PSKEY_RF_TRAP_BAD_DIVISION_RATIOS:
+               return "RF_TRAP_BAD_DIVISION_RATIOS";
+       case CSR_PSKEY_RADIOTEST_CDMA_LO_REF_LIMITS:
+               return "RADIOTEST_CDMA_LO_REF_LIMITS";
+       case CSR_PSKEY_INITIAL_BOOTMODE:
+               return "INITIAL_BOOTMODE";
+       case CSR_PSKEY_ONCHIP_HCI_CLIENT:
+               return "ONCHIP_HCI_CLIENT";
+       case CSR_PSKEY_RX_ATTEN_BACKOFF:
+               return "RX_ATTEN_BACKOFF";
+       case CSR_PSKEY_RX_ATTEN_UPDATE_RATE:
+               return "RX_ATTEN_UPDATE_RATE";
+       case CSR_PSKEY_SYNTH_TXRX_THRESHOLDS:
+               return "SYNTH_TXRX_THRESHOLDS";
+       case CSR_PSKEY_MIN_WAIT_STATES:
+               return "MIN_WAIT_STATES";
+       case CSR_PSKEY_RSSI_CORRECTION:
+               return "RSSI_CORRECTION";
+       case CSR_PSKEY_SCHED_THROTTLE_TIMEOUT:
+               return "SCHED_THROTTLE_TIMEOUT";
+       case CSR_PSKEY_DEEP_SLEEP_USE_EXTERNAL_CLOCK:
+               return "DEEP_SLEEP_USE_EXTERNAL_CLOCK";
+       case CSR_PSKEY_TRIM_RADIO_FILTERS:
+               return "TRIM_RADIO_FILTERS";
+       case CSR_PSKEY_TRANSMIT_OFFSET:
+               return "TRANSMIT_OFFSET";
+       case CSR_PSKEY_USB_VM_CONTROL:
+               return "USB_VM_CONTROL";
+       case CSR_PSKEY_MR_ANA_RX_FTRIM:
+               return "MR_ANA_RX_FTRIM";
+       case CSR_PSKEY_I2C_CONFIG:
+               return "I2C_CONFIG";
+       case CSR_PSKEY_IQ_LVL_RX:
+               return "IQ_LVL_RX";
+       case CSR_PSKEY_MR_TX_FILTER_CONFIG:
+               return "MR_TX_FILTER_CONFIG";
+       case CSR_PSKEY_MR_TX_CONFIG2:
+               return "MR_TX_CONFIG2";
+       case CSR_PSKEY_USB_DONT_RESET_BOOTMODE_ON_HOST_RESET:
+               return "USB_DONT_RESET_BOOTMODE_ON_HOST_RESET";
+       case CSR_PSKEY_LC_USE_THROTTLING:
+               return "LC_USE_THROTTLING";
+       case CSR_PSKEY_CHARGER_TRIM:
+               return "CHARGER_TRIM";
+       case CSR_PSKEY_CLOCK_REQUEST_FEATURES:
+               return "CLOCK_REQUEST_FEATURES";
+       case CSR_PSKEY_TRANSMIT_OFFSET_CLASS1:
+               return "TRANSMIT_OFFSET_CLASS1";
+       case CSR_PSKEY_TX_AVOID_PA_CLASS1_PIO:
+               return "TX_AVOID_PA_CLASS1_PIO";
+       case CSR_PSKEY_MR_PIO_CONFIG:
+               return "MR_PIO_CONFIG";
+       case CSR_PSKEY_UART_CONFIG2:
+               return "UART_CONFIG2";
+       case CSR_PSKEY_CLASS1_IQ_LVL:
+               return "CLASS1_IQ_LVL";
+       case CSR_PSKEY_CLASS1_TX_CONFIG2:
+               return "CLASS1_TX_CONFIG2";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA_CLASS1:
+               return "TEMPERATURE_VS_DELTA_INTERNAL_PA_CLASS1";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_EXTERNAL_PA_CLASS1:
+               return "TEMPERATURE_VS_DELTA_EXTERNAL_PA_CLASS1";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL_MR:
+               return "TEMPERATURE_VS_DELTA_TX_PRE_LVL_MR";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_HEADER:
+               return "TEMPERATURE_VS_DELTA_TX_BB_MR_HEADER";
+       case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_PAYLOAD:
+               return "TEMPERATURE_VS_DELTA_TX_BB_MR_PAYLOAD";
+       case CSR_PSKEY_RX_MR_EQ_TAPS:
+               return "RX_MR_EQ_TAPS";
+       case CSR_PSKEY_TX_PRE_LVL_CLASS1:
+               return "TX_PRE_LVL_CLASS1";
+       case CSR_PSKEY_ANALOGUE_ATTENUATOR:
+               return "ANALOGUE_ATTENUATOR";
+       case CSR_PSKEY_MR_RX_FILTER_TRIM:
+               return "MR_RX_FILTER_TRIM";
+       case CSR_PSKEY_MR_RX_FILTER_RESPONSE:
+               return "MR_RX_FILTER_RESPONSE";
+       case CSR_PSKEY_PIO_WAKEUP_STATE:
+               return "PIO_WAKEUP_STATE";
+       case CSR_PSKEY_MR_TX_IF_ATTEN_OFF_TEMP:
+               return "MR_TX_IF_ATTEN_OFF_TEMP";
+       case CSR_PSKEY_LO_DIV_LATCH_BYPASS:
+               return "LO_DIV_LATCH_BYPASS";
+       case CSR_PSKEY_LO_VCO_STANDBY:
+               return "LO_VCO_STANDBY";
+       case CSR_PSKEY_SLOW_CLOCK_FILTER_SHIFT:
+               return "SLOW_CLOCK_FILTER_SHIFT";
+       case CSR_PSKEY_SLOW_CLOCK_FILTER_DIVIDER:
+               return "SLOW_CLOCK_FILTER_DIVIDER";
+       case CSR_PSKEY_USB_ATTRIBUTES_POWER:
+               return "USB_ATTRIBUTES_POWER";
+       case CSR_PSKEY_USB_ATTRIBUTES_WAKEUP:
+               return "USB_ATTRIBUTES_WAKEUP";
+       case CSR_PSKEY_DFU_ATTRIBUTES_MANIFESTATION_TOLERANT:
+               return "DFU_ATTRIBUTES_MANIFESTATION_TOLERANT";
+       case CSR_PSKEY_DFU_ATTRIBUTES_CAN_UPLOAD:
+               return "DFU_ATTRIBUTES_CAN_UPLOAD";
+       case CSR_PSKEY_DFU_ATTRIBUTES_CAN_DOWNLOAD:
+               return "DFU_ATTRIBUTES_CAN_DOWNLOAD";
+       case CSR_PSKEY_UART_CONFIG_STOP_BITS:
+               return "UART_CONFIG_STOP_BITS";
+       case CSR_PSKEY_UART_CONFIG_PARITY_BIT:
+               return "UART_CONFIG_PARITY_BIT";
+       case CSR_PSKEY_UART_CONFIG_FLOW_CTRL_EN:
+               return "UART_CONFIG_FLOW_CTRL_EN";
+       case CSR_PSKEY_UART_CONFIG_RTS_AUTO_EN:
+               return "UART_CONFIG_RTS_AUTO_EN";
+       case CSR_PSKEY_UART_CONFIG_RTS:
+               return "UART_CONFIG_RTS";
+       case CSR_PSKEY_UART_CONFIG_TX_ZERO_EN:
+               return "UART_CONFIG_TX_ZERO_EN";
+       case CSR_PSKEY_UART_CONFIG_NON_BCSP_EN:
+               return "UART_CONFIG_NON_BCSP_EN";
+       case CSR_PSKEY_UART_CONFIG_RX_RATE_DELAY:
+               return "UART_CONFIG_RX_RATE_DELAY";
+       case CSR_PSKEY_UART_SEQ_TIMEOUT:
+               return "UART_SEQ_TIMEOUT";
+       case CSR_PSKEY_UART_SEQ_RETRIES:
+               return "UART_SEQ_RETRIES";
+       case CSR_PSKEY_UART_SEQ_WINSIZE:
+               return "UART_SEQ_WINSIZE";
+       case CSR_PSKEY_UART_USE_CRC_ON_TX:
+               return "UART_USE_CRC_ON_TX";
+       case CSR_PSKEY_UART_HOST_INITIAL_STATE:
+               return "UART_HOST_INITIAL_STATE";
+       case CSR_PSKEY_UART_HOST_ATTENTION_SPAN:
+               return "UART_HOST_ATTENTION_SPAN";
+       case CSR_PSKEY_UART_HOST_WAKEUP_TIME:
+               return "UART_HOST_WAKEUP_TIME";
+       case CSR_PSKEY_UART_HOST_WAKEUP_WAIT:
+               return "UART_HOST_WAKEUP_WAIT";
+       case CSR_PSKEY_BCSP_LM_MODE:
+               return "BCSP_LM_MODE";
+       case CSR_PSKEY_BCSP_LM_SYNC_RETRIES:
+               return "BCSP_LM_SYNC_RETRIES";
+       case CSR_PSKEY_BCSP_LM_TSHY:
+               return "BCSP_LM_TSHY";
+       case CSR_PSKEY_UART_DFU_CONFIG_STOP_BITS:
+               return "UART_DFU_CONFIG_STOP_BITS";
+       case CSR_PSKEY_UART_DFU_CONFIG_PARITY_BIT:
+               return "UART_DFU_CONFIG_PARITY_BIT";
+       case CSR_PSKEY_UART_DFU_CONFIG_FLOW_CTRL_EN:
+               return "UART_DFU_CONFIG_FLOW_CTRL_EN";
+       case CSR_PSKEY_UART_DFU_CONFIG_RTS_AUTO_EN:
+               return "UART_DFU_CONFIG_RTS_AUTO_EN";
+       case CSR_PSKEY_UART_DFU_CONFIG_RTS:
+               return "UART_DFU_CONFIG_RTS";
+       case CSR_PSKEY_UART_DFU_CONFIG_TX_ZERO_EN:
+               return "UART_DFU_CONFIG_TX_ZERO_EN";
+       case CSR_PSKEY_UART_DFU_CONFIG_NON_BCSP_EN:
+               return "UART_DFU_CONFIG_NON_BCSP_EN";
+       case CSR_PSKEY_UART_DFU_CONFIG_RX_RATE_DELAY:
+               return "UART_DFU_CONFIG_RX_RATE_DELAY";
+       case CSR_PSKEY_AMUX_AIO0:
+               return "AMUX_AIO0";
+       case CSR_PSKEY_AMUX_AIO1:
+               return "AMUX_AIO1";
+       case CSR_PSKEY_AMUX_AIO2:
+               return "AMUX_AIO2";
+       case CSR_PSKEY_AMUX_AIO3:
+               return "AMUX_AIO3";
+       case CSR_PSKEY_LOCAL_NAME_SIMPLIFIED:
+               return "LOCAL_NAME_SIMPLIFIED";
+       case CSR_PSKEY_EXTENDED_STUB:
+               return "EXTENDED_STUB";
+       default:
+               return "UNKNOWN";
+       }
+}
+
+int csr_write_varid_valueless(int dd, uint16_t seqnum, uint16_t varid)
+{
+       unsigned char cmd[] = { 0x02, 0x00, 0x09, 0x00,
+                               seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00,
+                               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+
+       switch (varid) {
+       case CSR_VARID_COLD_RESET:
+       case CSR_VARID_WARM_RESET:
+       case CSR_VARID_COLD_HALT:
+       case CSR_VARID_WARM_HALT:
+               return hci_send_cmd(dd, OGF_VENDOR_CMD, 0x00, sizeof(cmd) + 1, cp);
+       }
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int csr_write_varid_complex(int dd, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       unsigned char cmd[] = { 0x02, 0x00, ((length / 2) + 5) & 0xff, ((length / 2) + 5) >> 8,
+                               seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00,
+                               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+       memcpy(cp + 11, value, length);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + length + 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int csr_read_varid_complex(int dd, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       unsigned char cmd[] = { 0x00, 0x00, ((length / 2) + 5) & 0xff, ((length / 2) + 5) >> 8,
+                               seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00,
+                               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+       memcpy(cp + 11, value, length);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + length + 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       memcpy(value, rp + 11, length);
+
+       return 0;
+}
+
+int csr_read_varid_uint16(int dd, uint16_t seqnum, uint16_t varid, uint16_t *value)
+{
+       unsigned char cmd[] = { 0x00, 0x00, 0x09, 0x00,
+                               seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00,
+                               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       *value = rp[11] + (rp[12] << 8);
+
+       return 0;
+}
+
+int csr_read_varid_uint32(int dd, uint16_t seqnum, uint16_t varid, uint32_t *value)
+{
+       unsigned char cmd[] = { 0x00, 0x00, 0x09, 0x00,
+                               seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00,
+                               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       *value = ((rp[11] + (rp[12] << 8)) << 16) + (rp[13] + (rp[14] << 8));
+
+       return 0;
+}
+
+int csr_read_pskey_complex(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint8_t *value, uint16_t length)
+{
+       unsigned char cmd[] = { 0x00, 0x00, ((length / 2) + 8) & 0xff, ((length / 2) + 8) >> 8,
+                               seqnum & 0xff, seqnum >> 8, 0x03, 0x70, 0x00, 0x00,
+                               pskey & 0xff, pskey >> 8,
+                               (length / 2) & 0xff, (length / 2) >> 8,
+                               stores & 0xff, stores >> 8, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + length - 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       memcpy(value, rp + 17, length);
+
+       return 0;
+}
+
+int csr_write_pskey_complex(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint8_t *value, uint16_t length)
+{
+       unsigned char cmd[] = { 0x02, 0x00, ((length / 2) + 8) & 0xff, ((length / 2) + 8) >> 8,
+                               seqnum & 0xff, seqnum >> 8, 0x03, 0x70, 0x00, 0x00,
+                               pskey & 0xff, pskey >> 8,
+                               (length / 2) & 0xff, (length / 2) >> 8,
+                               stores & 0xff, stores >> 8, 0x00, 0x00 };
+
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+
+       memset(&cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+
+       memcpy(cp + 17, value, length);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = sizeof(cmd) + length - 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+int csr_read_pskey_uint16(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint16_t *value)
+{
+       uint8_t array[2] = { 0x00, 0x00 };
+       int err;
+
+       err = csr_read_pskey_complex(dd, seqnum, pskey, stores, array, 2);
+
+       *value = array[0] + (array[1] << 8);
+
+       return err;
+}
+
+int csr_write_pskey_uint16(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint16_t value)
+{
+       uint8_t array[2] = { value & 0xff, value >> 8 };
+
+       return csr_write_pskey_complex(dd, seqnum, pskey, stores, array, 2);
+}
+
+int csr_read_pskey_uint32(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint32_t *value)
+{
+       uint8_t array[4] = { 0x00, 0x00, 0x00, 0x00 };
+       int err;
+
+       err = csr_read_pskey_complex(dd, seqnum, pskey, stores, array, 4);
+
+       *value = ((array[0] + (array[1] << 8)) << 16) +
+                                               (array[2] + (array[3] << 8));
+
+       return err;
+}
+
+int csr_write_pskey_uint32(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint32_t value)
+{
+       uint8_t array[4] = { (value & 0xff0000) >> 16, value >> 24,
+                                       value & 0xff, (value & 0xff00) >> 8 };
+
+       return csr_write_pskey_complex(dd, seqnum, pskey, stores, array, 4);
+}
+
+int psr_put(uint16_t pskey, uint8_t *value, uint16_t size)
+{
+       struct psr_data *item;
+
+       item = malloc(sizeof(*item));
+       if (!item)
+               return -ENOMEM;
+
+       item->pskey = pskey;
+
+       if (size > 0) {
+               item->value = malloc(size);
+               if (!item->value) {
+                       free(item);
+                       return -ENOMEM;
+               }
+
+               memcpy(item->value, value, size);
+               item->size = size;
+       } else {
+               item->value = NULL;
+               item->size = 0;
+       }
+
+       item->next = NULL;
+
+       if (!head)
+               head = item;
+       else
+               tail->next = item;
+
+       tail = item;
+
+       return 0;
+}
+
+int psr_get(uint16_t *pskey, uint8_t *value, uint16_t *size)
+{
+       struct psr_data *item = head;
+
+       if (!head)
+               return -ENOENT;
+
+       *pskey = item->pskey;
+
+       if (item->value) {
+               if (value && item->size > 0)
+                       memcpy(value, item->value, item->size);
+               free(item->value);
+               *size = item->size;
+       } else
+               *size = 0;
+
+       if (head == tail)
+               tail = NULL;
+
+       head = head->next;
+       free(item);
+
+       return 0;
+}
+
+static int parse_line(char *str)
+{
+       uint8_t array[256];
+       uint16_t value, pskey, length = 0;
+       char *off, *end;
+
+       pskey = strtol(str + 1, NULL, 16);
+       off = strstr(str, "=") + 1;
+       if (!off)
+               return -EIO;
+
+       while (1) {
+               value = strtol(off, &end, 16);
+               if (value == 0 && off == end)
+                       break;
+
+               array[length++] = value & 0xff;
+               array[length++] = value >> 8;
+
+               if (*end == '\0')
+                       break;
+
+               off = end + 1;
+       }
+
+       return psr_put(pskey, array, length);
+}
+
+int psr_read(const char *filename)
+{
+       struct stat st;
+       char *str, *map, *off, *end;
+       int fd, err = 0;
+
+       fd = open(filename, O_RDONLY);
+       if (fd < 0)
+               return fd;
+
+       if (fstat(fd, &st) < 0) {
+               err = -errno;
+               goto close;
+       }
+
+       map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+       if (!map || map == MAP_FAILED) {
+               err = -errno;
+               goto close;
+       }
+
+       off = map;
+
+       while (1) {
+               if (*off == '\r' || *off == '\n') {
+                       off++;
+                       continue;
+               }
+
+               end = strpbrk(off, "\r\n");
+               if (!end)
+                       break;
+
+               str = malloc(end - off + 1);
+               if (!str)
+                       break;
+
+               memset(str, 0, end - off + 1);
+               strncpy(str, off, end - off);
+               if (*str == '&')
+                       parse_line(str);
+
+               free(str);
+               off = end + 1;
+       }
+
+       munmap(map, st.st_size);
+
+close:
+       close(fd);
+
+       return err;
+}
+
+int psr_print(void)
+{
+       uint8_t array[256];
+       uint16_t pskey, length;
+       char *str, val[7];
+       int i;
+
+       while (1) {
+               if (psr_get(&pskey, array, &length) < 0)
+                       break;
+
+               str = csr_pskeytoval(pskey);
+               if (!strcasecmp(str, "UNKNOWN")) {
+                       sprintf(val, "0x%04x", pskey);
+                       str = NULL;
+               }
+
+               printf("// %s%s\n&%04x =", str ? "PSKEY_" : "",
+                                               str ? str : val, pskey);
+               for (i = 0; i < length / 2; i++)
+                       printf(" %02x%02x", array[i * 2 + 1], array[i * 2]);
+               printf("\n");
+       }
+
+       return 0;
+}
diff --git a/tools/csr.h b/tools/csr.h
new file mode 100644 (file)
index 0000000..8b94d7b
--- /dev/null
@@ -0,0 +1,553 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <stdint.h>
+#include <termios.h>
+
+#define CSR_VARID_PS_CLR_ALL                   0x000b  /* valueless */
+#define CSR_VARID_PS_FACTORY_SET               0x000c  /* valueless */
+#define CSR_VARID_PS_CLR_ALL_STORES            0x082d  /* uint16 */
+#define CSR_VARID_BC01_STATUS                  0x2801  /* uint16 */
+#define CSR_VARID_BUILDID                      0x2819  /* uint16 */
+#define CSR_VARID_CHIPVER                      0x281a  /* uint16 */
+#define CSR_VARID_CHIPREV                      0x281b  /* uint16 */
+#define CSR_VARID_INTERFACE_VERSION            0x2825  /* uint16 */
+#define CSR_VARID_RAND                         0x282a  /* uint16 */
+#define CSR_VARID_MAX_CRYPT_KEY_LENGTH         0x282c  /* uint16 */
+#define CSR_VARID_CHIPANAREV                   0x2836  /* uint16 */
+#define CSR_VARID_BUILDID_LOADER               0x2838  /* uint16 */
+#define CSR_VARID_BT_CLOCK                     0x2c00  /* uint32 */
+#define CSR_VARID_PS_NEXT                      0x3005  /* complex */
+#define CSR_VARID_PS_SIZE                      0x3006  /* complex */
+#define CSR_VARID_CRYPT_KEY_LENGTH             0x3008  /* complex */
+#define CSR_VARID_PICONET_INSTANCE             0x3009  /* complex */
+#define CSR_VARID_GET_CLR_EVT                  0x300a  /* complex */
+#define CSR_VARID_GET_NEXT_BUILDDEF            0x300b  /* complex */
+#define CSR_VARID_PS_MEMORY_TYPE               0x3012  /* complex */
+#define CSR_VARID_READ_BUILD_NAME              0x301c  /* complex */
+#define CSR_VARID_COLD_RESET                   0x4001  /* valueless */
+#define CSR_VARID_WARM_RESET                   0x4002  /* valueless */
+#define CSR_VARID_COLD_HALT                    0x4003  /* valueless */
+#define CSR_VARID_WARM_HALT                    0x4004  /* valueless */
+#define CSR_VARID_INIT_BT_STACK                        0x4005  /* valueless */
+#define CSR_VARID_ACTIVATE_BT_STACK            0x4006  /* valueless */
+#define CSR_VARID_ENABLE_TX                    0x4007  /* valueless */
+#define CSR_VARID_DISABLE_TX                   0x4008  /* valueless */
+#define CSR_VARID_RECAL                                0x4009  /* valueless */
+#define CSR_VARID_PS_FACTORY_RESTORE           0x400d  /* valueless */
+#define CSR_VARID_PS_FACTORY_RESTORE_ALL       0x400e  /* valueless */
+#define CSR_VARID_PS_DEFRAG_RESET              0x400f  /* valueless */
+#define CSR_VARID_KILL_VM_APPLICATION          0x4010  /* valueless */
+#define CSR_VARID_HOPPING_ON                   0x4011  /* valueless */
+#define CSR_VARID_CANCEL_PAGE                  0x4012  /* valueless */
+#define CSR_VARID_PS_CLR                       0x4818  /* uint16 */
+#define CSR_VARID_MAP_SCO_PCM                  0x481c  /* uint16 */
+#define CSR_VARID_SINGLE_CHAN                  0x482e  /* uint16 */
+#define CSR_VARID_RADIOTEST                    0x5004  /* complex */
+#define CSR_VARID_PS_CLR_STORES                        0x500c  /* complex */
+#define CSR_VARID_NO_VARIABLE                  0x6000  /* valueless */
+#define CSR_VARID_CONFIG_UART                  0x6802  /* uint16 */
+#define CSR_VARID_PANIC_ARG                    0x6805  /* uint16 */
+#define CSR_VARID_FAULT_ARG                    0x6806  /* uint16 */
+#define CSR_VARID_MAX_TX_POWER                 0x6827  /* int8 */
+#define CSR_VARID_DEFAULT_TX_POWER             0x682b  /* int8 */
+#define CSR_VARID_PS                           0x7003  /* complex */
+
+#define CSR_PSKEY_BDADDR                                       0x0001  /* bdaddr / uint16[] = { 0x00A5A5, 0x5b, 0x0002 } */
+#define CSR_PSKEY_COUNTRYCODE                                  0x0002  /* uint16 */
+#define CSR_PSKEY_CLASSOFDEVICE                                        0x0003  /* bdcod */
+#define CSR_PSKEY_DEVICE_DRIFT                                 0x0004  /* uint16 */
+#define CSR_PSKEY_DEVICE_JITTER                                        0x0005  /* uint16 */
+#define CSR_PSKEY_MAX_ACLS                                     0x000d  /* uint16 */
+#define CSR_PSKEY_MAX_SCOS                                     0x000e  /* uint16 */
+#define CSR_PSKEY_MAX_REMOTE_MASTERS                           0x000f  /* uint16 */
+#define CSR_PSKEY_ENABLE_MASTERY_WITH_SLAVERY                  0x0010  /* bool */
+#define CSR_PSKEY_H_HC_FC_MAX_ACL_PKT_LEN                      0x0011  /* uint16 */
+#define CSR_PSKEY_H_HC_FC_MAX_SCO_PKT_LEN                      0x0012  /* uint8 */
+#define CSR_PSKEY_H_HC_FC_MAX_ACL_PKTS                         0x0013  /* uint16 */
+#define CSR_PSKEY_H_HC_FC_MAX_SCO_PKTS                         0x0014  /* uint16 */
+#define CSR_PSKEY_LC_FC_BUFFER_LOW_WATER_MARK                  0x0015  /* lc_fc_lwm */
+#define CSR_PSKEY_LC_MAX_TX_POWER                              0x0017  /* int16 */
+#define CSR_PSKEY_TX_GAIN_RAMP                                 0x001d  /* uint16 */
+#define CSR_PSKEY_LC_POWER_TABLE                               0x001e  /* power_setting[] */
+#define CSR_PSKEY_LC_PEER_POWER_PERIOD                         0x001f  /* TIME */
+#define CSR_PSKEY_LC_FC_POOLS_LOW_WATER_MARK                   0x0020  /* lc_fc_lwm */
+#define CSR_PSKEY_LC_DEFAULT_TX_POWER                          0x0021  /* int16 */
+#define CSR_PSKEY_LC_RSSI_GOLDEN_RANGE                         0x0022  /* uint8 */
+#define CSR_PSKEY_LC_COMBO_DISABLE_PIO_MASK                    0x0028  /* uint16[] */
+#define CSR_PSKEY_LC_COMBO_PRIORITY_PIO_MASK                   0x0029  /* uint16[] */
+#define CSR_PSKEY_LC_COMBO_DOT11_CHANNEL_PIO_BASE              0x002a  /* uint16 */
+#define CSR_PSKEY_LC_COMBO_DOT11_BLOCK_CHANNELS                        0x002b  /* uint16 */
+#define CSR_PSKEY_LC_MAX_TX_POWER_NO_RSSI                      0x002d  /* int8 */
+#define CSR_PSKEY_LC_CONNECTION_RX_WINDOW                      0x002e  /* uint16 */
+#define CSR_PSKEY_LC_COMBO_DOT11_TX_PROTECTION_MODE            0x0030  /* uint16 */
+#define CSR_PSKEY_LC_ENHANCED_POWER_TABLE                      0x0031  /* enhanced_power_setting[] */
+#define CSR_PSKEY_LC_WIDEBAND_RSSI_CONFIG                      0x0032  /* wideband_rssi_config */
+#define CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_LEAD                 0x0033  /* uint16 */
+#define CSR_PSKEY_BT_CLOCK_INIT                                        0x0034  /* uint32 */
+#define CSR_PSKEY_TX_MR_MOD_DELAY                              0x0038  /* uint8 */
+#define CSR_PSKEY_RX_MR_SYNC_TIMING                            0x0039  /* uint16 */
+#define CSR_PSKEY_RX_MR_SYNC_CONFIG                            0x003a  /* uint16 */
+#define CSR_PSKEY_LC_LOST_SYNC_SLOTS                           0x003b  /* uint16 */
+#define CSR_PSKEY_RX_MR_SAMP_CONFIG                            0x003c  /* uint16 */
+#define CSR_PSKEY_AGC_HYST_LEVELS                              0x003d  /* agc_hyst_config */
+#define CSR_PSKEY_RX_LEVEL_LOW_SIGNAL                          0x003e  /* uint16 */
+#define CSR_PSKEY_AGC_IQ_LVL_VALUES                            0x003f  /* IQ_LVL_VAL[] */
+#define CSR_PSKEY_MR_FTRIM_OFFSET_12DB                         0x0040  /* uint16 */
+#define CSR_PSKEY_MR_FTRIM_OFFSET_6DB                          0x0041  /* uint16 */
+#define CSR_PSKEY_NO_CAL_ON_BOOT                               0x0042  /* bool */
+#define CSR_PSKEY_RSSI_HI_TARGET                               0x0043  /* uint8 */
+#define CSR_PSKEY_PREFERRED_MIN_ATTENUATION                    0x0044  /* uint8 */
+#define CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_OVERRIDE             0x0045  /* bool */
+#define CSR_PSKEY_LC_MULTISLOT_HOLDOFF                         0x0047  /* TIME */
+#define CSR_PSKEY_FREE_KEY_PIGEON_HOLE                         0x00c9  /* uint16 */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR0                            0x00ca  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR1                            0x00cb  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR2                            0x00cc  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR3                            0x00cd  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR4                            0x00ce  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR5                            0x00cf  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR6                            0x00d0  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR7                            0x00d1  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR8                            0x00d2  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR9                            0x00d3  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR10                           0x00d4  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR11                           0x00d5  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR12                           0x00d6  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR13                           0x00d7  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR14                           0x00d8  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LINK_KEY_BD_ADDR15                           0x00d9  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_ENC_KEY_LMIN                                 0x00da  /* uint16 */
+#define CSR_PSKEY_ENC_KEY_LMAX                                 0x00db  /* uint16 */
+#define CSR_PSKEY_LOCAL_SUPPORTED_FEATURES                     0x00ef  /* uint16[] = { 0xffff, 0xfe8f, 0xf99b, 0x8000 }*/
+#define CSR_PSKEY_LM_USE_UNIT_KEY                              0x00f0  /* bool */
+#define CSR_PSKEY_HCI_NOP_DISABLE                              0x00f2  /* bool */
+#define CSR_PSKEY_LM_MAX_EVENT_FILTERS                         0x00f4  /* uint8 */
+#define CSR_PSKEY_LM_USE_ENC_MODE_BROADCAST                    0x00f5  /* bool */
+#define CSR_PSKEY_LM_TEST_SEND_ACCEPTED_TWICE                  0x00f6  /* bool */
+#define CSR_PSKEY_LM_MAX_PAGE_HOLD_TIME                                0x00f7  /* uint16 */
+#define CSR_PSKEY_AFH_ADAPTATION_RESPONSE_TIME                 0x00f8  /* uint16 */
+#define CSR_PSKEY_AFH_OPTIONS                                  0x00f9  /* uint16 */
+#define CSR_PSKEY_AFH_RSSI_RUN_PERIOD                          0x00fa  /* uint16 */
+#define CSR_PSKEY_AFH_REENABLE_CHANNEL_TIME                    0x00fb  /* uint16 */
+#define CSR_PSKEY_NO_DROP_ON_ACR_MS_FAIL                       0x00fc  /* bool */
+#define CSR_PSKEY_MAX_PRIVATE_KEYS                             0x00fd  /* uint8 */
+#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR0                    0x00fe  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR1                    0x00ff  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR2                    0x0100  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR3                    0x0101  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR4                    0x0102  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR5                    0x0103  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR6                    0x0104  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR7                    0x0105  /* LM_LINK_KEY_BD_ADDR_T */
+#define CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS                     0x0106  /* uint16[] = { 0xffff, 0x03ff, 0xfffe, 0xffff, 0xffff, 0xffff, 0x0ff3, 0xfff8, 0x003f } */
+#define CSR_PSKEY_LM_MAX_ABSENCE_INDEX                         0x0107  /* uint8 */
+#define CSR_PSKEY_DEVICE_NAME                                  0x0108  /* uint16[] */
+#define CSR_PSKEY_AFH_RSSI_THRESHOLD                           0x0109  /* uint16 */
+#define CSR_PSKEY_LM_CASUAL_SCAN_INTERVAL                      0x010a  /* uint16 */
+#define CSR_PSKEY_AFH_MIN_MAP_CHANGE                           0x010b  /* uint16[] */
+#define CSR_PSKEY_AFH_RSSI_LP_RUN_PERIOD                       0x010c  /* uint16 */
+#define CSR_PSKEY_HCI_LMP_LOCAL_VERSION                                0x010d  /* uint16 */
+#define CSR_PSKEY_LMP_REMOTE_VERSION                           0x010e  /* uint8 */
+#define CSR_PSKEY_HOLD_ERROR_MESSAGE_NUMBER                    0x0113  /* uint16 */
+#define CSR_PSKEY_DFU_ATTRIBUTES                               0x0136  /* uint8 */
+#define CSR_PSKEY_DFU_DETACH_TO                                        0x0137  /* uint16 */
+#define CSR_PSKEY_DFU_TRANSFER_SIZE                            0x0138  /* uint16 */
+#define CSR_PSKEY_DFU_ENABLE                                   0x0139  /* bool */
+#define CSR_PSKEY_DFU_LIN_REG_ENABLE                           0x013a  /* bool */
+#define CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_MSB                  0x015e  /* uint16[] */
+#define CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_LSB                  0x015f  /* uint16[] */
+#define CSR_PSKEY_DFUENC_VMAPP_PK_M_DASH                       0x0160  /* uint16 */
+#define CSR_PSKEY_DFUENC_VMAPP_PK_R2N_MSB                      0x0161  /* uint16[] */
+#define CSR_PSKEY_DFUENC_VMAPP_PK_R2N_LSB                      0x0162  /* uint16[] */
+#define CSR_PSKEY_BCSP_LM_PS_BLOCK                             0x0192  /* BCSP_LM_PS_BLOCK */
+#define CSR_PSKEY_HOSTIO_FC_PS_BLOCK                           0x0193  /* HOSTIO_FC_PS_BLOCK */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO0                                0x0194  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO1                                0x0195  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO2                                0x0196  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO3                                0x0197  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO4                                0x0198  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO5                                0x0199  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO6                                0x019a  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO7                                0x019b  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO8                                0x019c  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO9                                0x019d  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO10                       0x019e  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO11                       0x019f  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO12                       0x01a0  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO13                       0x01a1  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO14                       0x01a2  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO15                       0x01a3  /* PROTOCOL_INFO */
+#define CSR_PSKEY_HOSTIO_UART_RESET_TIMEOUT                    0x01a4  /* TIME */
+#define CSR_PSKEY_HOSTIO_USE_HCI_EXTN                          0x01a5  /* bool */
+#define CSR_PSKEY_HOSTIO_USE_HCI_EXTN_CCFC                     0x01a6  /* bool */
+#define CSR_PSKEY_HOSTIO_HCI_EXTN_PAYLOAD_SIZE                 0x01a7  /* uint16 */
+#define CSR_PSKEY_BCSP_LM_CNF_CNT_LIMIT                                0x01aa  /* uint16 */
+#define CSR_PSKEY_HOSTIO_MAP_SCO_PCM                           0x01ab  /* bool */
+#define CSR_PSKEY_HOSTIO_AWKWARD_PCM_SYNC                      0x01ac  /* bool */
+#define CSR_PSKEY_HOSTIO_BREAK_POLL_PERIOD                     0x01ad  /* TIME */
+#define CSR_PSKEY_HOSTIO_MIN_UART_HCI_SCO_SIZE                 0x01ae  /* uint16 */
+#define CSR_PSKEY_HOSTIO_MAP_SCO_CODEC                         0x01b0  /* bool */
+#define CSR_PSKEY_PCM_CVSD_TX_HI_FREQ_BOOST                    0x01b1  /* uint16 */
+#define CSR_PSKEY_PCM_CVSD_RX_HI_FREQ_BOOST                    0x01b2  /* uint16 */
+#define CSR_PSKEY_PCM_CONFIG32                                 0x01b3  /* uint32 */
+#define CSR_PSKEY_USE_OLD_BCSP_LE                              0x01b4  /* uint16 */
+#define CSR_PSKEY_PCM_CVSD_USE_NEW_FILTER                      0x01b5  /* bool */
+#define CSR_PSKEY_PCM_FORMAT                                   0x01b6  /* uint16 */
+#define CSR_PSKEY_CODEC_OUT_GAIN                               0x01b7  /* uint16 */
+#define CSR_PSKEY_CODEC_IN_GAIN                                        0x01b8  /* uint16 */
+#define CSR_PSKEY_CODEC_PIO                                    0x01b9  /* uint16 */
+#define CSR_PSKEY_PCM_LOW_JITTER_CONFIG                                0x01ba  /* uint32 */
+#define CSR_PSKEY_HOSTIO_SCO_PCM_THRESHOLDS                    0x01bb  /* uint16[] */
+#define CSR_PSKEY_HOSTIO_SCO_HCI_THRESHOLDS                    0x01bc  /* uint16[] */
+#define CSR_PSKEY_HOSTIO_MAP_SCO_PCM_SLOT                      0x01bd  /* uint16 */
+#define CSR_PSKEY_UART_BAUDRATE                                        0x01be  /* uint16 */
+#define CSR_PSKEY_UART_CONFIG_BCSP                             0x01bf  /* uint16 */
+#define CSR_PSKEY_UART_CONFIG_H4                               0x01c0  /* uint16 */
+#define CSR_PSKEY_UART_CONFIG_H5                               0x01c1  /* uint16 */
+#define CSR_PSKEY_UART_CONFIG_USR                              0x01c2  /* uint16 */
+#define CSR_PSKEY_UART_TX_CRCS                                 0x01c3  /* bool */
+#define CSR_PSKEY_UART_ACK_TIMEOUT                             0x01c4  /* uint16 */
+#define CSR_PSKEY_UART_TX_MAX_ATTEMPTS                         0x01c5  /* uint16 */
+#define CSR_PSKEY_UART_TX_WINDOW_SIZE                          0x01c6  /* uint16 */
+#define CSR_PSKEY_UART_HOST_WAKE                               0x01c7  /* uint16[] */
+#define CSR_PSKEY_HOSTIO_THROTTLE_TIMEOUT                      0x01c8  /* TIME */
+#define CSR_PSKEY_PCM_ALWAYS_ENABLE                            0x01c9  /* bool */
+#define CSR_PSKEY_UART_HOST_WAKE_SIGNAL                                0x01ca  /* uint16 */
+#define CSR_PSKEY_UART_CONFIG_H4DS                             0x01cb  /* uint16 */
+#define CSR_PSKEY_H4DS_WAKE_DURATION                           0x01cc  /* uint16 */
+#define CSR_PSKEY_H4DS_MAXWU                                   0x01cd  /* uint16 */
+#define CSR_PSKEY_H4DS_LE_TIMER_PERIOD                         0x01cf  /* uint16 */
+#define CSR_PSKEY_H4DS_TWU_TIMER_PERIOD                                0x01d0  /* uint16 */
+#define CSR_PSKEY_H4DS_UART_IDLE_TIMER_PERIOD                  0x01d1  /* uint16 */
+#define CSR_PSKEY_ANA_FTRIM                                    0x01f6  /* uint16 */
+#define CSR_PSKEY_WD_TIMEOUT                                   0x01f7  /* TIME */
+#define CSR_PSKEY_WD_PERIOD                                    0x01f8  /* TIME */
+#define CSR_PSKEY_HOST_INTERFACE                               0x01f9  /* phys_bus */
+#define CSR_PSKEY_HQ_HOST_TIMEOUT                              0x01fb  /* TIME */
+#define CSR_PSKEY_HQ_ACTIVE                                    0x01fc  /* bool */
+#define CSR_PSKEY_BCCMD_SECURITY_ACTIVE                                0x01fd  /* bool */
+#define CSR_PSKEY_ANA_FREQ                                     0x01fe  /* uint16 */
+#define CSR_PSKEY_PIO_PROTECT_MASK                             0x0202  /* uint16 */
+#define CSR_PSKEY_PMALLOC_SIZES                                        0x0203  /* uint16[] */
+#define CSR_PSKEY_UART_BAUD_RATE                               0x0204  /* uint16 */
+#define CSR_PSKEY_UART_CONFIG                                  0x0205  /* uint16 */
+#define CSR_PSKEY_STUB                                         0x0207  /* uint16 */
+#define CSR_PSKEY_TXRX_PIO_CONTROL                             0x0209  /* uint16 */
+#define CSR_PSKEY_ANA_RX_LEVEL                                 0x020b  /* uint16 */
+#define CSR_PSKEY_ANA_RX_FTRIM                                 0x020c  /* uint16 */
+#define CSR_PSKEY_PSBC_DATA_VERSION                            0x020d  /* uint16 */
+#define CSR_PSKEY_PCM0_ATTENUATION                             0x020f  /* uint16 */
+#define CSR_PSKEY_LO_LVL_MAX                                   0x0211  /* uint16 */
+#define CSR_PSKEY_LO_ADC_AMPL_MIN                              0x0212  /* uint16 */
+#define CSR_PSKEY_LO_ADC_AMPL_MAX                              0x0213  /* uint16 */
+#define CSR_PSKEY_IQ_TRIM_CHANNEL                              0x0214  /* uint16 */
+#define CSR_PSKEY_IQ_TRIM_GAIN                                 0x0215  /* uint16 */
+#define CSR_PSKEY_IQ_TRIM_ENABLE                               0x0216  /* iq_trim_enable_flag */
+#define CSR_PSKEY_TX_OFFSET_HALF_MHZ                           0x0217  /* int16 */
+#define CSR_PSKEY_GBL_MISC_ENABLES                             0x0221  /* uint16 */
+#define CSR_PSKEY_UART_SLEEP_TIMEOUT                           0x0222  /* uint16 */
+#define CSR_PSKEY_DEEP_SLEEP_STATE                             0x0229  /* deep_sleep_state */
+#define CSR_PSKEY_IQ_ENABLE_PHASE_TRIM                         0x022d  /* bool */
+#define CSR_PSKEY_HCI_HANDLE_FREEZE_PERIOD                     0x0237  /* TIME */
+#define CSR_PSKEY_MAX_FROZEN_HCI_HANDLES                       0x0238  /* uint16 */
+#define CSR_PSKEY_PAGETABLE_DESTRUCTION_DELAY                  0x0239  /* TIME */
+#define CSR_PSKEY_IQ_TRIM_PIO_SETTINGS                         0x023a  /* uint8 */
+#define CSR_PSKEY_USE_EXTERNAL_CLOCK                           0x023b  /* bool */
+#define CSR_PSKEY_DEEP_SLEEP_WAKE_CTS                          0x023c  /* uint16 */
+#define CSR_PSKEY_FC_HC2H_FLUSH_DELAY                          0x023d  /* TIME */
+#define CSR_PSKEY_RX_HIGHSIDE                                  0x023e  /* bool */
+#define CSR_PSKEY_TX_PRE_LVL                                   0x0240  /* uint8 */
+#define CSR_PSKEY_RX_SINGLE_ENDED                              0x0242  /* bool */
+#define CSR_PSKEY_TX_FILTER_CONFIG                             0x0243  /* uint32 */
+#define CSR_PSKEY_CLOCK_REQUEST_ENABLE                         0x0246  /* uint16 */
+#define CSR_PSKEY_RX_MIN_ATTEN                                 0x0249  /* uint16 */
+#define CSR_PSKEY_XTAL_TARGET_AMPLITUDE                                0x024b  /* uint8 */
+#define CSR_PSKEY_PCM_MIN_CPU_CLOCK                            0x024d  /* uint16 */
+#define CSR_PSKEY_HOST_INTERFACE_PIO_USB                       0x0250  /* uint16 */
+#define CSR_PSKEY_CPU_IDLE_MODE                                        0x0251  /* cpu_idle_mode */
+#define CSR_PSKEY_DEEP_SLEEP_CLEAR_RTS                         0x0252  /* bool */
+#define CSR_PSKEY_RF_RESONANCE_TRIM                            0x0254  /* uint16 */
+#define CSR_PSKEY_DEEP_SLEEP_PIO_WAKE                          0x0255  /* uint16 */
+#define CSR_PSKEY_DRAIN_BORE_TIMERS                            0x0256  /* uint32[] */
+#define CSR_PSKEY_DRAIN_TX_POWER_BASE                          0x0257  /* uint16 */
+#define CSR_PSKEY_MODULE_ID                                    0x0259  /* uint32 */
+#define CSR_PSKEY_MODULE_DESIGN                                        0x025a  /* uint16 */
+#define CSR_PSKEY_MODULE_SECURITY_CODE                         0x025c  /* uint16[] */
+#define CSR_PSKEY_VM_DISABLE                                   0x025d  /* bool */
+#define CSR_PSKEY_MOD_MANUF0                                   0x025e  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF1                                   0x025f  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF2                                   0x0260  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF3                                   0x0261  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF4                                   0x0262  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF5                                   0x0263  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF6                                   0x0264  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF7                                   0x0265  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF8                                   0x0266  /* uint16[] */
+#define CSR_PSKEY_MOD_MANUF9                                   0x0267  /* uint16[] */
+#define CSR_PSKEY_DUT_VM_DISABLE                               0x0268  /* bool */
+#define CSR_PSKEY_USR0                                         0x028a  /* uint16[] */
+#define CSR_PSKEY_USR1                                         0x028b  /* uint16[] */
+#define CSR_PSKEY_USR2                                         0x028c  /* uint16[] */
+#define CSR_PSKEY_USR3                                         0x028d  /* uint16[] */
+#define CSR_PSKEY_USR4                                         0x028e  /* uint16[] */
+#define CSR_PSKEY_USR5                                         0x028f  /* uint16[] */
+#define CSR_PSKEY_USR6                                         0x0290  /* uint16[] */
+#define CSR_PSKEY_USR7                                         0x0291  /* uint16[] */
+#define CSR_PSKEY_USR8                                         0x0292  /* uint16[] */
+#define CSR_PSKEY_USR9                                         0x0293  /* uint16[] */
+#define CSR_PSKEY_USR10                                                0x0294  /* uint16[] */
+#define CSR_PSKEY_USR11                                                0x0295  /* uint16[] */
+#define CSR_PSKEY_USR12                                                0x0296  /* uint16[] */
+#define CSR_PSKEY_USR13                                                0x0297  /* uint16[] */
+#define CSR_PSKEY_USR14                                                0x0298  /* uint16[] */
+#define CSR_PSKEY_USR15                                                0x0299  /* uint16[] */
+#define CSR_PSKEY_USR16                                                0x029a  /* uint16[] */
+#define CSR_PSKEY_USR17                                                0x029b  /* uint16[] */
+#define CSR_PSKEY_USR18                                                0x029c  /* uint16[] */
+#define CSR_PSKEY_USR19                                                0x029d  /* uint16[] */
+#define CSR_PSKEY_USR20                                                0x029e  /* uint16[] */
+#define CSR_PSKEY_USR21                                                0x029f  /* uint16[] */
+#define CSR_PSKEY_USR22                                                0x02a0  /* uint16[] */
+#define CSR_PSKEY_USR23                                                0x02a1  /* uint16[] */
+#define CSR_PSKEY_USR24                                                0x02a2  /* uint16[] */
+#define CSR_PSKEY_USR25                                                0x02a3  /* uint16[] */
+#define CSR_PSKEY_USR26                                                0x02a4  /* uint16[] */
+#define CSR_PSKEY_USR27                                                0x02a5  /* uint16[] */
+#define CSR_PSKEY_USR28                                                0x02a6  /* uint16[] */
+#define CSR_PSKEY_USR29                                                0x02a7  /* uint16[] */
+#define CSR_PSKEY_USR30                                                0x02a8  /* uint16[] */
+#define CSR_PSKEY_USR31                                                0x02a9  /* uint16[] */
+#define CSR_PSKEY_USR32                                                0x02aa  /* uint16[] */
+#define CSR_PSKEY_USR33                                                0x02ab  /* uint16[] */
+#define CSR_PSKEY_USR34                                                0x02ac  /* uint16[] */
+#define CSR_PSKEY_USR35                                                0x02ad  /* uint16[] */
+#define CSR_PSKEY_USR36                                                0x02ae  /* uint16[] */
+#define CSR_PSKEY_USR37                                                0x02af  /* uint16[] */
+#define CSR_PSKEY_USR38                                                0x02b0  /* uint16[] */
+#define CSR_PSKEY_USR39                                                0x02b1  /* uint16[] */
+#define CSR_PSKEY_USR40                                                0x02b2  /* uint16[] */
+#define CSR_PSKEY_USR41                                                0x02b3  /* uint16[] */
+#define CSR_PSKEY_USR42                                                0x02b4  /* uint16[] */
+#define CSR_PSKEY_USR43                                                0x02b5  /* uint16[] */
+#define CSR_PSKEY_USR44                                                0x02b6  /* uint16[] */
+#define CSR_PSKEY_USR45                                                0x02b7  /* uint16[] */
+#define CSR_PSKEY_USR46                                                0x02b8  /* uint16[] */
+#define CSR_PSKEY_USR47                                                0x02b9  /* uint16[] */
+#define CSR_PSKEY_USR48                                                0x02ba  /* uint16[] */
+#define CSR_PSKEY_USR49                                                0x02bb  /* uint16[] */
+#define CSR_PSKEY_USB_VERSION                                  0x02bc  /* uint16 */
+#define CSR_PSKEY_USB_DEVICE_CLASS_CODES                       0x02bd  /* usbclass */
+#define CSR_PSKEY_USB_VENDOR_ID                                        0x02be  /* uint16 */
+#define CSR_PSKEY_USB_PRODUCT_ID                               0x02bf  /* uint16 */
+#define CSR_PSKEY_USB_MANUF_STRING                             0x02c1  /* unicodestring */
+#define CSR_PSKEY_USB_PRODUCT_STRING                           0x02c2  /* unicodestring */
+#define CSR_PSKEY_USB_SERIAL_NUMBER_STRING                     0x02c3  /* unicodestring */
+#define CSR_PSKEY_USB_CONFIG_STRING                            0x02c4  /* unicodestring */
+#define CSR_PSKEY_USB_ATTRIBUTES                               0x02c5  /* uint8 */
+#define CSR_PSKEY_USB_MAX_POWER                                        0x02c6  /* uint16 */
+#define CSR_PSKEY_USB_BT_IF_CLASS_CODES                                0x02c7  /* usbclass */
+#define CSR_PSKEY_USB_LANGID                                   0x02c9  /* uint16 */
+#define CSR_PSKEY_USB_DFU_CLASS_CODES                          0x02ca  /* usbclass */
+#define CSR_PSKEY_USB_DFU_PRODUCT_ID                           0x02cb  /* uint16 */
+#define CSR_PSKEY_USB_PIO_DETACH                               0x02ce  /* uint16 */
+#define CSR_PSKEY_USB_PIO_WAKEUP                               0x02cf  /* uint16 */
+#define CSR_PSKEY_USB_PIO_PULLUP                               0x02d0  /* uint16 */
+#define CSR_PSKEY_USB_PIO_VBUS                                 0x02d1  /* uint16 */
+#define CSR_PSKEY_USB_PIO_WAKE_TIMEOUT                         0x02d2  /* uint16 */
+#define CSR_PSKEY_USB_PIO_RESUME                               0x02d3  /* uint16 */
+#define CSR_PSKEY_USB_BT_SCO_IF_CLASS_CODES                    0x02d4  /* usbclass */
+#define CSR_PSKEY_USB_SUSPEND_PIO_LEVEL                                0x02d5  /* uint16 */
+#define CSR_PSKEY_USB_SUSPEND_PIO_DIR                          0x02d6  /* uint16 */
+#define CSR_PSKEY_USB_SUSPEND_PIO_MASK                         0x02d7  /* uint16 */
+#define CSR_PSKEY_USB_ENDPOINT_0_MAX_PACKET_SIZE               0x02d8  /* uint8 */
+#define CSR_PSKEY_USB_CONFIG                                   0x02d9  /* uint16 */
+#define CSR_PSKEY_RADIOTEST_ATTEN_INIT                         0x0320  /* uint16 */
+#define CSR_PSKEY_RADIOTEST_FIRST_TRIM_TIME                    0x0326  /* TIME */
+#define CSR_PSKEY_RADIOTEST_SUBSEQUENT_TRIM_TIME               0x0327  /* TIME */
+#define CSR_PSKEY_RADIOTEST_LO_LVL_TRIM_ENABLE                 0x0328  /* bool */
+#define CSR_PSKEY_RADIOTEST_DISABLE_MODULATION                 0x032c  /* bool */
+#define CSR_PSKEY_RFCOMM_FCON_THRESHOLD                                0x0352  /* uint16 */
+#define CSR_PSKEY_RFCOMM_FCOFF_THRESHOLD                       0x0353  /* uint16 */
+#define CSR_PSKEY_IPV6_STATIC_ADDR                             0x0354  /* uint16[] */
+#define CSR_PSKEY_IPV4_STATIC_ADDR                             0x0355  /* uint32 */
+#define CSR_PSKEY_IPV6_STATIC_PREFIX_LEN                       0x0356  /* uint8 */
+#define CSR_PSKEY_IPV6_STATIC_ROUTER_ADDR                      0x0357  /* uint16[] */
+#define CSR_PSKEY_IPV4_STATIC_SUBNET_MASK                      0x0358  /* uint32 */
+#define CSR_PSKEY_IPV4_STATIC_ROUTER_ADDR                      0x0359  /* uint32 */
+#define CSR_PSKEY_MDNS_NAME                                    0x035a  /* char[] */
+#define CSR_PSKEY_FIXED_PIN                                    0x035b  /* uint8[] */
+#define CSR_PSKEY_MDNS_PORT                                    0x035c  /* uint16 */
+#define CSR_PSKEY_MDNS_TTL                                     0x035d  /* uint8 */
+#define CSR_PSKEY_MDNS_IPV4_ADDR                               0x035e  /* uint32 */
+#define CSR_PSKEY_ARP_CACHE_TIMEOUT                            0x035f  /* uint16 */
+#define CSR_PSKEY_HFP_POWER_TABLE                              0x0360  /* uint16[] */
+#define CSR_PSKEY_DRAIN_BORE_TIMER_COUNTERS                    0x03e7  /* uint32[] */
+#define CSR_PSKEY_DRAIN_BORE_COUNTERS                          0x03e6  /* uint32[] */
+#define CSR_PSKEY_LOOP_FILTER_TRIM                             0x03e4  /* uint16 */
+#define CSR_PSKEY_DRAIN_BORE_CURRENT_PEAK                      0x03e3  /* uint32[] */
+#define CSR_PSKEY_VM_E2_CACHE_LIMIT                            0x03e2  /* uint16 */
+#define CSR_PSKEY_FORCE_16MHZ_REF_PIO                          0x03e1  /* uint16 */
+#define CSR_PSKEY_CDMA_LO_REF_LIMITS                           0x03df  /* uint16 */
+#define CSR_PSKEY_CDMA_LO_ERROR_LIMITS                         0x03de  /* uint16 */
+#define CSR_PSKEY_CLOCK_STARTUP_DELAY                          0x03dd  /* uint16 */
+#define CSR_PSKEY_DEEP_SLEEP_CORRECTION_FACTOR                 0x03dc  /* int16 */
+#define CSR_PSKEY_TEMPERATURE_CALIBRATION                      0x03db  /* temperature_calibration */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA             0x03da  /* temperature_calibration[] */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL              0x03d9  /* temperature_calibration[] */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB                   0x03d8  /* temperature_calibration[] */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_ANA_FTRIM               0x03d7  /* temperature_calibration[] */
+#define CSR_PSKEY_TEST_DELTA_OFFSET                            0x03d6  /* uint16 */
+#define CSR_PSKEY_RX_DYNAMIC_LVL_OFFSET                                0x03d4  /* uint16 */
+#define CSR_PSKEY_TEST_FORCE_OFFSET                            0x03d3  /* bool */
+#define CSR_PSKEY_RF_TRAP_BAD_DIVISION_RATIOS                  0x03cf  /* uint16 */
+#define CSR_PSKEY_RADIOTEST_CDMA_LO_REF_LIMITS                 0x03ce  /* uint16 */
+#define CSR_PSKEY_INITIAL_BOOTMODE                             0x03cd  /* int16 */
+#define CSR_PSKEY_ONCHIP_HCI_CLIENT                            0x03cc  /* bool */
+#define CSR_PSKEY_RX_ATTEN_BACKOFF                             0x03ca  /* uint16 */
+#define CSR_PSKEY_RX_ATTEN_UPDATE_RATE                         0x03c9  /* uint16 */
+#define CSR_PSKEY_SYNTH_TXRX_THRESHOLDS                                0x03c7  /* uint16 */
+#define CSR_PSKEY_MIN_WAIT_STATES                              0x03c6  /* uint16 */
+#define CSR_PSKEY_RSSI_CORRECTION                              0x03c5  /* int8 */
+#define CSR_PSKEY_SCHED_THROTTLE_TIMEOUT                       0x03c4  /* TIME */
+#define CSR_PSKEY_DEEP_SLEEP_USE_EXTERNAL_CLOCK                        0x03c3  /* bool */
+#define CSR_PSKEY_TRIM_RADIO_FILTERS                           0x03c2  /* uint16 */
+#define CSR_PSKEY_TRANSMIT_OFFSET                              0x03c1  /* int16 */
+#define CSR_PSKEY_USB_VM_CONTROL                               0x03c0  /* bool */
+#define CSR_PSKEY_MR_ANA_RX_FTRIM                              0x03bf  /* uint16 */
+#define CSR_PSKEY_I2C_CONFIG                                   0x03be  /* uint16 */
+#define CSR_PSKEY_IQ_LVL_RX                                    0x03bd  /* uint16 */
+#define CSR_PSKEY_MR_TX_FILTER_CONFIG                          0x03bb  /* uint32 */
+#define CSR_PSKEY_MR_TX_CONFIG2                                        0x03ba  /* uint16 */
+#define CSR_PSKEY_USB_DONT_RESET_BOOTMODE_ON_HOST_RESET                0x03b9  /* bool */
+#define CSR_PSKEY_LC_USE_THROTTLING                            0x03b8  /* bool */
+#define CSR_PSKEY_CHARGER_TRIM                                 0x03b7  /* uint16 */
+#define CSR_PSKEY_CLOCK_REQUEST_FEATURES                       0x03b6  /* uint16 */
+#define CSR_PSKEY_TRANSMIT_OFFSET_CLASS1                       0x03b4  /* int16 */
+#define CSR_PSKEY_TX_AVOID_PA_CLASS1_PIO                       0x03b3  /* uint16 */
+#define CSR_PSKEY_MR_PIO_CONFIG                                        0x03b2  /* uint16 */
+#define CSR_PSKEY_UART_CONFIG2                                 0x03b1  /* uint8 */
+#define CSR_PSKEY_CLASS1_IQ_LVL                                        0x03b0  /* uint16 */
+#define CSR_PSKEY_CLASS1_TX_CONFIG2                            0x03af  /* uint16 */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA_CLASS1      0x03ae  /* temperature_calibration[] */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_EXTERNAL_PA_CLASS1      0x03ad  /* temperature_calibration[] */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL_MR           0x03ac  /* temperature_calibration[] */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_HEADER         0x03ab  /* temperature_calibration[] */
+#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_PAYLOAD                0x03aa  /* temperature_calibration[] */
+#define CSR_PSKEY_RX_MR_EQ_TAPS                                        0x03a9  /* uint16[] */
+#define CSR_PSKEY_TX_PRE_LVL_CLASS1                            0x03a8  /* uint8 */
+#define CSR_PSKEY_ANALOGUE_ATTENUATOR                          0x03a7  /* bool */
+#define CSR_PSKEY_MR_RX_FILTER_TRIM                            0x03a6  /* uint16 */
+#define CSR_PSKEY_MR_RX_FILTER_RESPONSE                                0x03a5  /* int16[] */
+#define CSR_PSKEY_PIO_WAKEUP_STATE                             0x039f  /* uint16 */
+#define CSR_PSKEY_MR_TX_IF_ATTEN_OFF_TEMP                      0x0394  /* int16 */
+#define CSR_PSKEY_LO_DIV_LATCH_BYPASS                          0x0393  /* bool */
+#define CSR_PSKEY_LO_VCO_STANDBY                               0x0392  /* bool */
+#define CSR_PSKEY_SLOW_CLOCK_FILTER_SHIFT                      0x0391  /* uint16 */
+#define CSR_PSKEY_SLOW_CLOCK_FILTER_DIVIDER                    0x0390  /* uint16 */
+#define CSR_PSKEY_USB_ATTRIBUTES_POWER                         0x03f2  /* bool */
+#define CSR_PSKEY_USB_ATTRIBUTES_WAKEUP                                0x03f3  /* bool */
+#define CSR_PSKEY_DFU_ATTRIBUTES_MANIFESTATION_TOLERANT                0x03f4  /* bool */
+#define CSR_PSKEY_DFU_ATTRIBUTES_CAN_UPLOAD                    0x03f5  /* bool */
+#define CSR_PSKEY_DFU_ATTRIBUTES_CAN_DOWNLOAD                  0x03f6  /* bool */
+#define CSR_PSKEY_UART_CONFIG_STOP_BITS                                0x03fc  /* bool */
+#define CSR_PSKEY_UART_CONFIG_PARITY_BIT                       0x03fd  /* uint16 */
+#define CSR_PSKEY_UART_CONFIG_FLOW_CTRL_EN                     0x03fe  /* bool */
+#define CSR_PSKEY_UART_CONFIG_RTS_AUTO_EN                      0x03ff  /* bool */
+#define CSR_PSKEY_UART_CONFIG_RTS                              0x0400  /* bool */
+#define CSR_PSKEY_UART_CONFIG_TX_ZERO_EN                       0x0401  /* bool */
+#define CSR_PSKEY_UART_CONFIG_NON_BCSP_EN                      0x0402  /* bool */
+#define CSR_PSKEY_UART_CONFIG_RX_RATE_DELAY                    0x0403  /* uint16 */
+#define CSR_PSKEY_UART_SEQ_TIMEOUT                             0x0405  /* uint16 */
+#define CSR_PSKEY_UART_SEQ_RETRIES                             0x0406  /* uint16 */
+#define CSR_PSKEY_UART_SEQ_WINSIZE                             0x0407  /* uint16 */
+#define CSR_PSKEY_UART_USE_CRC_ON_TX                           0x0408  /* bool */
+#define CSR_PSKEY_UART_HOST_INITIAL_STATE                      0x0409  /* hwakeup_state */
+#define CSR_PSKEY_UART_HOST_ATTENTION_SPAN                     0x040a  /* uint16 */
+#define CSR_PSKEY_UART_HOST_WAKEUP_TIME                                0x040b  /* uint16 */
+#define CSR_PSKEY_UART_HOST_WAKEUP_WAIT                                0x040c  /* uint16 */
+#define CSR_PSKEY_BCSP_LM_MODE                                 0x0410  /* uint16 */
+#define CSR_PSKEY_BCSP_LM_SYNC_RETRIES                         0x0411  /* uint16 */
+#define CSR_PSKEY_BCSP_LM_TSHY                                 0x0412  /* uint16 */
+#define CSR_PSKEY_UART_DFU_CONFIG_STOP_BITS                    0x0417  /* bool */
+#define CSR_PSKEY_UART_DFU_CONFIG_PARITY_BIT                   0x0418  /* uint16 */
+#define CSR_PSKEY_UART_DFU_CONFIG_FLOW_CTRL_EN                 0x0419  /* bool */
+#define CSR_PSKEY_UART_DFU_CONFIG_RTS_AUTO_EN                  0x041a  /* bool */
+#define CSR_PSKEY_UART_DFU_CONFIG_RTS                          0x041b  /* bool */
+#define CSR_PSKEY_UART_DFU_CONFIG_TX_ZERO_EN                   0x041c  /* bool */
+#define CSR_PSKEY_UART_DFU_CONFIG_NON_BCSP_EN                  0x041d  /* bool */
+#define CSR_PSKEY_UART_DFU_CONFIG_RX_RATE_DELAY                        0x041e  /* uint16 */
+#define CSR_PSKEY_AMUX_AIO0                                    0x041f  /* ana_amux_sel */
+#define CSR_PSKEY_AMUX_AIO1                                    0x0420  /* ana_amux_sel */
+#define CSR_PSKEY_AMUX_AIO2                                    0x0421  /* ana_amux_sel */
+#define CSR_PSKEY_AMUX_AIO3                                    0x0422  /* ana_amux_sel */
+#define CSR_PSKEY_LOCAL_NAME_SIMPLIFIED                                0x0423  /* local_name_complete */
+#define CSR_PSKEY_EXTENDED_STUB                                        0x2001  /* uint16 */
+
+char *csr_builddeftostr(uint16_t def);
+char *csr_buildidtostr(uint16_t id);
+char *csr_chipvertostr(uint16_t ver, uint16_t rev);
+char *csr_pskeytostr(uint16_t pskey);
+char *csr_pskeytoval(uint16_t pskey);
+
+int csr_open_hci(char *device);
+int csr_read_hci(uint16_t varid, uint8_t *value, uint16_t length);
+int csr_write_hci(uint16_t varid, uint8_t *value, uint16_t length);
+void csr_close_hci(void);
+
+int csr_open_usb(char *device);
+int csr_read_usb(uint16_t varid, uint8_t *value, uint16_t length);
+int csr_write_usb(uint16_t varid, uint8_t *value, uint16_t length);
+void csr_close_usb(void);
+
+int csr_open_bcsp(char *device, speed_t bcsp_rate);
+int csr_read_bcsp(uint16_t varid, uint8_t *value, uint16_t length);
+int csr_write_bcsp(uint16_t varid, uint8_t *value, uint16_t length);
+void csr_close_bcsp(void);
+
+int csr_open_h4(char *device);
+int csr_read_h4(uint16_t varid, uint8_t *value, uint16_t length);
+int csr_write_h4(uint16_t varid, uint8_t *value, uint16_t length);
+void csr_close_h4(void);
+
+int csr_open_3wire(char *device);
+int csr_read_3wire(uint16_t varid, uint8_t *value, uint16_t length);
+int csr_write_3wire(uint16_t varid, uint8_t *value, uint16_t length);
+void csr_close_3wire(void);
+
+int csr_write_varid_valueless(int dd, uint16_t seqnum, uint16_t varid);
+int csr_write_varid_complex(int dd, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length);
+int csr_read_varid_complex(int dd, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length);
+int csr_read_varid_uint16(int dd, uint16_t seqnum, uint16_t varid, uint16_t *value);
+int csr_read_varid_uint32(int dd, uint16_t seqnum, uint16_t varid, uint32_t *value);
+int csr_read_pskey_complex(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint8_t *value, uint16_t length);
+int csr_write_pskey_complex(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint8_t *value, uint16_t length);
+int csr_read_pskey_uint16(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint16_t *value);
+int csr_write_pskey_uint16(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint16_t value);
+int csr_read_pskey_uint32(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint32_t *value);
+int csr_write_pskey_uint32(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint32_t value);
+
+int psr_put(uint16_t pskey, uint8_t *value, uint16_t size);
+int psr_get(uint16_t *pskey, uint8_t *value, uint16_t *size);
+int psr_read(const char *filename);
+int psr_print(void);
diff --git a/tools/csr_3wire.c b/tools/csr_3wire.c
new file mode 100644 (file)
index 0000000..33fcf38
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include "csr.h"
+
+static uint16_t seqnum = 0x0000;
+
+int csr_open_3wire(char *device)
+{
+       fprintf(stderr, "Transport not implemented\n");
+
+       return -1;
+}
+
+static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       errno = EIO;
+
+       return -1;
+}
+
+int csr_read_3wire(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0000, seqnum++, varid, value, length);
+}
+
+int csr_write_3wire(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0002, seqnum++, varid, value, length);
+}
+
+void csr_close_3wire(void)
+{
+}
diff --git a/tools/csr_bcsp.c b/tools/csr_bcsp.c
new file mode 100644 (file)
index 0000000..df247a2
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <termios.h>
+
+#include "csr.h"
+#include "ubcsp.h"
+
+static uint16_t seqnum = 0x0000;
+
+static int fd = -1;
+
+static struct ubcsp_packet send_packet;
+static uint8_t send_buffer[512];
+
+static struct ubcsp_packet receive_packet;
+static uint8_t receive_buffer[512];
+
+int csr_open_bcsp(char *device, speed_t bcsp_rate)
+{
+       struct termios ti;
+       uint8_t delay, activity = 0x00;
+       int timeout = 0;
+
+       if (!device)
+               device = "/dev/ttyS0";
+
+       fd = open(device, O_RDWR | O_NOCTTY);
+       if (fd < 0) {
+               fprintf(stderr, "Can't open serial port: %s (%d)\n",
+                                               strerror(errno), errno);
+               return -1;
+       }
+
+       tcflush(fd, TCIOFLUSH);
+
+       if (tcgetattr(fd, &ti) < 0) {
+               fprintf(stderr, "Can't get port settings: %s (%d)\n",
+                                               strerror(errno), errno);
+               close(fd);
+               return -1;
+       }
+
+       cfmakeraw(&ti);
+
+       ti.c_cflag |=  CLOCAL;
+       ti.c_cflag &= ~CRTSCTS;
+       ti.c_cflag |=  PARENB;
+       ti.c_cflag &= ~PARODD;
+       ti.c_cflag &= ~CSIZE;
+       ti.c_cflag |=  CS8;
+       ti.c_cflag &= ~CSTOPB;
+
+       ti.c_cc[VMIN] = 1;
+       ti.c_cc[VTIME] = 0;
+
+       cfsetospeed(&ti, bcsp_rate);
+
+       if (tcsetattr(fd, TCSANOW, &ti) < 0) {
+               fprintf(stderr, "Can't change port settings: %s (%d)\n",
+                                               strerror(errno), errno);
+               close(fd);
+               return -1;
+       }
+
+       tcflush(fd, TCIOFLUSH);
+
+       if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) {
+               fprintf(stderr, "Can't set non blocking mode: %s (%d)\n",
+                                               strerror(errno), errno);
+               close(fd);
+               return -1;
+       }
+
+       memset(&send_packet, 0, sizeof(send_packet));
+       memset(&receive_packet, 0, sizeof(receive_packet));
+
+       ubcsp_initialize();
+
+       send_packet.length = 512;
+       send_packet.payload = send_buffer;
+
+       receive_packet.length = 512;
+       receive_packet.payload = receive_buffer;
+
+       ubcsp_receive_packet(&receive_packet);
+
+       while (1) {
+               delay = ubcsp_poll(&activity);
+
+               if (activity & UBCSP_PACKET_RECEIVED)
+                       break;
+
+               if (delay) {
+                       usleep(delay * 100);
+
+                       if (timeout++ > 5000) {
+                               fprintf(stderr, "Initialization timed out\n");
+                               return -1;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+void put_uart(uint8_t ch)
+{
+       if (write(fd, &ch, 1) < 0)
+               fprintf(stderr, "UART write error\n");
+}
+
+uint8_t get_uart(uint8_t *ch)
+{
+       int res = read(fd, ch, 1);
+       return res > 0 ? res : 0;
+}
+
+static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       unsigned char cp[254], rp[254];
+       uint8_t cmd[10];
+       uint16_t size;
+       uint8_t delay, activity = 0x00;
+       int timeout = 0, sent = 0;
+
+       size = (length < 8) ? 9 : ((length + 1) / 2) + 5;
+
+       cmd[0] = command & 0xff;
+       cmd[1] = command >> 8;
+       cmd[2] = size & 0xff;
+       cmd[3] = size >> 8;
+       cmd[4] = seqnum & 0xff;
+       cmd[5] = seqnum >> 8;
+       cmd[6] = varid & 0xff;
+       cmd[7] = varid >> 8;
+       cmd[8] = 0x00;
+       cmd[9] = 0x00;
+
+       memset(cp, 0, sizeof(cp));
+       cp[0] = 0x00;
+       cp[1] = 0xfc;
+       cp[2] = (size * 2) + 1;
+       cp[3] = 0xc2;
+       memcpy(cp + 4, cmd, sizeof(cmd));
+       memcpy(cp + 14, value, length);
+
+       receive_packet.length = 512;
+       ubcsp_receive_packet(&receive_packet);
+
+       send_packet.channel  = 5;
+       send_packet.reliable = 1;
+       send_packet.length   = (size * 2) + 4;
+       memcpy(send_packet.payload, cp, (size * 2) + 4);
+
+       ubcsp_send_packet(&send_packet);
+
+       while (1) {
+               delay = ubcsp_poll(&activity);
+
+               if (activity & UBCSP_PACKET_SENT) {
+                       switch (varid) {
+                       case CSR_VARID_COLD_RESET:
+                       case CSR_VARID_WARM_RESET:
+                       case CSR_VARID_COLD_HALT:
+                       case CSR_VARID_WARM_HALT:
+                               return 0;
+                       }
+
+                       sent = 1;
+                       timeout = 0;
+               }
+
+               if (activity & UBCSP_PACKET_RECEIVED) {
+                       if (sent && receive_packet.channel == 5 &&
+                                       receive_packet.payload[0] == 0xff) {
+                               memcpy(rp, receive_packet.payload,
+                                                       receive_packet.length);
+                               break;
+                       }
+
+                       receive_packet.length = 512;
+                       ubcsp_receive_packet(&receive_packet);
+                       timeout = 0;
+               }
+
+               if (delay) {
+                       usleep(delay * 100);
+
+                       if (timeout++ > 5000) {
+                               fprintf(stderr, "Operation timed out\n");
+                               return -1;
+                       }
+               }
+       }
+
+       if (rp[0] != 0xff || rp[2] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[11] + (rp[12] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       memcpy(value, rp + 13, length);
+
+       return 0;
+}
+
+int csr_read_bcsp(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0000, seqnum++, varid, value, length);
+}
+
+int csr_write_bcsp(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0002, seqnum++, varid, value, length);
+}
+
+void csr_close_bcsp(void)
+{
+       close(fd);
+}
diff --git a/tools/csr_h4.c b/tools/csr_h4.c
new file mode 100644 (file)
index 0000000..3371770
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <termios.h>
+
+#include "csr.h"
+
+static uint16_t seqnum = 0x0000;
+
+static int fd = -1;
+
+int csr_open_h4(char *device)
+{
+       struct termios ti;
+
+       if (!device)
+               device = "/dev/ttyS0";
+
+       fd = open(device, O_RDWR | O_NOCTTY);
+       if (fd < 0) {
+               fprintf(stderr, "Can't open serial port: %s (%d)\n",
+                                               strerror(errno), errno);
+               return -1;
+       }
+
+       tcflush(fd, TCIOFLUSH);
+
+       if (tcgetattr(fd, &ti) < 0) {
+               fprintf(stderr, "Can't get port settings: %s (%d)\n",
+                                               strerror(errno), errno);
+               close(fd);
+               return -1;
+       }
+
+       cfmakeraw(&ti);
+
+       ti.c_cflag |= CLOCAL;
+       ti.c_cflag |= CRTSCTS;
+
+       cfsetospeed(&ti, B38400);
+
+       if (tcsetattr(fd, TCSANOW, &ti) < 0) {
+               fprintf(stderr, "Can't change port settings: %s (%d)\n",
+                                               strerror(errno), errno);
+               close(fd);
+               return -1;
+       }
+
+       tcflush(fd, TCIOFLUSH);
+
+       return 0;
+}
+
+static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       unsigned char cp[254], rp[254];
+       uint8_t cmd[10];
+       uint16_t size;
+       int len, offset = 3;
+
+       size = (length < 8) ? 9 : ((length + 1) / 2) + 5;
+
+       cmd[0] = command & 0xff;
+       cmd[1] = command >> 8;
+       cmd[2] = size & 0xff;
+       cmd[3] = size >> 8;
+       cmd[4] = seqnum & 0xff;
+       cmd[5] = seqnum >> 8;
+       cmd[6] = varid & 0xff;
+       cmd[7] = varid >> 8;
+       cmd[8] = 0x00;
+       cmd[9] = 0x00;
+
+       memset(cp, 0, sizeof(cp));
+       cp[0] = 0x01;
+       cp[1] = 0x00;
+       cp[2] = 0xfc;
+       cp[3] = (size * 2) + 1;
+       cp[4] = 0xc2;
+       memcpy(cp + 5, cmd, sizeof(cmd));
+       memcpy(cp + 15, value, length);
+
+       if (write(fd, cp, (size * 2) + 5) < 0)
+               return -1;
+
+       switch (varid) {
+       case CSR_VARID_COLD_RESET:
+       case CSR_VARID_WARM_RESET:
+       case CSR_VARID_COLD_HALT:
+       case CSR_VARID_WARM_HALT:
+               return 0;
+       }
+
+       do {
+               if (read(fd, rp, 1) < 1)
+                       return -1;
+       } while (rp[0] != 0x04);
+
+       if (read(fd, rp + 1, 2) < 2)
+               return -1;
+
+       do {
+               len = read(fd, rp + offset, sizeof(rp) - offset);
+               offset += len;
+       } while (offset < rp[2] + 3);
+
+       if (rp[0] != 0x04 || rp[1] != 0xff || rp[3] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[12] + (rp[13] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       memcpy(value, rp + 14, length);
+
+       return 0;
+}
+
+int csr_read_h4(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0000, seqnum++, varid, value, length);
+}
+
+int csr_write_h4(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0002, seqnum++, varid, value, length);
+}
+
+void csr_close_h4(void)
+{
+       close(fd);
+}
diff --git a/tools/csr_hci.c b/tools/csr_hci.c
new file mode 100644 (file)
index 0000000..6bd37c3
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "csr.h"
+
+static uint16_t seqnum = 0x0000;
+
+static int dd = -1;
+
+int csr_open_hci(char *device)
+{
+       struct hci_dev_info di;
+       struct hci_version ver;
+       int dev = 0;
+
+       if (device) {
+               dev = hci_devid(device);
+               if (dev < 0) {
+                       fprintf(stderr, "Device not available\n");
+                       return -1;
+               }
+       }
+
+       dd = hci_open_dev(dev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               return -1;
+       }
+
+       if (hci_devinfo(dev, &di) < 0) {
+               fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               return -1;
+       }
+
+       if (hci_read_local_version(dd, &ver, 1000) < 0) {
+               fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               return -1;
+       }
+
+       if (ver.manufacturer != 10) {
+               fprintf(stderr, "Unsupported manufacturer\n");
+               hci_close_dev(dd);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       unsigned char cp[254], rp[254];
+       struct hci_request rq;
+       uint8_t cmd[10];
+       uint16_t size;
+
+       size = (length < 8) ? 9 : ((length + 1) / 2) + 5;
+
+       cmd[0] = command & 0xff;
+       cmd[1] = command >> 8;
+       cmd[2] = size & 0xff;
+       cmd[3] = size >> 8;
+       cmd[4] = seqnum & 0xff;
+       cmd[5] = seqnum >> 8;
+       cmd[6] = varid & 0xff;
+       cmd[7] = varid >> 8;
+       cmd[8] = 0x00;
+       cmd[9] = 0x00;
+
+       memset(cp, 0, sizeof(cp));
+       cp[0] = 0xc2;
+       memcpy(cp + 1, cmd, sizeof(cmd));
+       memcpy(cp + 11, value, length);
+
+       switch (varid) {
+       case CSR_VARID_COLD_RESET:
+       case CSR_VARID_WARM_RESET:
+       case CSR_VARID_COLD_HALT:
+       case CSR_VARID_WARM_HALT:
+               return hci_send_cmd(dd, OGF_VENDOR_CMD, 0x00, (size * 2) + 1, cp);
+       }
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x00;
+       rq.event  = EVT_VENDOR;
+       rq.cparam = cp;
+       rq.clen   = (size * 2) + 1;
+       rq.rparam = rp;
+       rq.rlen   = sizeof(rp);
+
+       if (hci_send_req(dd, &rq, 2000) < 0)
+               return -1;
+
+       if (rp[0] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[9] + (rp[10] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       memcpy(value, rp + 11, length);
+
+       return 0;
+}
+
+int csr_read_hci(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0000, seqnum++, varid, value, length);
+}
+
+int csr_write_hci(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0002, seqnum++, varid, value, length);
+}
+
+void csr_close_hci(void)
+{
+       hci_close_dev(dd);
+}
diff --git a/tools/csr_usb.c b/tools/csr_usb.c
new file mode 100644 (file)
index 0000000..19903b0
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include <usb.h>
+
+#include "csr.h"
+
+#ifdef NEED_USB_GET_BUSSES
+static inline struct usb_bus *usb_get_busses(void)
+{
+       return usb_busses;
+}
+#endif
+
+#ifdef NEED_USB_INTERRUPT_READ
+static inline int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout)
+{
+       return usb_bulk_read(dev, ep, bytes, size, timeout);
+}
+#endif
+
+#ifndef USB_DIR_OUT
+#define USB_DIR_OUT    0x00
+#endif
+
+static uint16_t seqnum = 0x0000;
+
+static struct usb_dev_handle *udev = NULL;
+
+int csr_open_usb(char *device)
+{
+       struct usb_bus *bus;
+       struct usb_device *dev;
+
+       usb_init();
+
+       usb_find_busses();
+       usb_find_devices();
+
+       for (bus = usb_get_busses(); bus; bus = bus->next) {
+               for (dev = bus->devices; dev; dev = dev->next) {
+                       if (dev->descriptor.bDeviceClass == USB_CLASS_HUB)
+                               continue;
+
+                       if (dev->descriptor.idVendor != 0x0a12 ||
+                                       dev->descriptor.idProduct != 0x0001)
+                               continue;
+
+                       goto found;
+               }
+       }
+
+       fprintf(stderr, "Device not available\n");
+
+       return -1;
+
+found:
+       udev = usb_open(dev);
+       if (!udev) {
+               fprintf(stderr, "Can't open device: %s (%d)\n",
+                                               strerror(errno), errno);
+               return -1;
+       }
+
+       if (usb_claim_interface(udev, 0) < 0) {
+               fprintf(stderr, "Can't claim interface: %s (%d)\n",
+                                               strerror(errno), errno);
+               usb_close(udev);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length)
+{
+       unsigned char cp[254], rp[254];
+       uint8_t cmd[10];
+       uint16_t size;
+       int len, offset = 0;
+
+       size = (length < 8) ? 9 : ((length + 1) / 2) + 5;
+
+       cmd[0] = command & 0xff;
+       cmd[1] = command >> 8;
+       cmd[2] = size & 0xff;
+       cmd[3] = size >> 8;
+       cmd[4] = seqnum & 0xff;
+       cmd[5] = seqnum >> 8;
+       cmd[6] = varid & 0xff;
+       cmd[7] = varid >> 8;
+       cmd[8] = 0x00;
+       cmd[9] = 0x00;
+
+       memset(cp, 0, sizeof(cp));
+       cp[0] = 0x00;
+       cp[1] = 0xfc;
+       cp[2] = (size * 2) + 1;
+       cp[3] = 0xc2;
+       memcpy(cp + 4, cmd, sizeof(cmd));
+       memcpy(cp + 14, value, length);
+
+       usb_interrupt_read(udev, 0x81, (void *) rp, sizeof(rp), 2);
+
+       if (usb_control_msg(udev, USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_DEVICE,
+                               0, 0, 0, (void *) cp, (size * 2) + 4, 1000) < 0)
+               return -1;
+
+       switch (varid) {
+       case CSR_VARID_COLD_RESET:
+       case CSR_VARID_WARM_RESET:
+       case CSR_VARID_COLD_HALT:
+       case CSR_VARID_WARM_HALT:
+               return 0;
+       }
+
+       do {
+               len = usb_interrupt_read(udev, 0x81,
+                       (void *) (rp + offset), sizeof(rp) - offset, 10);
+               offset += len;
+       } while (len > 0);
+
+       if (rp[0] != 0xff || rp[2] != 0xc2) {
+               errno = EIO;
+               return -1;
+       }
+
+       if ((rp[11] + (rp[12] << 8)) != 0) {
+               errno = ENXIO;
+               return -1;
+       }
+
+       memcpy(value, rp + 13, length);
+
+       return 0;
+}
+
+int csr_read_usb(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0000, seqnum++, varid, value, length);
+}
+
+int csr_write_usb(uint16_t varid, uint8_t *value, uint16_t length)
+{
+       return do_command(0x0002, seqnum++, varid, value, length);
+}
+
+void csr_close_usb(void)
+{
+       usb_release_interface(udev, 0);
+       usb_close(udev);
+}
diff --git a/tools/dfu.c b/tools/dfu.c
new file mode 100644 (file)
index 0000000..39ec088
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include <usb.h>
+
+#include "dfu.h"
+
+#ifndef USB_DIR_OUT
+#define USB_DIR_OUT    0x00
+#endif
+
+#ifndef USB_DIR_IN
+#define USB_DIR_IN     0x80
+#endif
+
+#ifndef USB_DT_DFU
+#define USB_DT_DFU     0x21
+#endif
+
+#define DFU_PACKETSIZE         0x03ff          /* CSR default value: 1023 */
+#define DFU_TIMEOUT            10000
+
+static uint32_t dfu_crc32_table[] = {
+       0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+       0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+       0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+       0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+       0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+       0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+       0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+       0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+       0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+       0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+       0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+       0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+       0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+       0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+       0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+       0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+       0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+       0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+       0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+       0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+       0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+       0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+       0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+       0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+       0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+       0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+       0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+       0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+       0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+       0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+       0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+       0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+       0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+       0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+       0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+       0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+       0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+       0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+       0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+       0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+       0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+       0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+       0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+uint32_t crc32_init(void)
+{
+       return 0xffffffff;
+}
+
+uint32_t crc32_byte(uint32_t accum, uint8_t delta)
+{
+       return dfu_crc32_table[(accum ^ delta) & 0xff] ^ (accum >> 8);
+}
+
+int dfu_detach(struct usb_dev_handle *udev, int intf)
+{
+       if (!udev)
+               return -EIO;
+
+       return usb_control_msg(udev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+               DFU_DETACH, 0x1388, intf, NULL, 0, DFU_TIMEOUT);
+}
+
+int dfu_upload(struct usb_dev_handle *udev, int intf, int block, char *buffer, int size)
+{
+       if (!udev)
+               return -EIO;
+
+       return usb_control_msg(udev, USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE,
+               DFU_UPLOAD, block, intf, buffer, size, DFU_TIMEOUT);
+}
+
+int dfu_download(struct usb_dev_handle *udev, int intf, int block, char *buffer, int size)
+{
+       if (!udev)
+               return -EIO;
+
+       return usb_control_msg(udev, USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE,
+               DFU_DNLOAD, block, intf, buffer, size, DFU_TIMEOUT);
+}
+
+int dfu_get_status(struct usb_dev_handle *udev, int intf, struct dfu_status *status)
+{
+       if (!udev || !status)
+               return -EIO;
+
+       return usb_control_msg(udev, USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE,
+               DFU_GETSTATUS, 0, intf, (char *) status, DFU_STATUS_SIZE, DFU_TIMEOUT);
+}
+
+int dfu_clear_status(struct usb_dev_handle *udev, int intf)
+{
+       if (!udev)
+               return -EIO;
+
+       return usb_control_msg(udev, USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE,
+               DFU_CLRSTATUS, 0, intf, NULL, 0, DFU_TIMEOUT);
+}
+
+int dfu_get_state(struct usb_dev_handle *udev, int intf, uint8_t *state)
+{
+       if (!udev || !state)
+               return -EIO;
+
+       return usb_control_msg(udev, USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE,
+               DFU_GETSTATE, 0, intf, (char *) state, 1, DFU_TIMEOUT);
+}
+
+int dfu_abort(struct usb_dev_handle *udev, int intf)
+{
+       if (!udev)
+               return -EIO;
+
+       return usb_control_msg(udev, USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE,
+               DFU_ABORT, 0, intf, NULL, 0, DFU_TIMEOUT);
+}
diff --git a/tools/dfu.h b/tools/dfu.h
new file mode 100644 (file)
index 0000000..7f999f4
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <stdint.h>
+
+/* CRC interface */
+uint32_t crc32_init(void);
+uint32_t crc32_byte(uint32_t accum, uint8_t delta);
+
+/* DFU descriptor */
+struct usb_dfu_descriptor {
+       u_int8_t  bLength;
+       u_int8_t  bDescriptorType;
+       u_int8_t  bmAttributes;
+       u_int16_t wDetachTimeout;
+       u_int16_t wTransferSize;
+};
+
+/* DFU commands */
+#define DFU_DETACH             0
+#define DFU_DNLOAD             1
+#define DFU_UPLOAD             2
+#define DFU_GETSTATUS          3
+#define DFU_CLRSTATUS          4
+#define DFU_GETSTATE           5
+#define DFU_ABORT              6
+
+/* DFU status */
+struct dfu_status {
+       uint8_t bStatus;
+       uint8_t bwPollTimeout[3];
+       uint8_t bState;
+       uint8_t iString;
+} __attribute__ ((packed));
+#define DFU_STATUS_SIZE 6
+
+/* DFU status */
+#define DFU_OK                 0x00
+#define DFU_ERR_TARGET         0x01
+#define DFU_ERR_FILE           0x02
+#define DFU_ERR_WRITE          0x03
+#define DFU_ERR_ERASE          0x04
+#define DFU_ERR_CHECK_ERASED   0x05
+#define DFU_ERR_PROG           0x06
+#define DFU_ERR_VERIFY         0x07
+#define DFU_ERR_ADDRESS                0x08
+#define DFU_ERR_NOTDONE                0x09
+#define DFU_ERR_FIRMWARE       0x0a
+#define DFU_ERR_VENDOR         0x0b
+#define DFU_ERR_USBR           0x0c
+#define DFU_ERR_POR            0x0d
+#define DFU_ERR_UNKNOWN                0x0e
+#define DFU_ERR_STALLEDPKT     0x0f
+
+/* DFU state */
+#define DFU_STATE_APP_IDLE             0
+#define DFU_STATE_APP_DETACH           1
+#define DFU_STATE_DFU_IDLE             2
+#define DFU_STATE_DFU_DNLOAD_SYNC      3
+#define DFU_STATE_DFU_DNLOAD_BUSY      4
+#define DFU_STATE_DFU_DNLOAD_IDLE      5
+#define DFU_STATE_DFU_MANIFEST_SYNC    6
+#define DFU_STATE_DFU_MANIFEST         7
+#define DFU_STATE_MANIFEST_WAIT_RESET  8
+#define DFU_STATE_UPLOAD_IDLE          9
+#define DFU_STATE_ERROR                        10
+
+/* DFU suffix */
+struct dfu_suffix {
+       uint16_t bcdDevice;
+       uint16_t idProduct;
+       uint16_t idVendor;
+       uint16_t bcdDFU;
+       uint8_t  ucDfuSignature[3];
+       uint8_t  bLength;
+       uint32_t dwCRC;
+} __attribute__ ((packed));
+#define DFU_SUFFIX_SIZE 16
+
+/* DFU interface */
+int dfu_detach(struct usb_dev_handle *udev, int intf);
+int dfu_upload(struct usb_dev_handle *udev, int intf, int block, char *buffer, int size);
+int dfu_download(struct usb_dev_handle *udev, int intf, int block, char *buffer, int size);
+int dfu_get_status(struct usb_dev_handle *udev, int intf, struct dfu_status *status);
+int dfu_clear_status(struct usb_dev_handle *udev, int intf);
+int dfu_get_state(struct usb_dev_handle *udev, int intf, uint8_t *state);
+int dfu_abort(struct usb_dev_handle *udev, int intf);
diff --git a/tools/dfubabel.1 b/tools/dfubabel.1
new file mode 100644 (file)
index 0000000..5e0f805
--- /dev/null
@@ -0,0 +1,38 @@
+.\"
+.\"    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., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.TH DFUBABEL 8 "JUNE 6, 2005" "" ""
+
+.SH NAME
+dfubabel \- Babel DFU mode switching utility
+.SH SYNOPSIS
+.BR "dfubabel
+[
+.I options
+]
+.SH DESCRIPTION
+.B dfubabel
+is used to switch Babel devices into DFU mode.
+.SH OPTIONS
+.TP
+.BI -h
+Gives a list of possible options.
+.TP
+.BI -q
+Don't display any messages.
+.SH AUTHOR
+Written by Marcel Holtmann <marcel@holtmann.org>.
+.br
diff --git a/tools/dfubabel.c b/tools/dfubabel.c
new file mode 100644 (file)
index 0000000..612accc
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <usb.h>
+
+#ifdef NEED_USB_GET_BUSSES
+static inline struct usb_bus *usb_get_busses(void)
+{
+       return usb_busses;
+}
+#endif
+
+struct device_info;
+
+struct device_id {
+       uint16_t vendor;
+       uint16_t product;
+       int (*func)(struct device_info *dev, int argc, char *argv[]);
+};
+
+struct device_info {
+       struct usb_device *dev;
+       struct device_id *id;
+};
+
+static int switch_babel(struct device_info *devinfo, int argc, char *argv[])
+{
+       char buf[3];
+       struct usb_dev_handle *udev;
+       int err;
+
+       memset(buf, 0, sizeof(buf));
+
+       buf[0] = 0x00;
+       buf[1] = 0x06;
+       buf[2] = 0x00;
+
+       udev = usb_open(devinfo->dev);
+       if (!udev)
+               return -errno;
+
+       if (usb_claim_interface(udev, 0) < 0) {
+               err = -errno;
+               usb_close(udev);
+               return err;
+       }
+
+       err = usb_bulk_write(udev, 0x02, buf, sizeof(buf), 10000);
+
+       if (err == 0) {
+               err = -1;
+               errno = EALREADY;
+       } else {
+               if (errno == ETIMEDOUT)
+                       err = 0;
+       }
+
+       usb_release_interface(udev, 0);
+       usb_close(udev);
+
+       return err;
+}
+
+static struct device_id device_list[] = {
+       { 0x0a12, 0x0042, switch_babel },
+       { -1 }
+};
+
+static struct device_id *match_device(uint16_t vendor, uint16_t product)
+{
+       int i;
+
+       for (i = 0; device_list[i].func; i++) {
+               if (vendor == device_list[i].vendor &&
+                               product == device_list[i].product)
+                       return &device_list[i];
+       }
+
+       return NULL;
+}
+
+static int find_devices(struct device_info *devinfo, size_t size)
+{
+       struct usb_bus *bus;
+       struct usb_device *dev;
+       struct device_id *id;
+       unsigned int count = 0;
+
+       usb_find_busses();
+       usb_find_devices();
+
+       for (bus = usb_get_busses(); bus; bus = bus->next)
+               for (dev = bus->devices; dev; dev = dev->next) {
+                       id = match_device(dev->descriptor.idVendor,
+                                               dev->descriptor.idProduct);
+                       if (!id)
+                               continue;
+
+                       if (count < size) {
+                               devinfo[count].dev = dev;
+                               devinfo[count].id = id;
+                               count++;
+                       }
+               }
+
+       return count;
+}
+
+static void usage(void)
+{
+       printf("dfubabel - Babel DFU mode switching utility\n\n");
+
+       printf("Usage:\n"
+               "\tdfubabel [options]\n"
+               "\n");
+
+       printf("Options:\n"
+               "\t-h, --help           Display help\n"
+               "\t-q, --quiet          Don't display any messages\n"
+               "\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "quiet",      0, 0, 'q' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       struct device_info dev[16];
+       int i, opt, num, quiet = 0;
+
+       while ((opt = getopt_long(argc, argv, "+qh", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'q':
+                       quiet = 1;
+                       break;
+               case 'h':
+                       usage();
+                       exit(0);
+               default:
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       usb_init();
+
+       num = find_devices(dev, sizeof(dev) / sizeof(dev[0]));
+       if (num <= 0) {
+               if (!quiet)
+                       fprintf(stderr, "No Babel devices found\n");
+               exit(1);
+       }
+
+       for (i = 0; i < num; i++) {
+               struct device_id *id = dev[i].id;
+               int err;
+
+               if (!quiet)
+                       printf("Switching device %04x:%04x ",
+                                               id->vendor, id->product);
+               fflush(stdout);
+
+               err = id->func(&dev[i], argc, argv);
+               if (err < 0) {
+                       if (!quiet)
+                               printf("failed (%s)\n", strerror(-err));
+               } else {
+                       if (!quiet)
+                               printf("was successful\n");
+               }
+       }
+
+       return 0;
+}
diff --git a/tools/dfutool.1 b/tools/dfutool.1
new file mode 100644 (file)
index 0000000..115114b
--- /dev/null
@@ -0,0 +1,53 @@
+.\"
+.\"    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., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.TH DFUTOOL 1 "APRIL 21, 2005" "" ""
+
+.SH NAME
+dfutool \- Device Firmware Upgrade utility
+.SH SYNOPSIS
+.BR "dfutool
+[
+.I options
+] <
+.I command
+>
+.SH DESCRIPTION
+.B dfutool
+is used to verify, archive and upgrade firmware files.
+.SH OPTIONS
+.TP
+.BI -h
+Gives a list of possible commands.
+.TP
+.BI -d " <device>"
+The command specifies the device to use.
+.SH COMMANDS
+.TP
+.BI verify " <dfu-file>"
+Display information about the firmware file.
+.TP
+.BI modify " <dfu-file>"
+Change DFU specific values in the firmware file.
+.TP
+.BI upgrade " <dfu-file>"
+Upgrade the device with a new firmware.
+.TP
+.BI archive " <dfu-file>"
+Archive the current firmware of the device.
+.SH AUTHOR
+Written by Marcel Holtmann <marcel@holtmann.org>.
+.br
diff --git a/tools/dfutool.c b/tools/dfutool.c
new file mode 100644 (file)
index 0000000..16dd62e
--- /dev/null
@@ -0,0 +1,791 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+#include <libgen.h>
+#include <endian.h>
+#include <byteswap.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <usb.h>
+
+#include "dfu.h"
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define cpu_to_le16(d)  (d)
+#define cpu_to_le32(d)  (d)
+#define le16_to_cpu(d)  (d)
+#define le32_to_cpu(d)  (d)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define cpu_to_le16(d)  bswap_16(d)
+#define cpu_to_le32(d)  bswap_32(d)
+#define le16_to_cpu(d)  bswap_16(d)
+#define le32_to_cpu(d)  bswap_32(d)
+#else
+#error "Unknown byte order"
+#endif
+
+#ifdef NEED_USB_GET_BUSSES
+static inline struct usb_bus *usb_get_busses(void)
+{
+       return usb_busses;
+}
+#endif
+
+#ifndef USB_CLASS_WIRELESS
+#define USB_CLASS_WIRELESS     0xe0
+#endif
+
+#ifndef USB_CLASS_APPLICATION
+#define USB_CLASS_APPLICATION  0xfe
+#endif
+
+static int get_interface_number(struct usb_device *dev)
+{
+       int c, i, a;
+
+       for (c = 0; c < dev->descriptor.bNumConfigurations; c++) {
+               struct usb_config_descriptor *config = &dev->config[c];
+
+               for (i = 0; i < config->bNumInterfaces; i++) {
+                       struct usb_interface *interface = &config->interface[i];
+
+                       for (a = 0; a < interface->num_altsetting; a++) {
+                               struct usb_interface_descriptor *desc = &interface->altsetting[a];
+
+                               if (desc->bInterfaceClass != USB_CLASS_APPLICATION)
+                                       continue;
+                               if (desc->bInterfaceSubClass != 0x01)
+                                       continue;
+                               if (desc->bInterfaceProtocol != 0x00)
+                                       continue;
+
+                               return desc->bInterfaceNumber;
+                       }
+               }
+       }
+
+       return -1;
+}
+
+static void print_device(struct usb_device *dev)
+{
+       printf("Bus %s Device %s: ID %04x:%04x Interface %d%s\n",
+               dev->bus->dirname, dev->filename,
+               dev->descriptor.idVendor, dev->descriptor.idProduct,
+               get_interface_number(dev),
+               dev->descriptor.bDeviceClass == USB_CLASS_APPLICATION ? " (DFU mode)" : "");
+}
+
+static struct usb_dev_handle *open_device(char *device, struct dfu_suffix *suffix)
+{
+       struct usb_bus *bus;
+       struct usb_device *dev, *dfu_dev[10];
+       struct usb_dev_handle *udev;
+       struct dfu_status status;
+       char str[8];
+       int i, intf, sel = 0, num = 0, try = 5, bus_id = -1, dev_id = -1;
+
+       printf("Scanning USB busses ... ");
+       fflush(stdout);
+
+       usb_find_busses();
+       usb_find_devices();
+
+       for (bus = usb_get_busses(); bus; bus = bus->next) {
+               if (bus_id > 0) {
+                       snprintf(str, sizeof(str) - 1, "%03i", bus_id);
+                       if (strcmp(str, bus->dirname))
+                               continue;
+               }
+
+               for (dev = bus->devices; dev; dev = dev->next) {
+                       if (bus_id > 0 && dev_id > 0) {
+                               snprintf(str, sizeof(str) - 1, "%03i", dev_id);
+                               if (strcmp(str, dev->filename))
+                                       continue;
+                       }
+
+                       if (dev->descriptor.bDeviceClass == USB_CLASS_HUB)
+                               continue;
+
+                       if (num > 9 || get_interface_number(dev) < 0)
+                               continue;
+
+                       dfu_dev[num++] = dev;
+               }
+       }
+
+       if (num < 1) {
+               printf("\rCan't find any DFU devices\n");
+               return NULL;
+       }
+
+       printf("\rAvailable devices with DFU support:\n\n");
+       for (i = 0; i < num; i++) {
+               printf("\t%2d) ", i + 1);
+               print_device(dfu_dev[i]);
+       }
+       printf("\n");
+
+       do {
+               printf("\rSelect device (abort with 0): ");
+               fflush(stdout);
+               memset(str, 0, sizeof(str));
+               if (!fgets(str, sizeof(str) - 1, stdin))
+                       continue;
+               sel = atoi(str);
+       } while (!isdigit(str[0]) || sel < 0 || sel > num );
+
+       if (sel < 1)
+               return NULL;
+
+       sel--;
+       intf = get_interface_number(dfu_dev[sel]);
+       printf("\n");
+
+       udev = usb_open(dfu_dev[sel]);
+       if (!udev) {
+               printf("Can't open device: %s (%d)\n", strerror(errno), errno);
+               return NULL;
+       }
+
+       if (usb_claim_interface(udev, intf) < 0) {
+               printf("Can't claim interface: %s (%d)\n", strerror(errno), errno);
+               usb_close(udev);
+               return NULL;
+       }
+
+       if (dfu_get_status(udev, intf, &status) < 0) {
+               printf("Can't get status: %s (%d)\n", strerror(errno), errno);
+               goto error;
+       }
+
+       if (status.bState == DFU_STATE_ERROR) {
+               if (dfu_clear_status(udev, intf) < 0) {
+                       printf("Can't clear status: %s (%d)\n", strerror(errno), errno);
+                       goto error;
+               }
+               if (dfu_abort(udev, intf) < 0) {
+                       printf("Can't abort previous action: %s (%d)\n", strerror(errno), errno);
+                       goto error;
+               }
+               if (dfu_get_status(udev, intf, &status) < 0) {
+                       printf("Can't get status: %s (%d)\n", strerror(errno), errno);
+                       goto error;
+               }
+       }
+
+       if (status.bState == DFU_STATE_DFU_IDLE) {
+               if (suffix) {
+                       suffix->idVendor  = cpu_to_le16(0x0000);
+                       suffix->idProduct = cpu_to_le16(0x0000);
+                       suffix->bcdDevice = cpu_to_le16(0x0000);
+               }
+               return udev;
+       }
+
+       if (status.bState != DFU_STATE_APP_IDLE) {
+               printf("Device is not idle, can't detach it (state %d)\n", status.bState);
+               goto error;
+       }
+
+       printf("Switching device into DFU mode ... ");
+       fflush(stdout);
+
+       if (suffix) {
+               suffix->idVendor  = cpu_to_le16(dfu_dev[sel]->descriptor.idVendor);
+               suffix->idProduct = cpu_to_le16(dfu_dev[sel]->descriptor.idProduct);
+               suffix->bcdDevice = cpu_to_le16(dfu_dev[sel]->descriptor.bcdDevice);
+       }
+
+       if (dfu_detach(udev, intf) < 0) {
+               printf("\rCan't detach device: %s (%d)\n", strerror(errno), errno);
+               goto error;
+       }
+
+       if (dfu_get_status(udev, intf, &status) < 0) {
+               printf("\rCan't get status: %s (%d)\n", strerror(errno), errno);
+               goto error;
+       }
+
+       if (status.bState != DFU_STATE_APP_DETACH) {
+               printf("\rDevice is not in detach mode, try again\n");
+               goto error;
+       }
+
+       usb_release_interface(udev, intf);
+       usb_reset(udev);
+       usb_close(udev);
+
+       bus = dfu_dev[sel]->bus;
+       num = 0;
+
+       while (num != 1 && try-- > 0) {
+               sleep(1);
+               usb_find_devices();
+
+               for (dev = bus->devices; dev; dev = dev->next) {
+                       if (dev->descriptor.bDeviceClass != USB_CLASS_APPLICATION)
+                               continue;
+
+                       if (suffix && dev->descriptor.idVendor != le16_to_cpu(suffix->idVendor))
+                               continue;
+
+                       if (num > 9 || get_interface_number(dev) != 0)
+                               continue;
+
+                       dfu_dev[num++] = dev;
+               }
+       }
+
+       if (num != 1) {
+               printf("\rCan't identify device with DFU mode\n");
+               goto error;
+       }
+
+       printf("\r");
+
+       intf = 0;
+
+       udev = usb_open(dfu_dev[0]);
+       if (!udev) {
+               printf("Can't open device: %s (%d)\n", strerror(errno), errno);
+               return NULL;
+       }
+
+       if (usb_claim_interface(udev, intf) < 0) {
+               printf("Can't claim interface: %s (%d)\n", strerror(errno), errno);
+               usb_close(udev);
+               return NULL;
+       }
+
+       if (dfu_get_status(udev, intf, &status) < 0) {
+               printf("Can't get status: %s (%d)\n", strerror(errno), errno);
+               goto error;
+       }
+
+       if (status.bState != DFU_STATE_DFU_IDLE) {
+               printf("Device is not in DFU mode, can't use it\n");
+               goto error;
+       }
+
+       return udev;
+
+error:
+       usb_release_interface(udev, intf);
+       usb_close(udev);
+       return NULL;
+}
+
+static void usage(void);
+
+static void cmd_verify(char *device, int argc, char **argv)
+{
+       struct stat st;
+       struct dfu_suffix *suffix;
+       uint32_t crc;
+       uint16_t bcd;
+       char str[16];
+       unsigned char *buf;
+       size_t size;
+       char *filename;
+       unsigned int i, len;
+       int fd;
+
+       if (argc < 2) {
+               usage();
+               exit(1);
+       }
+
+       filename = argv[1];
+
+       if (stat(filename, &st) < 0) {
+               perror("Can't access firmware");
+               exit(1);
+       }
+
+       size = st.st_size;
+
+       if (!(buf = malloc(size))) {
+               perror("Unable to allocate file buffer");
+               exit(1);
+       }
+
+       if ((fd = open(filename, O_RDONLY)) < 0) {
+               perror("Can't open firmware");
+               free(buf);
+               exit(1);
+       }
+
+       if (read(fd, buf, size) < (ssize_t) size) {
+               perror("Can't load firmware");
+               free(buf);
+               close(fd);
+               exit(1);
+       }
+
+       printf("Filename\t%s\n", basename(filename));
+       printf("Filesize\t%zd\n", size);
+
+       crc = crc32_init();
+       for (i = 0; i < size - 4; i++)
+               crc = crc32_byte(crc, buf[i]);
+       printf("Checksum\t%08x\n", crc);
+
+       printf("\n");
+       len = buf[size - 5];
+       printf("DFU suffix\t");
+       for (i = 0; i < len; i++) {
+               printf("%02x ", buf[size - len + i]);
+       }
+       printf("\n\n");
+
+       suffix = (struct dfu_suffix *) (buf + size - DFU_SUFFIX_SIZE);
+
+       printf("idVendor\t%04x\n", le16_to_cpu(suffix->idVendor));
+       printf("idProduct\t%04x\n", le16_to_cpu(suffix->idProduct));
+       printf("bcdDevice\t%x\n", le16_to_cpu(suffix->bcdDevice));
+
+       printf("\n");
+
+       bcd = le16_to_cpu(suffix->bcdDFU);
+
+       printf("bcdDFU\t\t%x.%x\n", bcd >> 8, bcd & 0xff);
+       printf("ucDfuSignature\t%c%c%c\n", suffix->ucDfuSignature[2],
+               suffix->ucDfuSignature[1], suffix->ucDfuSignature[0]);
+       printf("bLength\t\t%d\n", suffix->bLength);
+       printf("dwCRC\t\t%08x\n", le32_to_cpu(suffix->dwCRC));
+       printf("\n");
+
+       memset(str, 0, sizeof(str));
+       memcpy(str, buf, 8);
+
+       if (!strcmp(str, "CSR-dfu1") || !strcmp(str, "CSR-dfu2")) {
+               crc = crc32_init();
+               for (i = 0; i < size - DFU_SUFFIX_SIZE; i++)
+                       crc = crc32_byte(crc, buf[i]);
+
+               printf("Firmware type\t%s\n", str);
+               printf("Firmware check\t%s checksum\n", crc == 0 ? "valid" : "corrupt");
+               printf("\n");
+       }
+
+       free(buf);
+
+       close(fd);
+}
+
+static void cmd_modify(char *device, int argc, char **argv)
+{
+}
+
+static void cmd_upgrade(char *device, int argc, char **argv)
+{
+       struct usb_dev_handle *udev;
+       struct dfu_status status;
+       struct dfu_suffix suffix;
+       struct stat st;
+       char *buf;
+       size_t filesize;
+       unsigned long count, timeout = 0;
+       char *filename;
+       uint32_t crc, dwCRC;
+       unsigned int i;
+       int fd, block, len, size, sent = 0, try = 10;
+
+       if (argc < 2) {
+               usage();
+               exit(1);
+       }
+
+       filename = argv[1];
+
+       if (stat(filename, &st) < 0) {
+               perror("Can't access firmware");
+               exit(1);
+       }
+
+       filesize = st.st_size;
+
+       if (!(buf = malloc(filesize))) {
+               perror("Unable to allocate file buffer");
+               exit(1);
+       }
+
+       if ((fd = open(filename, O_RDONLY)) < 0) {
+               perror("Can't open firmware");
+               free(buf);
+               exit(1);
+       }
+
+       if (read(fd, buf, filesize) < (ssize_t) filesize) {
+               perror("Can't load firmware");
+               free(buf);
+               close(fd);
+               exit(1);
+       }
+
+       memcpy(&suffix, buf + filesize - DFU_SUFFIX_SIZE, sizeof(suffix));
+       dwCRC = le32_to_cpu(suffix.dwCRC);
+
+       printf("Filename\t%s\n", basename(filename));
+       printf("Filesize\t%zd\n", filesize);
+
+       crc = crc32_init();
+       for (i = 0; i < filesize - 4; i++)
+               crc = crc32_byte(crc, buf[i]);
+
+       printf("Checksum\t%08x (%s)\n", crc,
+                       crc == dwCRC ? "valid" : "corrupt");
+
+       if (crc != dwCRC) {
+               free(buf);
+               close(fd);
+               exit(1);
+       }
+
+       printf("\n");
+
+       udev = open_device(device, &suffix);
+       if (!udev)
+               exit(1);
+
+       printf("\r" "          " "          " "          " "          " "          ");
+       printf("\rFirmware download ... ");
+       fflush(stdout);
+
+       count = filesize - DFU_SUFFIX_SIZE;
+       block = 0;
+
+       while (count) {
+               size = (count > 1023) ? 1023 : count;
+
+               if (dfu_get_status(udev, 0, &status) < 0) {
+                       if (try-- > 0) {
+                               sleep(1);
+                               continue;
+                       }
+                       printf("\rCan't get status: %s (%d)\n", strerror(errno), errno);
+                       goto done;
+               }
+
+               if (status.bStatus != DFU_OK) {
+                       if (try-- > 0) {
+                               dfu_clear_status(udev, 0);
+                               sleep(1);
+                               continue;
+                       }
+                       printf("\rFirmware download ... aborting (status %d state %d)\n",
+                                               status.bStatus, status.bState);
+                       goto done;
+               }
+
+               if (status.bState != DFU_STATE_DFU_IDLE &&
+                               status.bState != DFU_STATE_DFU_DNLOAD_IDLE) {
+                       sleep(1);
+                       continue;
+               }
+
+               timeout = (status.bwPollTimeout[2] << 16) |
+                               (status.bwPollTimeout[1] << 8) |
+                                       status.bwPollTimeout[0];
+
+               usleep(timeout * 1000);
+
+               len = dfu_download(udev, 0, block, buf + sent, size);
+               if (len < 0) {
+                       if (try-- > 0) {
+                               sleep(1);
+                               continue;
+                       }
+                       printf("\rCan't upload next block: %s (%d)\n", strerror(errno), errno);
+                       goto done;
+               }
+
+               printf("\rFirmware download ... %d bytes ", block * 1023 + len);
+               fflush(stdout);
+
+               sent  += len;
+               count -= len;
+               block++;
+       }
+
+       printf("\r" "          " "          " "          " "          " "          ");
+       printf("\rFinishing firmware download ... ");
+       fflush(stdout);
+
+       sleep(1);
+
+       if (dfu_get_status(udev, 0, &status) < 0) {
+               printf("\rCan't get status: %s (%d)\n", strerror(errno), errno);
+               goto done;
+       }
+
+       timeout = (status.bwPollTimeout[2] << 16) |
+                       (status.bwPollTimeout[1] << 8) |
+                               status.bwPollTimeout[0];
+
+       usleep(timeout * 1000);
+
+       if (count == 0) {
+               len = dfu_download(udev, 0, block, NULL, 0);
+               if (len < 0) {
+                       printf("\rCan't send final block: %s (%d)\n", strerror(errno), errno);
+                       goto done;
+               }
+       }
+
+       printf("\r" "          " "          " "          " "          " "          ");
+       printf("\rWaiting for device ... ");
+       fflush(stdout);
+
+       sleep(10);
+
+       printf("\n");
+
+done:
+       free(buf);
+       close(fd);
+
+       usb_release_interface(udev, 0);
+       usb_reset(udev);
+       usb_close(udev);
+}
+
+static void cmd_archive(char *device, int argc, char **argv)
+{
+       struct usb_dev_handle *udev;
+       struct dfu_status status;
+       struct dfu_suffix suffix;
+       char buf[2048];
+       unsigned long timeout = 0;
+       char *filename;
+       uint32_t crc;
+       int fd, i, n, len, try = 8;
+
+       if (argc < 2) {
+               usage();
+               exit(1);
+       }
+
+       filename = argv[1];
+
+       udev = open_device(device, &suffix);
+       if (!udev)
+               exit(1);
+
+       fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+       if (fd < 0) {
+               printf("Can't open firmware file: %s (%d)\n", strerror(errno), errno);
+               goto done;
+       }
+
+       printf("\r" "          " "          " "          " "          " "          ");
+       printf("\rFirmware upload ... ");
+       fflush(stdout);
+
+       crc = crc32_init();
+       n = 0;
+       while (1) {
+               if (dfu_get_status(udev, 0, &status) < 0) {
+                       if (try-- > 0) {
+                               sleep(1);
+                               continue;
+                       }
+                       printf("\rCan't get status: %s (%d)\n", strerror(errno), errno);
+                       goto done;
+               }
+
+               if (status.bStatus != DFU_OK) {
+                       if (try-- > 0) {
+                               dfu_clear_status(udev, 0);
+                               sleep(1);
+                               continue;
+                       }
+                       printf("\rFirmware upload ... aborting (status %d state %d)\n",
+                                               status.bStatus, status.bState);
+                       goto done;
+               }
+
+               if (status.bState != DFU_STATE_DFU_IDLE &&
+                               status.bState != DFU_STATE_UPLOAD_IDLE) {
+                       sleep(1);
+                       continue;
+               }
+
+               timeout = (status.bwPollTimeout[2] << 16) |
+                               (status.bwPollTimeout[1] << 8) |
+                                       status.bwPollTimeout[0];
+
+               usleep(timeout * 1000);
+
+               len = dfu_upload(udev, 0, n, buf, 1023);
+               if (len < 0) {
+                       if (try-- > 0) {
+                               sleep(1);
+                               continue;
+                       }
+                       printf("\rCan't upload next block: %s (%d)\n", strerror(errno), errno);
+                       goto done;
+               }
+
+               printf("\rFirmware upload ... %d bytes ", n * 1023 + len);
+               fflush(stdout);
+
+               for (i = 0; i < len; i++)
+                       crc = crc32_byte(crc, buf[i]);
+
+               if (len > 0) {
+                       if (write(fd, buf, len) < 0) {
+                               printf("\rCan't write next block: %s (%d)\n", strerror(errno), errno);
+                               goto done;
+                       }
+               }
+
+               n++;
+               if (len != 1023)
+                       break;
+       }
+       printf("\n");
+
+       suffix.bcdDFU = cpu_to_le16(0x0100);
+       suffix.ucDfuSignature[0] = 'U';
+       suffix.ucDfuSignature[1] = 'F';
+       suffix.ucDfuSignature[2] = 'D';
+       suffix.bLength = DFU_SUFFIX_SIZE;
+
+       memcpy(buf, &suffix, DFU_SUFFIX_SIZE);
+       for (i = 0; i < DFU_SUFFIX_SIZE - 4; i++)
+               crc = crc32_byte(crc, buf[i]);
+
+       suffix.dwCRC = cpu_to_le32(crc);
+
+       if (write(fd, &suffix, DFU_SUFFIX_SIZE) < 0)
+               printf("Can't write suffix block: %s (%d)\n", strerror(errno), errno);
+
+done:
+       close(fd);
+
+       usb_release_interface(udev, 0);
+       usb_reset(udev);
+       usb_close(udev);
+}
+
+struct {
+       char *cmd;
+       char *alt;
+       void (*func)(char *device, int argc, char **argv);
+       char *opt;
+       char *doc;
+} command[] = {
+       { "verify",  "check",    cmd_verify,  "<dfu-file>", "Check firmware file"         },
+       { "modify",  "change",   cmd_modify,  "<dfu-file>", "Change firmware attributes"  },
+       { "upgrade", "download", cmd_upgrade, "<dfu-file>", "Download a new firmware"     },
+       { "archive", "upload",   cmd_archive, "<dfu-file>", "Upload the current firmware" },
+       { NULL, NULL, NULL, 0, 0 }
+};
+
+static void usage(void)
+{
+       int i;
+
+       printf("dfutool - Device Firmware Upgrade utility ver %s\n\n", VERSION);
+
+       printf("Usage:\n"
+               "\tdfutool [options] <command>\n"
+               "\n");
+
+       printf("Options:\n"
+               "\t-d, --device <device>   USB device\n"
+               "\t-h, --help              Display help\n"
+               "\n");
+
+       printf("Commands:\n");
+       for (i = 0; command[i].cmd; i++)
+               printf("\t%-8s %-10s\t%s\n", command[i].cmd,
+               command[i].opt ? command[i].opt : " ",
+               command[i].doc);
+       printf("\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "device",     1, 0, 'd' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       char *device = NULL;
+       int i, opt;
+
+       while ((opt = getopt_long(argc, argv, "+d:h", main_options, NULL)) != -1) {
+               switch(opt) {
+               case 'd':
+                       device = strdup(optarg);
+                       break;
+
+               case 'h':
+                       usage();
+                       exit(0);
+
+               default:
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       usb_init();
+
+       for (i = 0; command[i].cmd; i++) {
+               if (strcmp(command[i].cmd, argv[0]) && strcmp(command[i].alt, argv[0]))
+                       continue;
+               command[i].func(device, argc, argv);
+               exit(0);
+       }
+
+       usage();
+       exit(1);
+}
diff --git a/tools/hciattach.8 b/tools/hciattach.8
new file mode 100644 (file)
index 0000000..e0e2730
--- /dev/null
@@ -0,0 +1,155 @@
+.TH HCIATTACH 8 "Jan 22 2002" BlueZ "Linux System Administration"
+.SH NAME
+hciattach \- attach serial devices via UART HCI to BlueZ stack
+.SH SYNOPSIS
+.B hciattach
+.RB [\| \-b \|]
+.RB [\| \-n \|]
+.RB [\| \-p \|]
+.RB [\| \-t
+.IR timeout \|]
+.RB [\| \-s
+.IR speed \|]
+.RB [\| \-l \|]
+.RB [\| \-r \|]
+.I tty
+.IR type \||\| id
+.I speed
+.I flow
+.I bdaddr
+.SH DESCRIPTION
+.LP
+Hciattach is used to attach a serial UART to the Bluetooth stack as HCI
+transport interface.
+.SH OPTIONS
+.TP
+.B \-b
+Send break.
+.TP
+.B \-n
+Don't detach from controlling terminal.
+.TP
+.B \-p
+Print the PID when detaching.
+.TP
+.BI \-t " timeout"
+Specify an initialization timeout.  (Default is 5 seconds.)
+.TP
+.BI \-s " speed"
+Specify an initial speed instead of the hardware default.
+.TP
+.B \-l
+List all available configurations.
+.TP
+.B \-r
+Set the HCI device into raw mode (the kernel and bluetoothd will ignore it).
+.TP
+.I tty
+This specifies the serial device to attach. A leading
+.B /dev
+can be omitted. Examples:
+.B /dev/ttyS1
+.B ttyS2
+.TP
+.IR type \||\| id
+The
+.I type
+or
+.I id
+of the Bluetooth device that is to be attached, i.e. vendor or other device
+specific identifier. Currently supported types are
+.RS
+.TP
+.B type
+.B description
+.TP
+.B any
+Unspecified HCI_UART interface, no vendor specific options
+.TP
+.B ericsson
+Ericsson based modules
+.TP
+.B digi
+Digianswer based cards
+.TP
+.B xircom
+Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter
+.TP
+.B csr
+CSR Casira serial adapter or BrainBoxes serial dongle (BL642)
+.TP
+.B bboxes
+BrainBoxes PCMCIA card (BL620)
+.TP
+.B swave
+Silicon Wave kits
+.TP
+.B bcsp
+Serial adapters using CSR chips with BCSP serial protocol
+.TP
+.B ath3k
+Atheros AR300x based serial Bluetooth device
+.RE
+
+Supported IDs are (manufacturer id, product id)
+.RS
+.TP
+.B 0x0105, 0x080a
+Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter
+.TP
+.B 0x0160, 0x0002
+BrainBoxes PCMCIA card (BL620)
+.RE
+
+.TP
+.I speed
+The
+.I speed
+specifies the UART speed to use. Baudrates higher than 115.200bps require
+vendor specific initializations that are not implemented for all types of
+devices. In general the following speeds are supported:
+
+.B 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+
+Supported vendor devices are automatically initialised to their respective
+best settings.
+.TP
+.I flow
+If the keyword
+.I flow
+is appended to the list of options then hardware flow control is forced on
+the serial link (
+.B CRTSCTS
+). All above mentioned device types have
+.B flow
+set by default. To force no flow control use
+.B noflow
+instead.
+.TP
+.I sleep
+Enables hardware specific power management feature. If
+.I sleep
+is appended to the list of options then this feature is enabled. To disable
+this feature use
+.B nosleep
+instead.
+All above mentioned device types have
+.B nosleep
+set by default.
+
+Note: This option will only be valid for hardware which support
+hardware specific power management enable option from host.
+.TP
+.I bdaddr
+The
+.I bdaddr
+specifies the Bluetooth Address to use.  Some devices (like the STLC2500)
+do not store the Bluetooth address in hardware memory.  Instead it must
+be uploaded during the initialization process.  If this argument
+is specified, then the address will be used to initialize the device.
+Otherwise, a default address will be used.
+
+.SH AUTHORS
+Written by Maxim Krasnyansky <maxk@qualcomm.com>
+.PP
+Manual page by Nils Faerber <nils@kernelconcepts.de>
diff --git a/tools/hciattach.c b/tools/hciattach.c
new file mode 100644 (file)
index 0000000..23aa8c5
--- /dev/null
@@ -0,0 +1,1567 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <syslog.h>
+#include <termios.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "hciattach.h"
+
+#ifdef NEED_PPOLL
+#include "ppoll.h"
+#endif
+
+struct uart_t {
+       char *type;
+       int  m_id;
+       int  p_id;
+       int  proto;
+       int  init_speed;
+       int  speed;
+       int  flags;
+       int  pm;
+       char *bdaddr;
+       int  (*init) (int fd, struct uart_t *u, struct termios *ti);
+       int  (*post) (int fd, struct uart_t *u, struct termios *ti);
+#ifdef __TI_PATCH__
+       uint16_t device_param;
+#endif
+};
+
+#ifdef __TI_PATCH__
+       int firmware_path = 0;
+#endif
+
+#if defined(__TI_PATCH__) || defined(__BROADCOM_PATCH__)
+#define TIOSETBRFPOWER         0x6000
+#define BRF_DEEP_SLEEP_OPCODE_BYTE_1   0x0c
+#define BRF_DEEP_SLEEP_OPCODE_BYTE_2   0xfd
+#define BRF_DEEP_SLEEP_OPCODE          \
+       (BRF_DEEP_SLEEP_OPCODE_BYTE_1 | (BRF_DEEP_SLEEP_OPCODE_BYTE_2 << 8))
+#endif
+#define FLOW_CTL       0x0001
+#define ENABLE_PM      1
+#define DISABLE_PM     0
+
+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 sig_alarm(int sig)
+{
+       fprintf(stderr, "Initialization timed out.\n");
+       exit(1);
+}
+
+static int uart_speed(int s)
+{
+       switch (s) {
+       case 9600:
+               return B9600;
+       case 19200:
+               return B19200;
+       case 38400:
+               return B38400;
+       case 57600:
+               return B57600;
+       case 115200:
+               return B115200;
+       case 230400:
+               return B230400;
+       case 460800:
+               return B460800;
+       case 500000:
+               return B500000;
+       case 576000:
+               return B576000;
+       case 921600:
+               return B921600;
+       case 1000000:
+               return B1000000;
+       case 1152000:
+               return B1152000;
+       case 1500000:
+               return B1500000;
+       case 2000000:
+               return B2000000;
+#ifdef B2500000
+       case 2500000:
+               return B2500000;
+#endif
+#ifdef B3000000
+       case 3000000:
+               return B3000000;
+#endif
+#ifdef B3500000
+       case 3500000:
+               return B3500000;
+#endif
+#ifdef B4000000
+       case 4000000:
+               return B4000000;
+#endif
+       default:
+               return B57600;
+       }
+}
+
+int set_speed(int fd, struct termios *ti, int speed)
+{
+       if (cfsetospeed(ti, uart_speed(speed)) < 0)
+               return -errno;
+
+       if (cfsetispeed(ti, uart_speed(speed)) < 0)
+               return -errno;
+
+       if (tcsetattr(fd, TCSANOW, ti) < 0)
+               return -errno;
+
+       return 0;
+}
+
+/*
+ * Read an HCI event from the given file descriptor.
+ */
+int read_hci_event(int fd, unsigned char* buf, int size)
+{
+       int remain, r;
+       int count = 0;
+
+       if (size <= 0)
+               return -1;
+
+       /* The first byte identifies the packet type. For HCI event packets, it
+        * should be 0x04, so we read until we get to the 0x04. */
+       while (1) {
+               r = read(fd, buf, 1);
+               if (r <= 0)
+                       return -1;
+               if (buf[0] == 0x04)
+                       break;
+       }
+       count++;
+
+       /* The next two bytes are the event code and parameter total length. */
+       while (count < 3) {
+               r = read(fd, buf + count, 3 - count);
+               if (r <= 0)
+                       return -1;
+               count += r;
+       }
+
+       /* Now we read the parameters. */
+       if (buf[2] < (size - 3))
+               remain = buf[2];
+       else
+               remain = size - 3;
+
+       while ((count - 3) < remain) {
+               r = read(fd, buf + count, remain - (count - 3));
+               if (r <= 0)
+                       return -1;
+               count += r;
+       }
+
+       return count;
+}
+
+/*
+ * Ericsson specific initialization
+ */
+static int ericsson(int fd, struct uart_t *u, struct termios *ti)
+{
+       struct timespec tm = {0, 50000};
+       char cmd[5];
+
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x09;
+       cmd[2] = 0xfc;
+       cmd[3] = 0x01;
+
+       switch (u->speed) {
+       case 57600:
+               cmd[4] = 0x03;
+               break;
+       case 115200:
+               cmd[4] = 0x02;
+               break;
+       case 230400:
+               cmd[4] = 0x01;
+               break;
+       case 460800:
+               cmd[4] = 0x00;
+               break;
+       case 921600:
+               cmd[4] = 0x20;
+               break;
+       case 2000000:
+               cmd[4] = 0x25;
+               break;
+       case 3000000:
+               cmd[4] = 0x27;
+               break;
+       case 4000000:
+               cmd[4] = 0x2B;
+               break;
+       default:
+               cmd[4] = 0x03;
+               u->speed = 57600;
+               fprintf(stderr, "Invalid speed requested, using %d bps instead\n", u->speed);
+               break;
+       }
+
+       /* Send initialization command */
+       if (write(fd, cmd, 5) != 5) {
+               perror("Failed to write init command");
+               return -1;
+       }
+
+       nanosleep(&tm, NULL);
+       return 0;
+}
+
+/*
+ * Digianswer specific initialization
+ */
+static int digi(int fd, struct uart_t *u, struct termios *ti)
+{
+       struct timespec tm = {0, 50000};
+       char cmd[5];
+
+       /* DigiAnswer set baud rate command */
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x07;
+       cmd[2] = 0xfc;
+       cmd[3] = 0x01;
+
+       switch (u->speed) {
+       case 57600:
+               cmd[4] = 0x08;
+               break;
+       case 115200:
+               cmd[4] = 0x09;
+               break;
+       default:
+               cmd[4] = 0x09;
+               u->speed = 115200;
+               break;
+       }
+
+       /* Send initialization command */
+       if (write(fd, cmd, 5) != 5) {
+               perror("Failed to write init command");
+               return -1;
+       }
+
+       nanosleep(&tm, NULL);
+       return 0;
+}
+
+static int texas(int fd, struct uart_t *u, struct termios *ti)
+{
+       return texas_init(fd, ti);
+}
+
+static int texas2(int fd, struct uart_t *u, struct termios *ti)
+{
+       return texas_post(fd, ti);
+}
+
+static int texasalt(int fd, struct uart_t *u, struct termios *ti)
+{
+       return texasalt_init(fd, u->speed, ti);
+}
+
+static int ath3k_ps(int fd, struct uart_t *u, struct termios *ti)
+{
+       return ath3k_init(fd, u->speed, u->init_speed, u->bdaddr, ti);
+}
+
+static int ath3k_pm(int fd, struct uart_t *u, struct termios *ti)
+{
+       return ath3k_post(fd, u->pm);
+}
+
+static int qualcomm(int fd, struct uart_t *u, struct termios *ti)
+{
+       return qualcomm_init(fd, u->speed, ti, u->bdaddr);
+}
+
+static int read_check(int fd, void *buf, int count)
+{
+       int res;
+
+       do {
+               res = read(fd, buf, count);
+               if (res != -1) {
+                       buf += res;
+                       count -= res;
+               }
+       } while (count && (errno == 0 || errno == EINTR));
+
+       if (count)
+               return -1;
+
+       return 0;
+}
+
+/*
+ * BCSP specific initialization
+ */
+static int serial_fd;
+static int bcsp_max_retries = 10;
+
+static void bcsp_tshy_sig_alarm(int sig)
+{
+       unsigned char bcsp_sync_pkt[10] = {0xc0,0x00,0x41,0x00,0xbe,0xda,0xdc,0xed,0xed,0xc0};
+       int len;
+       static int retries = 0;
+
+       if (retries < bcsp_max_retries) {
+               retries++;
+               len = write(serial_fd, &bcsp_sync_pkt, 10);
+               alarm(1);
+               return;
+       }
+
+       tcflush(serial_fd, TCIOFLUSH);
+       fprintf(stderr, "BCSP initialization timed out\n");
+       exit(1);
+}
+
+static void bcsp_tconf_sig_alarm(int sig)
+{
+       unsigned char bcsp_conf_pkt[10] = {0xc0,0x00,0x41,0x00,0xbe,0xad,0xef,0xac,0xed,0xc0};
+       int len;
+       static int retries = 0;
+
+       if (retries < bcsp_max_retries){
+               retries++;
+               len = write(serial_fd, &bcsp_conf_pkt, 10);
+               alarm(1);
+               return;
+       }
+
+       tcflush(serial_fd, TCIOFLUSH);
+       fprintf(stderr, "BCSP initialization timed out\n");
+       exit(1);
+}
+
+static int bcsp(int fd, struct uart_t *u, struct termios *ti)
+{
+       unsigned char byte, bcsph[4], bcspp[4],
+               bcsp_sync_resp_pkt[10] = {0xc0,0x00,0x41,0x00,0xbe,0xac,0xaf,0xef,0xee,0xc0},
+               bcsp_conf_resp_pkt[10] = {0xc0,0x00,0x41,0x00,0xbe,0xde,0xad,0xd0,0xd0,0xc0},
+               bcspsync[4]     = {0xda, 0xdc, 0xed, 0xed},
+               bcspsyncresp[4] = {0xac,0xaf,0xef,0xee},
+               bcspconf[4]     = {0xad,0xef,0xac,0xed},
+               bcspconfresp[4] = {0xde,0xad,0xd0,0xd0};
+       struct sigaction sa;
+       int len;
+
+       if (set_speed(fd, ti, u->speed) < 0) {
+               perror("Can't set default baud rate");
+               return -1;
+       }
+
+       ti->c_cflag |= PARENB;
+       ti->c_cflag &= ~(PARODD);
+
+       if (tcsetattr(fd, TCSANOW, ti) < 0) {
+               perror("Can't set port settings");
+               return -1;
+       }
+
+       alarm(0);
+
+       serial_fd = fd;
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_flags = SA_NOCLDSTOP;
+       sa.sa_handler = bcsp_tshy_sig_alarm;
+       sigaction(SIGALRM, &sa, NULL);
+
+       /* State = shy */
+
+       bcsp_tshy_sig_alarm(0);
+       while (1) {
+               do {
+                       if (read_check(fd, &byte, 1) == -1){
+                               perror("Failed to read");
+                               return -1;
+                       }
+               } while (byte != 0xC0);
+
+               do {
+                       if ( read_check(fd, &bcsph[0], 1) == -1){
+                               perror("Failed to read");
+                               return -1;
+                       }
+               } while (bcsph[0] == 0xC0);
+
+               if ( read_check(fd, &bcsph[1], 3) == -1){
+                       perror("Failed to read");
+                       return -1;
+               }
+
+               if (((bcsph[0] + bcsph[1] + bcsph[2]) & 0xFF) != (unsigned char)~bcsph[3])
+                       continue;
+               if (bcsph[1] != 0x41 || bcsph[2] != 0x00)
+                       continue;
+
+               if (read_check(fd, &bcspp, 4) == -1){
+                       perror("Failed to read");
+                       return -1;
+               }
+
+               if (!memcmp(bcspp, bcspsync, 4)) {
+                       len = write(fd, &bcsp_sync_resp_pkt,10);
+               } else if (!memcmp(bcspp, bcspsyncresp, 4))
+                       break;
+       }
+
+       /* State = curious */
+
+       alarm(0);
+       sa.sa_handler = bcsp_tconf_sig_alarm;
+       sigaction(SIGALRM, &sa, NULL);
+       alarm(1);
+
+       while (1) {
+               do {
+                       if (read_check(fd, &byte, 1) == -1){
+                               perror("Failed to read");
+                               return -1;
+                       }
+               } while (byte != 0xC0);
+
+               do {
+                       if (read_check(fd, &bcsph[0], 1) == -1){
+                               perror("Failed to read");
+                               return -1;
+                       }
+               } while (bcsph[0] == 0xC0);
+
+               if (read_check(fd, &bcsph[1], 3) == -1){
+                       perror("Failed to read");
+                       return -1;
+               }
+
+               if (((bcsph[0] + bcsph[1] + bcsph[2]) & 0xFF) != (unsigned char)~bcsph[3])
+                       continue;
+
+               if (bcsph[1] != 0x41 || bcsph[2] != 0x00)
+                       continue;
+
+               if (read_check(fd, &bcspp, 4) == -1){
+                       perror("Failed to read");
+                       return -1;
+               }
+
+               if (!memcmp(bcspp, bcspsync, 4))
+                       len = write(fd, &bcsp_sync_resp_pkt, 10);
+               else if (!memcmp(bcspp, bcspconf, 4))
+                       len = write(fd, &bcsp_conf_resp_pkt, 10);
+               else if (!memcmp(bcspp, bcspconfresp,  4))
+                       break;
+       }
+
+       /* State = garrulous */
+
+       return 0;
+}
+
+/*
+ * CSR specific initialization
+ * Inspired strongly by code in OpenBT and experimentations with Brainboxes
+ * Pcmcia card.
+ * Jean Tourrilhes <jt@hpl.hp.com> - 14.11.01
+ */
+static int csr(int fd, struct uart_t *u, struct termios *ti)
+{
+       struct timespec tm = {0, 10000000};     /* 10ms - be generous */
+       unsigned char cmd[30];          /* Command */
+       unsigned char resp[30];         /* Response */
+       int  clen = 0;          /* Command len */
+       static int csr_seq = 0; /* Sequence number of command */
+       int  divisor;
+
+       /* It seems that if we set the CSR UART speed straight away, it
+        * won't work, the CSR UART gets into a state where we can't talk
+        * to it anymore.
+        * On the other hand, doing a read before setting the CSR speed
+        * seems to be ok.
+        * Therefore, the strategy is to read the build ID (useful for
+        * debugging) and only then set the CSR UART speed. Doing like
+        * this is more complex but at least it works ;-)
+        * The CSR UART control may be slow to wake up or something because
+        * every time I read its speed, its bogus...
+        * Jean II */
+
+       /* Try to read the build ID of the CSR chip */
+       clen = 5 + (5 + 6) * 2;
+       /* HCI header */
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x00;          /* CSR command */
+       cmd[2] = 0xfc;          /* MANUFACTURER_SPEC */
+       cmd[3] = 1 + (5 + 6) * 2;       /* len */
+       /* CSR MSG header */
+       cmd[4] = 0xC2;          /* first+last+channel=BCC */
+       /* CSR BCC header */
+       cmd[5] = 0x00;          /* type = GET-REQ */
+       cmd[6] = 0x00;          /* - msB */
+       cmd[7] = 5 + 4;         /* len */
+       cmd[8] = 0x00;          /* - msB */
+       cmd[9] = csr_seq & 0xFF;/* seq num */
+       cmd[10] = (csr_seq >> 8) & 0xFF;        /* - msB */
+       csr_seq++;
+       cmd[11] = 0x19;         /* var_id = CSR_CMD_BUILD_ID */
+       cmd[12] = 0x28;         /* - msB */
+       cmd[13] = 0x00;         /* status = STATUS_OK */
+       cmd[14] = 0x00;         /* - msB */
+       /* CSR BCC payload */
+       memset(cmd + 15, 0, 6 * 2);
+
+       /* Send command */
+       do {
+               if (write(fd, cmd, clen) != clen) {
+                       perror("Failed to write init command (GET_BUILD_ID)");
+                       return -1;
+               }
+
+               /* Read reply. */
+               if (read_hci_event(fd, resp, 100) < 0) {
+                       perror("Failed to read init response (GET_BUILD_ID)");
+                       return -1;
+               }
+
+       /* Event code 0xFF is for vendor-specific events, which is
+        * what we're looking for. */
+       } while (resp[1] != 0xFF);
+
+#ifdef CSR_DEBUG
+       {
+       char temp[512];
+       int i;
+       for (i=0; i < rlen; i++)
+               sprintf(temp + (i*3), "-%02X", resp[i]);
+       fprintf(stderr, "Reading CSR build ID %d [%s]\n", rlen, temp + 1);
+       // In theory, it should look like :
+       // 04-FF-13-FF-01-00-09-00-00-00-19-28-00-00-73-00-00-00-00-00-00-00
+       }
+#endif
+       /* Display that to user */
+       fprintf(stderr, "CSR build ID 0x%02X-0x%02X\n",
+               resp[15] & 0xFF, resp[14] & 0xFF);
+
+       /* Try to read the current speed of the CSR chip */
+       clen = 5 + (5 + 4)*2;
+       /* -- HCI header */
+       cmd[3] = 1 + (5 + 4)*2; /* len */
+       /* -- CSR BCC header -- */
+       cmd[9] = csr_seq & 0xFF;        /* seq num */
+       cmd[10] = (csr_seq >> 8) & 0xFF;        /* - msB */
+       csr_seq++;
+       cmd[11] = 0x02;         /* var_id = CONFIG_UART */
+       cmd[12] = 0x68;         /* - msB */
+
+#ifdef CSR_DEBUG
+       /* Send command */
+       do {
+               if (write(fd, cmd, clen) != clen) {
+                       perror("Failed to write init command (GET_BUILD_ID)");
+                       return -1;
+               }
+
+               /* Read reply. */
+               if (read_hci_event(fd, resp, 100) < 0) {
+                       perror("Failed to read init response (GET_BUILD_ID)");
+                       return -1;
+               }
+
+       /* Event code 0xFF is for vendor-specific events, which is
+        * what we're looking for. */
+       } while (resp[1] != 0xFF);
+
+       {
+       char temp[512];
+       int i;
+       for (i=0; i < rlen; i++)
+               sprintf(temp + (i*3), "-%02X", resp[i]);
+       fprintf(stderr, "Reading CSR UART speed %d [%s]\n", rlen, temp+1);
+       }
+#endif
+
+       if (u->speed > 1500000) {
+               fprintf(stderr, "Speed %d too high. Remaining at %d baud\n",
+                       u->speed, u->init_speed);
+               u->speed = u->init_speed;
+       } else if (u->speed != 57600 && uart_speed(u->speed) == B57600) {
+               /* Unknown speed. Why oh why can't we just pass an int to the kernel? */
+               fprintf(stderr, "Speed %d unrecognised. Remaining at %d baud\n",
+                       u->speed, u->init_speed);
+               u->speed = u->init_speed;
+       }
+       if (u->speed == u->init_speed)
+               return 0;
+
+       /* Now, create the command that will set the UART speed */
+       /* CSR BCC header */
+       cmd[5] = 0x02;                  /* type = SET-REQ */
+       cmd[6] = 0x00;                  /* - msB */
+       cmd[9] = csr_seq & 0xFF;        /* seq num */
+       cmd[10] = (csr_seq >> 8) & 0xFF;/* - msB */
+       csr_seq++;
+
+       divisor = (u->speed*64+7812)/15625;
+
+       /* No parity, one stop bit -> divisor |= 0x0000; */
+       cmd[15] = (divisor) & 0xFF;             /* divider */
+       cmd[16] = (divisor >> 8) & 0xFF;        /* - msB */
+       /* The rest of the payload will be 0x00 */
+
+#ifdef CSR_DEBUG
+       {
+       char temp[512];
+       int i;
+       for(i = 0; i < clen; i++)
+               sprintf(temp + (i*3), "-%02X", cmd[i]);
+       fprintf(stderr, "Writing CSR UART speed %d [%s]\n", clen, temp + 1);
+       // In theory, it should look like :
+       // 01-00-FC-13-C2-02-00-09-00-03-00-02-68-00-00-BF-0E-00-00-00-00-00-00
+       // 01-00-FC-13-C2-02-00-09-00-01-00-02-68-00-00-D8-01-00-00-00-00-00-00
+       }
+#endif
+
+       /* Send the command to set the CSR UART speed */
+       if (write(fd, cmd, clen) != clen) {
+               perror("Failed to write init command (SET_UART_SPEED)");
+               return -1;
+       }
+
+       nanosleep(&tm, NULL);
+       return 0;
+}
+
+/*
+ * Silicon Wave specific initialization
+ * Thomas Moser <thomas.moser@tmoser.ch>
+ */
+static int swave(int fd, struct uart_t *u, struct termios *ti)
+{
+       struct timespec tm = { 0, 500000 };
+       char cmd[10], rsp[100];
+       int r;
+
+       // Silicon Wave set baud rate command
+       // see HCI Vendor Specific Interface from Silicon Wave
+       // first send a "param access set" command to set the
+       // appropriate data fields in RAM. Then send a "HCI Reset
+       // Subcommand", e.g. "soft reset" to make the changes effective.
+
+       cmd[0] = HCI_COMMAND_PKT;       // it's a command packet
+       cmd[1] = 0x0B;                  // OCF 0x0B     = param access set
+       cmd[2] = 0xfc;                  // OGF bx111111 = vendor specific
+       cmd[3] = 0x06;                  // 6 bytes of data following
+       cmd[4] = 0x01;                  // param sub command
+       cmd[5] = 0x11;                  // tag 17 = 0x11 = HCI Transport Params
+       cmd[6] = 0x03;                  // length of the parameter following
+       cmd[7] = 0x01;                  // HCI Transport flow control enable
+       cmd[8] = 0x01;                  // HCI Transport Type = UART
+
+       switch (u->speed) {
+       case 19200:
+               cmd[9] = 0x03;
+               break;
+       case 38400:
+               cmd[9] = 0x02;
+               break;
+       case 57600:
+               cmd[9] = 0x01;
+               break;
+       case 115200:
+               cmd[9] = 0x00;
+               break;
+       default:
+               u->speed = 115200;
+               cmd[9] = 0x00;
+               break;
+       }
+
+       /* Send initialization command */
+       if (write(fd, cmd, 10) != 10) {
+               perror("Failed to write init command");
+               return -1;
+       }
+
+       // We should wait for a "GET Event" to confirm the success of
+       // the baud rate setting. Wait some time before reading. Better:
+       // read with timeout, parse data
+       // until correct answer, else error handling ... todo ...
+
+       nanosleep(&tm, NULL);
+
+       r = read(fd, rsp, sizeof(rsp));
+       if (r > 0) {
+               // guess it's okay, but we should parse the reply. But since
+               // I don't react on an error anyway ... todo
+               // Response packet format:
+               //  04  Event
+               //  FF  Vendor specific
+               //  07  Parameter length
+               //  0B  Subcommand
+               //  01  Setevent
+               //  11  Tag specifying HCI Transport Layer Parameter
+               //  03  length
+               //  01  flow on
+               //  01  Hci Transport type = Uart
+               //  xx  Baud rate set (see above)
+       } else {
+               // ups, got error.
+               return -1;
+       }
+
+       // we probably got the reply. Now we must send the "soft reset"
+       // which is standard HCI RESET.
+
+       cmd[0] = HCI_COMMAND_PKT;       // it's a command packet
+       cmd[1] = 0x03;
+       cmd[2] = 0x0c;
+       cmd[3] = 0x00;
+
+       /* Send reset command */
+       if (write(fd, cmd, 4) != 4) {
+               perror("Can't write Silicon Wave reset cmd.");
+               return -1;
+       }
+
+       nanosleep(&tm, NULL);
+
+       // now the uart baud rate on the silicon wave module is set and effective.
+       // change our own baud rate as well. Then there is a reset event comming in
+       // on the *new* baud rate. This is *undocumented*! The packet looks like this:
+       // 04 FF 01 0B (which would make that a confirmation of 0x0B = "Param
+       // subcommand class". So: change to new baud rate, read with timeout, parse
+       // data, error handling. BTW: all param access in Silicon Wave is done this way.
+       // Maybe this code would belong in a seperate file, or at least code reuse...
+
+       return 0;
+}
+
+/*
+ * ST Microelectronics specific initialization
+ * Marcel Holtmann <marcel@holtmann.org>
+ */
+static int st(int fd, struct uart_t *u, struct termios *ti)
+{
+       struct timespec tm = {0, 50000};
+       char cmd[5];
+
+       /* ST Microelectronics set baud rate command */
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x46;                  // OCF = Hci_Cmd_ST_Set_Uart_Baud_Rate
+       cmd[2] = 0xfc;                  // OGF = Vendor specific
+       cmd[3] = 0x01;
+
+       switch (u->speed) {
+       case 9600:
+               cmd[4] = 0x09;
+               break;
+       case 19200:
+               cmd[4] = 0x0b;
+               break;
+       case 38400:
+               cmd[4] = 0x0d;
+               break;
+       case 57600:
+               cmd[4] = 0x0e;
+               break;
+       case 115200:
+               cmd[4] = 0x10;
+               break;
+       case 230400:
+               cmd[4] = 0x12;
+               break;
+       case 460800:
+               cmd[4] = 0x13;
+               break;
+       case 921600:
+               cmd[4] = 0x14;
+               break;
+       default:
+               cmd[4] = 0x10;
+               u->speed = 115200;
+               break;
+       }
+
+       /* Send initialization command */
+       if (write(fd, cmd, 5) != 5) {
+               perror("Failed to write init command");
+               return -1;
+       }
+
+       nanosleep(&tm, NULL);
+       return 0;
+}
+
+static int stlc2500(int fd, struct uart_t *u, struct termios *ti)
+{
+       bdaddr_t bdaddr;
+       unsigned char resp[10];
+       int n;
+       int rvalue;
+
+       /* STLC2500 has an ericsson core */
+       rvalue = ericsson(fd, u, ti);
+       if (rvalue != 0)
+               return rvalue;
+
+#ifdef STLC2500_DEBUG
+       fprintf(stderr, "Setting speed\n");
+#endif
+       if (set_speed(fd, ti, u->speed) < 0) {
+               perror("Can't set baud rate");
+               return -1;
+       }
+
+#ifdef STLC2500_DEBUG
+       fprintf(stderr, "Speed set...\n");
+#endif
+
+       /* Read reply */
+       if ((n = read_hci_event(fd, resp, 10)) < 0) {
+               fprintf(stderr, "Failed to set baud rate on chip\n");
+               return -1;
+       }
+
+#ifdef STLC2500_DEBUG
+       for (i = 0; i < n; i++) {
+               fprintf(stderr, "resp[%d] = %02x\n", i, resp[i]);
+       }
+#endif
+
+       str2ba(u->bdaddr, &bdaddr);
+       return stlc2500_init(fd, &bdaddr);
+}
+
+static int bgb2xx(int fd, struct uart_t *u, struct termios *ti)
+{
+       bdaddr_t bdaddr;
+
+       str2ba(u->bdaddr, &bdaddr);
+
+       return bgb2xx_init(fd, &bdaddr);
+}
+
+/*
+ * Broadcom specific initialization
+ * Extracted from Jungo openrg
+ */
+static int bcm2035(int fd, struct uart_t *u, struct termios *ti)
+{
+       int n;
+       unsigned char cmd[30], resp[30];
+
+       /* Reset the BT Chip */
+       memset(cmd, 0, sizeof(cmd));
+       memset(resp, 0, sizeof(resp));
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x03;
+       cmd[2] = 0x0c;
+       cmd[3] = 0x00;
+
+       /* Send command */
+       if (write(fd, cmd, 4) != 4) {
+               fprintf(stderr, "Failed to write reset command\n");
+               return -1;
+       }
+
+       /* Read reply */
+       if ((n = read_hci_event(fd, resp, 4)) < 0) {
+               fprintf(stderr, "Failed to reset chip\n");
+               return -1;
+       }
+
+       if (u->bdaddr != NULL) {
+               /* Set BD_ADDR */
+               memset(cmd, 0, sizeof(cmd));
+               memset(resp, 0, sizeof(resp));
+               cmd[0] = HCI_COMMAND_PKT;
+               cmd[1] = 0x01;
+               cmd[2] = 0xfc;
+               cmd[3] = 0x06;
+               str2ba(u->bdaddr, (bdaddr_t *) (cmd + 4));
+
+               /* Send command */
+               if (write(fd, cmd, 10) != 10) {
+                       fprintf(stderr, "Failed to write BD_ADDR command\n");
+                       return -1;
+               }
+
+               /* Read reply */
+               if ((n = read_hci_event(fd, resp, 10)) < 0) {
+                       fprintf(stderr, "Failed to set BD_ADDR\n");
+                       return -1;
+               }
+       }
+
+       /* Read the local version info */
+       memset(cmd, 0, sizeof(cmd));
+       memset(resp, 0, sizeof(resp));
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x01;
+       cmd[2] = 0x10;
+       cmd[3] = 0x00;
+
+       /* Send command */
+       if (write(fd, cmd, 4) != 4) {
+               fprintf(stderr, "Failed to write \"read local version\" "
+                       "command\n");
+               return -1;
+       }
+
+       /* Read reply */
+       if ((n = read_hci_event(fd, resp, 4)) < 0) {
+               fprintf(stderr, "Failed to read local version\n");
+               return -1;
+       }
+
+       /* Read the local supported commands info */
+       memset(cmd, 0, sizeof(cmd));
+       memset(resp, 0, sizeof(resp));
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x02;
+       cmd[2] = 0x10;
+       cmd[3] = 0x00;
+
+       /* Send command */
+       if (write(fd, cmd, 4) != 4) {
+               fprintf(stderr, "Failed to write \"read local supported "
+                                               "commands\" command\n");
+               return -1;
+       }
+
+       /* Read reply */
+       if ((n = read_hci_event(fd, resp, 4)) < 0) {
+               fprintf(stderr, "Failed to read local supported commands\n");
+               return -1;
+       }
+
+       /* Set the baud rate */
+       memset(cmd, 0, sizeof(cmd));
+       memset(resp, 0, sizeof(resp));
+#ifndef __BROADCOM_PATCH__
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x18;
+       cmd[2] = 0xfc;
+       cmd[3] = 0x02;
+       switch (u->speed) {
+       case 57600:
+               cmd[4] = 0x00;
+               cmd[5] = 0xe6;
+               break;
+       case 230400:
+               cmd[4] = 0x22;
+               cmd[5] = 0xfa;
+               break;
+       case 460800:
+               cmd[4] = 0x22;
+               cmd[5] = 0xfd;
+               break;
+       case 921600:
+               cmd[4] = 0x55;
+               cmd[5] = 0xff;
+               break;
+       default:
+               /* Default is 115200 */
+               cmd[4] = 0x00;
+               cmd[5] = 0xf3;
+               break;
+       }
+       fprintf(stderr, "Baud rate parameters: DHBR=0x%2x,DLBR=0x%2x\n",
+               cmd[4], cmd[5]);
+
+       /* Send command */
+       if (write(fd, cmd, 6) != 6) {
+               fprintf(stderr, "Failed to write \"set baud rate\" command\n");
+               return -1;
+       }
+#else
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x18;
+       cmd[2] = 0xfc;
+
+       switch (u->speed) {
+       case 57600:
+       case 230400:
+       case 460800:
+       case 921600:
+       case 3000000:
+               break;
+       default:
+               break;
+       }
+
+       cmd[3] = 0x06;
+       cmd[4] = 0x00;
+       cmd[5] = 0x00;
+       cmd[6] = u->speed & 0xFF;
+       cmd[7] = (u->speed >> 8) & 0xFF;
+       cmd[8] = (u->speed >> 16) & 0xFF;
+       cmd[9] = (u->speed >> 24) & 0xFF;
+
+       fprintf(stderr, "Set the baud rate %d : 0x%02x,0x%02x,0x%02x,0x%02x\n",u->speed,cmd[6],cmd[7],cmd[8],cmd[9] );
+
+       /* Send command */
+       if (write(fd, cmd, 10) != 10) {
+               fprintf(stderr, "Failed to write \"set baud rate\" command\n");
+               return -1;
+       }
+
+#endif
+
+       if ((n = read_hci_event(fd, resp, 6)) < 0) {
+               fprintf(stderr, "Failed to set baud rate\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+struct uart_t uart[] = {
+       { "any",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
+                               FLOW_CTL, DISABLE_PM, NULL, NULL     },
+
+       { "ericsson",   0x0000, 0x0000, HCI_UART_H4,   57600,  115200,
+                               FLOW_CTL, DISABLE_PM, NULL, ericsson },
+
+       { "digi",       0x0000, 0x0000, HCI_UART_H4,   9600,   115200,
+                               FLOW_CTL, DISABLE_PM, NULL, digi     },
+
+       { "bcsp",       0x0000, 0x0000, HCI_UART_BCSP, 115200, 115200,
+                               0, DISABLE_PM, NULL, bcsp     },
+
+       /* Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter */
+       { "xircom",     0x0105, 0x080a, HCI_UART_H4,   115200, 115200,
+                               FLOW_CTL, DISABLE_PM,  NULL, NULL     },
+
+       /* CSR Casira serial adapter or BrainBoxes serial dongle (BL642) */
+       { "csr",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
+                               FLOW_CTL, DISABLE_PM, NULL, csr      },
+
+       /* BrainBoxes PCMCIA card (BL620) */
+       { "bboxes",     0x0160, 0x0002, HCI_UART_H4,   115200, 460800,
+                               FLOW_CTL, DISABLE_PM, NULL, csr      },
+
+       /* Silicon Wave kits */
+       { "swave",      0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
+                               FLOW_CTL, DISABLE_PM, NULL, swave    },
+
+#ifdef __TI_PATCH__
+       /* Texas Instruments BRF63xx modules */
+       { "texas",      0x0000, 0x0000, HCI_UART_LL,   115200,3000000, FLOW_CTL, NULL, texas,    NULL/*texas_continue_script*/,    BRF_DEEP_SLEEP_OPCODE},
+#else
+       /* Texas Instruments Bluelink (BRF) modules */
+       { "texas",      0x0000, 0x0000, HCI_UART_LL,   115200, 115200, FLOW_CTL, NULL, texas,    texas2 },
+       { "texasalt",   0x0000, 0x0000, HCI_UART_LL,   115200, 115200, FLOW_CTL, NULL, texasalt, NULL   },
+#endif
+
+       /* ST Microelectronics minikits based on STLC2410/STLC2415 */
+       { "st",         0x0000, 0x0000, HCI_UART_H4,    57600, 115200,
+                               FLOW_CTL, DISABLE_PM,  NULL, st       },
+
+       /* ST Microelectronics minikits based on STLC2500 */
+       { "stlc2500",   0x0000, 0x0000, HCI_UART_H4, 115200, 115200,
+                       FLOW_CTL, DISABLE_PM, "00:80:E1:00:AB:BA", stlc2500 },
+
+       /* Philips generic Ericsson IP core based */
+       { "philips",    0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
+                               FLOW_CTL, DISABLE_PM, NULL, NULL     },
+
+       /* Philips BGB2xx Module */
+       { "bgb2xx",    0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
+                       FLOW_CTL, DISABLE_PM, "BD:B2:10:00:AB:BA", bgb2xx },
+
+       /* Sphinx Electronics PICO Card */
+       { "picocard",   0x025e, 0x1000, HCI_UART_H4, 115200, 115200,
+                               FLOW_CTL, DISABLE_PM, NULL, NULL     },
+
+       /* Inventel BlueBird Module */
+       { "inventel",   0x0000, 0x0000, HCI_UART_H4, 115200, 115200,
+                               FLOW_CTL, DISABLE_PM, NULL, NULL     },
+
+       /* COM One Platinium Bluetooth PC Card */
+       { "comone",     0xffff, 0x0101, HCI_UART_BCSP, 115200, 115200,
+                               0, DISABLE_PM,  NULL, bcsp     },
+
+       /* TDK Bluetooth PC Card and IBM Bluetooth PC Card II */
+       { "tdk",        0x0105, 0x4254, HCI_UART_BCSP, 115200, 115200,
+                               0, DISABLE_PM, NULL, bcsp     },
+
+       /* Socket Bluetooth CF Card (Rev G) */
+       { "socket",     0x0104, 0x0096, HCI_UART_BCSP, 230400, 230400,
+                               0, DISABLE_PM, NULL, bcsp     },
+
+       /* 3Com Bluetooth Card (Version 3.0) */
+       { "3com",       0x0101, 0x0041, HCI_UART_H4,   115200, 115200,
+                               FLOW_CTL, DISABLE_PM, NULL, csr      },
+
+       /* AmbiCom BT2000C Bluetooth PC/CF Card */
+       { "bt2000c",    0x022d, 0x2000, HCI_UART_H4,    57600, 460800,
+                               FLOW_CTL, DISABLE_PM, NULL, csr      },
+
+       /* Zoom Bluetooth PCMCIA Card */
+       { "zoom",       0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200,
+                               0, DISABLE_PM, NULL, bcsp     },
+
+       /* Sitecom CN-504 PCMCIA Card */
+       { "sitecom",    0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200,
+                               0, DISABLE_PM, NULL, bcsp     },
+
+       /* Billionton PCBTC1 PCMCIA Card */
+       { "billionton", 0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200,
+                               0, DISABLE_PM, NULL, bcsp     },
+
+       /* Broadcom BCM2035 */
+       { "bcm2035",    0x0A5C, 0x2035, HCI_UART_H4,   115200, 460800,
+                               FLOW_CTL, DISABLE_PM, NULL, bcm2035  },
+
+       { "ath3k",    0x0000, 0x0000, HCI_UART_ATH3K, 115200, 115200,
+                       FLOW_CTL, DISABLE_PM, NULL, ath3k_ps, ath3k_pm  },
+
+       /* QUALCOMM BTS */
+       { "qualcomm",   0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
+                       FLOW_CTL, DISABLE_PM, NULL, qualcomm, NULL },
+
+       { NULL, 0 }
+};
+
+static struct uart_t * get_by_id(int m_id, int p_id)
+{
+       int i;
+       for (i = 0; uart[i].type; i++) {
+               if (uart[i].m_id == m_id && uart[i].p_id == p_id)
+                       return &uart[i];
+       }
+       return NULL;
+}
+
+static struct uart_t * get_by_type(char *type)
+{
+       int i;
+       for (i = 0; uart[i].type; i++) {
+               if (!strcmp(uart[i].type, type))
+                       return &uart[i];
+       }
+       return NULL;
+}
+
+/* Initialize UART driver */
+static int init_uart(char *dev, struct uart_t *u, int send_break, int raw)
+{
+       struct termios ti;
+       int fd, i;
+       unsigned long flags = 0;
+
+#if defined(__TI_PATCH__) || defined(__BROADCOM_PATCH__)
+       int power;
+#endif
+       if (raw)
+               flags |= 1 << HCI_UART_RAW_DEVICE;
+
+       fd = open(dev, O_RDWR | O_NOCTTY);
+       if (fd < 0) {
+               perror("Can't open serial port");
+               return -1;
+       }
+
+       tcflush(fd, TCIOFLUSH);
+
+       if (tcgetattr(fd, &ti) < 0) {
+               perror("Can't get port settings");
+               return -1;
+       }
+
+       cfmakeraw(&ti);
+
+#ifndef __BROADCOM_PATCH__
+       ti.c_cflag |= CLOCAL;
+       if (u->flags & FLOW_CTL)
+               ti.c_cflag |= CRTSCTS;
+       else
+               ti.c_cflag &= ~CRTSCTS;
+#endif
+
+       if (tcsetattr(fd, TCSANOW, &ti) < 0) {
+               perror("Can't set port settings");
+               return -1;
+       }
+
+       /* Set initial baudrate */
+       if (set_speed(fd, &ti, u->init_speed) < 0) {
+               perror("Can't set initial baud rate");
+               return -1;
+       }
+
+       tcflush(fd, TCIOFLUSH);
+
+       if (send_break) {
+               tcsendbreak(fd, 0);
+               usleep(500000);
+       }
+#if defined(__TI_PATCH__) || defined(__BROADCOM_PATCH__)
+       /* Power up the BRF chip */
+       power = 1;
+       ioctl(fd, TIOSETBRFPOWER, &power);
+#endif
+#ifdef __TI_PATCH__
+       usleep(500000);
+#endif
+
+       if (u->init && u->init(fd, u, &ti) < 0)
+               return -1;
+
+       tcflush(fd, TCIOFLUSH);
+
+       /* Set actual baudrate */
+       if (set_speed(fd, &ti, u->speed) < 0) {
+               perror("Can't set baud rate");
+               return -1;
+       }
+
+       /* Set TTY to N_HCI line discipline */
+       i = N_HCI;
+       if (ioctl(fd, TIOCSETD, &i) < 0) {
+               perror("Can't set line discipline");
+               return -1;
+       }
+
+       if (flags && ioctl(fd, HCIUARTSETFLAGS, flags) < 0) {
+               perror("Can't set UART flags");
+               return -1;
+       }
+
+       if (ioctl(fd, HCIUARTSETPROTO, u->proto) < 0) {
+               perror("Can't set device");
+               return -1;
+       }
+
+       if (u->post && u->post(fd, u, &ti) < 0)
+               return -1;
+
+       return fd;
+}
+
+static void usage(void)
+{
+       printf("hciattach - HCI UART driver initialization utility\n");
+       printf("Usage:\n");
+#ifdef __TI_PATCH__
+       printf("\thciattach [-n] [-p] [-b] [-g device_param] [-r] [-f] [-t timeout] [-s initial_speed] <tty> <type | id> [speed] [flow|noflow] [bdaddr]\n");
+#else
+       printf("\thciattach [-n] [-p] [-b] [-r] [-t timeout] [-s initial_speed] <tty> <type | id> [speed] [flow|noflow] [bdaddr]\n");
+#endif
+       printf("\thciattach -l\n");
+}
+
+int main(int argc, char *argv[])
+{
+       struct uart_t *u = NULL;
+       int detach, printpid, raw, opt, i, n, ld, err;
+       int to = 10;
+       int init_speed = 0;
+       int send_break = 0;
+       pid_t pid;
+       struct sigaction sa;
+       struct pollfd p;
+       sigset_t sigs;
+       char dev[PATH_MAX];
+
+#if defined(__TI_PATCH__) || defined(__BROADCOM_PATCH__)
+       int power;
+#endif
+#ifdef __TI_PATCH__
+       uint16_t device_param = 0;
+       int reset_device = 0;
+       int bt_fd;
+#endif
+       detach = 1;
+       printpid = 0;
+       raw = 0;
+#ifdef __TI_PATCH__
+       while ((opt=getopt(argc, argv, "bnprft:g:s:l")) != EOF)
+#else
+       while ((opt=getopt(argc, argv, "bnpt:s:l")) != EOF)
+#endif
+       {
+               switch(opt) {
+               case 'b':
+                       send_break = 1;
+                       break;
+
+               case 'n':
+                       detach = 0;
+                       break;
+
+               case 'p':
+                       printpid = 1;
+                       break;
+
+               case 't':
+                       to = atoi(optarg);
+                       break;
+
+#ifdef __TI_PATCH__
+               case 'g':
+                       device_param = (uint16_t)strtol(optarg, NULL, 16);
+                       break;
+
+               case 'r':
+                       reset_device = 1;
+                       break;
+
+               case 'f':
+                       firmware_path = 1;
+                       break;
+#endif
+               case 's':
+                       init_speed = atoi(optarg);
+                       break;
+
+               case 'l':
+                       for (i = 0; uart[i].type; i++) {
+                               printf("%-10s0x%04x,0x%04x\n", uart[i].type,
+                                                       uart[i].m_id, uart[i].p_id);
+                       }
+                       exit(0);
+
+               case 'r':
+                       raw = 1;
+                       break;
+
+               default:
+                       usage();
+                       exit(1);
+               }
+       }
+
+       n = argc - optind;
+#ifdef __TI_PATCH__
+       if (!reset_device || (reset_device && n < 1))
+#endif
+       if (n < 2) {
+               usage();
+               exit(1);
+       }
+
+       for (n = 0; optind < argc; n++, optind++) {
+               char *opt;
+
+               opt = argv[optind];
+
+               switch(n) {
+               case 0:
+                       dev[0] = 0;
+                       if (!strchr(opt, '/'))
+                               strcpy(dev, "/dev/");
+                       strcat(dev, opt);
+                       break;
+
+               case 1:
+                       if (strchr(argv[optind], ',')) {
+                               int m_id, p_id;
+                               sscanf(argv[optind], "%x,%x", &m_id, &p_id);
+                               u = get_by_id(m_id, p_id);
+                       } else {
+                               u = get_by_type(opt);
+                       }
+
+                       if (!u) {
+                               fprintf(stderr, "Unknown device type or id\n");
+                               exit(1);
+                       }
+
+                       break;
+
+               case 2:
+                       u->speed = atoi(argv[optind]);
+                       break;
+
+               case 3:
+                       if (!strcmp("flow", argv[optind]))
+                               u->flags |=  FLOW_CTL;
+                       else
+                               u->flags &= ~FLOW_CTL;
+                       break;
+
+               case 4:
+                       if (!strcmp("sleep", argv[optind]))
+                               u->pm = ENABLE_PM;
+                       else
+                               u->pm = DISABLE_PM;
+                       break;
+
+               case 5:
+                       u->bdaddr = argv[optind];
+                       break;
+               }
+       }
+#ifdef __TI_PATCH__
+       if (reset_device)
+       {
+               // Reset row device
+               bt_fd = open(dev, O_RDWR | O_NOCTTY);
+               if (bt_fd< 0) {
+                       perror("Can't open serial port");
+                       return -1;
+               }
+               /* Power up the BRF chip */
+               power = 0;
+               ioctl(bt_fd, TIOSETBRFPOWER, &power);
+               return 0;
+       }
+#endif
+
+       if (!u) {
+               fprintf(stderr, "Unknown device type or id\n");
+               exit(1);
+       }
+
+       /* If user specified a initial speed, use that instead of
+          the hardware's default */
+       if (init_speed)
+               u->init_speed = init_speed;
+#ifdef __TI_PATCH__
+       /* If user specified a device parameter, use that instead of
+          the hardware's default */
+       if (device_param)
+               u->device_param = device_param;
+#endif
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_flags   = SA_NOCLDSTOP;
+       sa.sa_handler = sig_alarm;
+       sigaction(SIGALRM, &sa, NULL);
+
+       /* 10 seconds should be enough for initialization */
+       alarm(to);
+       bcsp_max_retries = to;
+
+       n = init_uart(dev, u, send_break, raw);
+       if (n < 0) {
+               perror("Can't initialize device");
+               exit(1);
+       }
+
+       printf("Device setup complete\n");
+
+       alarm(0);
+
+       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) {
+               if ((pid = fork())) {
+                       if (printpid)
+                               printf("%d\n", pid);
+                       return 0;
+               }
+
+               for (i = 0; i < 20; i++)
+                       if (i != n)
+                               close(i);
+       }
+
+       p.fd = n;
+       p.events = POLLERR | POLLHUP;
+
+       sigfillset(&sigs);
+       sigdelset(&sigs, SIGCHLD);
+       sigdelset(&sigs, SIGPIPE);
+       sigdelset(&sigs, SIGTERM);
+       sigdelset(&sigs, SIGINT);
+       sigdelset(&sigs, SIGHUP);
+
+       while (!__io_canceled) {
+               p.revents = 0;
+               err = ppoll(&p, 1, NULL, &sigs);
+               if (err < 0 && errno == EINTR)
+                       continue;
+               if (err)
+                       break;
+       }
+
+       /* Restore TTY line discipline */
+       ld = N_TTY;
+       if (ioctl(n, TIOCSETD, &ld) < 0) {
+               perror("Can't restore line discipline");
+               exit(1);
+       }
+
+#if defined(__TI_PATCH__) || defined(__BROADCOM_PATCH__)
+       /* Power down the BRF or BCMchip */
+       power = 0;
+       ioctl(n, TIOSETBRFPOWER, &power);
+#endif
+       return 0;
+}
diff --git a/tools/hciattach.h b/tools/hciattach.h
new file mode 100644 (file)
index 0000000..fed0d11
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <termios.h>
+
+#ifndef N_HCI
+#define N_HCI  15
+#endif
+
+#define HCIUARTSETPROTO                _IOW('U', 200, int)
+#define HCIUARTGETPROTO                _IOR('U', 201, int)
+#define HCIUARTGETDEVICE       _IOR('U', 202, int)
+#define HCIUARTSETFLAGS                _IOW('U', 203, int)
+#define HCIUARTGETFLAGS                _IOR('U', 204, int)
+
+#define HCI_UART_H4    0
+#define HCI_UART_BCSP  1
+#define HCI_UART_3WIRE 2
+#define HCI_UART_H4DS  3
+#define HCI_UART_LL    4
+#define HCI_UART_ATH3K  5
+
+#define HCI_UART_RAW_DEVICE    0
+
+int read_hci_event(int fd, unsigned char* buf, int size);
+int set_speed(int fd, struct termios *ti, int speed);
+
+int texas_init(int fd, struct termios *ti);
+int texas_post(int fd, struct termios *ti);
+int texasalt_init(int fd, int speed, struct termios *ti);
+int stlc2500_init(int fd, bdaddr_t *bdaddr);
+int bgb2xx_init(int dd, bdaddr_t *bdaddr);
+int ath3k_init(int fd, int speed, int init_speed, char *bdaddr,
+                                               struct termios *ti);
+int ath3k_post(int fd, int pm);
+int qualcomm_init(int fd, int speed, struct termios *ti, const char *bdaddr);
diff --git a/tools/hciattach_ath3k.c b/tools/hciattach_ath3k.c
new file mode 100644 (file)
index 0000000..728e660
--- /dev/null
@@ -0,0 +1,1049 @@
+/*
+ *  Copyright (c) 2009-2010 Atheros Communications 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "hciattach.h"
+
+#define TRUE    1
+#define FALSE   0
+
+#define FW_PATH "/lib/firmware/ar3k/"
+
+struct ps_cfg_entry {
+       uint32_t id;
+       uint32_t len;
+       uint8_t *data;
+};
+
+struct ps_entry_type {
+       unsigned char type;
+       unsigned char array;
+};
+
+#define MAX_TAGS              50
+#define PS_HDR_LEN            4
+#define HCI_VENDOR_CMD_OGF    0x3F
+#define HCI_PS_CMD_OCF        0x0B
+
+struct ps_cfg_entry ps_list[MAX_TAGS];
+
+static void load_hci_ps_hdr(uint8_t *cmd, uint8_t ps_op, int len, int index)
+{
+       hci_command_hdr *ch = (void *)cmd;
+
+       ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF,
+                                               HCI_PS_CMD_OCF));
+       ch->plen = len + PS_HDR_LEN;
+       cmd += HCI_COMMAND_HDR_SIZE;
+
+       cmd[0] = ps_op;
+       cmd[1] = index;
+       cmd[2] = index >> 8;
+       cmd[3] = len;
+}
+
+#define PS_EVENT_LEN 100
+
+/*
+ * Send HCI command and wait for command complete event.
+ * The event buffer has to be freed by the caller.
+ */
+static int send_hci_cmd_sync(int dev, uint8_t *cmd, int len, uint8_t **event)
+{
+       int err;
+       uint8_t *hci_event;
+       uint8_t pkt_type = HCI_COMMAND_PKT;
+
+       if (len == 0)
+               return len;
+
+       if (write(dev, &pkt_type, 1) != 1)
+               return -EILSEQ;
+       if (write(dev, (unsigned char *)cmd, len) != len)
+               return -EILSEQ;
+
+       hci_event = (uint8_t *)malloc(PS_EVENT_LEN);
+       if (!hci_event)
+               return -ENOMEM;
+
+       err = read_hci_event(dev, (unsigned char *)hci_event, PS_EVENT_LEN);
+       if (err > 0) {
+               *event = hci_event;
+       } else {
+               free(hci_event);
+               return -EILSEQ;
+       }
+
+       return len;
+}
+
+#define HCI_EV_SUCCESS        0x00
+
+static int read_ps_event(uint8_t *event, uint16_t ocf)
+{
+       hci_event_hdr *eh;
+       uint16_t opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF, ocf));
+
+       event++;
+
+       eh = (void *)event;
+       event += HCI_EVENT_HDR_SIZE;
+
+       if (eh->evt == EVT_CMD_COMPLETE) {
+               evt_cmd_complete *cc = (void *)event;
+
+               event += EVT_CMD_COMPLETE_SIZE;
+
+               if (cc->opcode == opcode && event[0] == HCI_EV_SUCCESS)
+                       return 0;
+               else
+                       return -EILSEQ;
+       }
+
+       return -EILSEQ;
+}
+
+static int write_cmd(int fd, uint8_t *buffer, int len)
+{
+       uint8_t *event;
+       int err;
+
+       err = send_hci_cmd_sync(fd, buffer, len, &event);
+       if (err < 0)
+               return err;
+
+       err = read_ps_event(event, HCI_PS_CMD_OCF);
+       if (event)
+               free(event);
+
+       return err;
+}
+
+#define PS_WRITE           1
+#define PS_RESET           2
+#define WRITE_PATCH        8
+#define ENABLE_PATCH       11
+
+#define HCI_PS_CMD_HDR_LEN 7
+
+#define PS_RESET_PARAM_LEN 6
+#define HCI_MAX_CMD_SIZE   260
+#define PS_RESET_CMD_LEN   (HCI_PS_CMD_HDR_LEN + PS_RESET_PARAM_LEN)
+
+#define PS_ID_MASK         0xFF
+
+/* Sends PS commands using vendor specficic HCI commands */
+static int write_ps_cmd(int fd, uint8_t opcode, uint32_t ps_param)
+{
+       uint8_t cmd[HCI_MAX_CMD_SIZE];
+       uint32_t i;
+
+       switch (opcode) {
+       case ENABLE_PATCH:
+               load_hci_ps_hdr(cmd, opcode, 0, 0x00);
+
+               if (write_cmd(fd, cmd, HCI_PS_CMD_HDR_LEN) < 0)
+                       return -EILSEQ;
+               break;
+
+       case PS_RESET:
+               load_hci_ps_hdr(cmd, opcode, PS_RESET_PARAM_LEN, 0x00);
+
+               cmd[7] = 0x00;
+               cmd[PS_RESET_CMD_LEN - 2] = ps_param & PS_ID_MASK;
+               cmd[PS_RESET_CMD_LEN - 1] = (ps_param >> 8) & PS_ID_MASK;
+
+               if (write_cmd(fd, cmd, PS_RESET_CMD_LEN) < 0)
+                       return -EILSEQ;
+               break;
+
+       case PS_WRITE:
+               for (i = 0; i < ps_param; i++) {
+                       load_hci_ps_hdr(cmd, opcode, ps_list[i].len,
+                                                       ps_list[i].id);
+
+                       memcpy(&cmd[HCI_PS_CMD_HDR_LEN], ps_list[i].data,
+                                                       ps_list[i].len);
+
+                       if (write_cmd(fd, cmd, ps_list[i].len +
+                                               HCI_PS_CMD_HDR_LEN) < 0)
+                               return -EILSEQ;
+               }
+               break;
+       }
+
+       return 0;
+}
+
+#define __is_delim(ch) ((ch) == ':')
+#define MAX_PREAMBLE_LEN 4
+
+/* Parse PS entry preamble of format [X:X] for main type and subtype */
+static int get_ps_type(char *ptr, int index, char *type, char *sub_type)
+{
+       int i;
+       int delim = FALSE;
+
+       if (index > MAX_PREAMBLE_LEN)
+               return -EILSEQ;
+
+       for (i = 1; i < index; i++) {
+               if (__is_delim(ptr[i])) {
+                       delim = TRUE;
+                       continue;
+               }
+
+               if (isalpha(ptr[i])) {
+                       if (delim == FALSE)
+                               (*type) = toupper(ptr[i]);
+                       else
+                               (*sub_type) = toupper(ptr[i]);
+               }
+       }
+
+       return 0;
+}
+
+#define ARRAY   'A'
+#define STRING  'S'
+#define DECIMAL 'D'
+#define BINARY  'B'
+
+#define PS_HEX           0
+#define PS_DEC           1
+
+static int get_input_format(char *buf, struct ps_entry_type *format)
+{
+       char *ptr = NULL;
+       char type = '\0';
+       char sub_type = '\0';
+
+       format->type = PS_HEX;
+       format->array = TRUE;
+
+       if (strstr(buf, "[") != buf)
+               return 0;
+
+       ptr = strstr(buf, "]");
+       if (!ptr)
+               return -EILSEQ;
+
+       if (get_ps_type(buf, ptr - buf, &type, &sub_type) < 0)
+               return -EILSEQ;
+
+       /* Check is data type is of array */
+       if (type == ARRAY || sub_type == ARRAY)
+               format->array = TRUE;
+
+       if (type == STRING || sub_type == STRING)
+               format->array = FALSE;
+
+       if (type == DECIMAL || type == BINARY)
+               format->type = PS_DEC;
+       else
+               format->type = PS_HEX;
+
+       return 0;
+}
+
+#define UNDEFINED 0xFFFF
+
+static unsigned int read_data_in_section(char *buf, struct ps_entry_type type)
+{
+       char *ptr = buf;
+
+       if (!buf)
+               return UNDEFINED;
+
+       if (buf == strstr(buf, "[")) {
+               ptr = strstr(buf, "]");
+               if (!ptr)
+                       return UNDEFINED;
+
+               ptr++;
+       }
+
+       if (type.type == PS_HEX && type.array != TRUE)
+               return strtol(ptr, NULL, 16);
+
+       return UNDEFINED;
+}
+
+struct tag_info {
+       unsigned section;
+       unsigned line_count;
+       unsigned char_cnt;
+       unsigned byte_count;
+};
+
+static inline int update_char_count(const char *buf)
+{
+       char *end_ptr;
+
+       if (strstr(buf, "[") == buf) {
+               end_ptr = strstr(buf, "]");
+               if (!end_ptr)
+                       return 0;
+               else
+                       return (end_ptr - buf) + 1;
+       }
+
+       return 0;
+}
+
+/* Read PS entries as string, convert and add to Hex array */
+static void update_tag_data(struct ps_cfg_entry *tag,
+                               struct tag_info *info, const char *ptr)
+{
+       char buf[3];
+
+       buf[2] = '\0';
+
+       strncpy(buf, &ptr[info->char_cnt], 2);
+       tag->data[info->byte_count] = strtol(buf, NULL, 16);
+       info->char_cnt += 3;
+       info->byte_count++;
+
+       strncpy(buf, &ptr[info->char_cnt], 2);
+       tag->data[info->byte_count] = strtol(buf, NULL, 16);
+       info->char_cnt += 3;
+       info->byte_count++;
+}
+
+#define PS_UNDEF   0
+#define PS_ID      1
+#define PS_LEN     2
+#define PS_DATA    3
+
+#define PS_MAX_LEN         500
+#define LINE_SIZE_MAX      (PS_MAX_LEN * 2)
+#define ENTRY_PER_LINE     16
+
+#define __check_comment(buf) (((buf)[0] == '/') && ((buf)[1] == '/'))
+#define __skip_space(str)      while (*(str) == ' ') ((str)++)
+
+static int ath_parse_ps(FILE *stream)
+{
+       char buf[LINE_SIZE_MAX + 1];
+       char *ptr;
+       uint8_t tag_cnt = 0;
+       int16_t byte_count = 0;
+       struct ps_entry_type format;
+       struct tag_info status = { 0, 0, 0, 0 };
+
+       do {
+               int read_count;
+               struct ps_cfg_entry *tag;
+
+               ptr = fgets(buf, LINE_SIZE_MAX, stream);
+               if (!ptr)
+                       break;
+
+               __skip_space(ptr);
+               if (__check_comment(ptr))
+                       continue;
+
+               /* Lines with a '#' will be followed by new PS entry */
+               if (ptr == strstr(ptr, "#")) {
+                       if (status.section != PS_UNDEF) {
+                               return -EILSEQ;
+                       } else {
+                               status.section = PS_ID;
+                               continue;
+                       }
+               }
+
+               tag = &ps_list[tag_cnt];
+
+               switch (status.section) {
+               case PS_ID:
+                       if (get_input_format(ptr, &format) < 0)
+                               return -EILSEQ;
+
+                       tag->id = read_data_in_section(ptr, format);
+                       status.section = PS_LEN;
+                       break;
+
+               case PS_LEN:
+                       if (get_input_format(ptr, &format) < 0)
+                               return -EILSEQ;
+
+                       byte_count = read_data_in_section(ptr, format);
+                       if (byte_count > PS_MAX_LEN)
+                               return -EILSEQ;
+
+                       tag->len = byte_count;
+                       tag->data = (uint8_t *)malloc(byte_count);
+
+                       status.section = PS_DATA;
+                       status.line_count = 0;
+                       break;
+
+               case PS_DATA:
+                       if (status.line_count == 0)
+                               if (get_input_format(ptr, &format) < 0)
+                                       return -EILSEQ;
+
+                       __skip_space(ptr);
+
+                       status.char_cnt = update_char_count(ptr);
+
+                       read_count = (byte_count > ENTRY_PER_LINE) ?
+                                       ENTRY_PER_LINE : byte_count;
+
+                       if (format.type == PS_HEX && format.array == TRUE) {
+                               while (read_count > 0) {
+                                       update_tag_data(tag, &status, ptr);
+                                       read_count -= 2;
+                               }
+
+                               if (byte_count > ENTRY_PER_LINE)
+                                       byte_count -= ENTRY_PER_LINE;
+                               else
+                                       byte_count = 0;
+                       }
+
+                       status.line_count++;
+
+                       if (byte_count == 0)
+                               memset(&status, 0x00, sizeof(struct tag_info));
+
+                       if (status.section == PS_UNDEF)
+                               tag_cnt++;
+
+                       if (tag_cnt == MAX_TAGS)
+                               return -EILSEQ;
+                       break;
+               }
+       } while (ptr);
+
+       return tag_cnt;
+}
+
+#define MAX_PATCH_CMD 244
+struct patch_entry {
+       int16_t len;
+       uint8_t data[MAX_PATCH_CMD];
+};
+
+#define SET_PATCH_RAM_ID       0x0D
+#define SET_PATCH_RAM_CMD_SIZE 11
+#define ADDRESS_LEN            4
+static int set_patch_ram(int dev, char *patch_loc, int len)
+{
+       int err;
+       uint8_t cmd[20];
+       int i, j;
+       char loc_byte[3];
+       uint8_t *event;
+       uint8_t *loc_ptr = &cmd[7];
+
+       if (!patch_loc)
+               return -1;
+
+       loc_byte[2] = '\0';
+
+       load_hci_ps_hdr(cmd, SET_PATCH_RAM_ID, ADDRESS_LEN, 0);
+
+       for (i = 0, j = 3; i < 4; i++, j--) {
+               loc_byte[0] = patch_loc[0];
+               loc_byte[1] = patch_loc[1];
+               loc_ptr[j] = strtol(loc_byte, NULL, 16);
+               patch_loc += 2;
+       }
+
+       err = send_hci_cmd_sync(dev, cmd, SET_PATCH_RAM_CMD_SIZE, &event);
+       if (err < 0)
+               return err;
+
+       err = read_ps_event(event, HCI_PS_CMD_OCF);
+
+       if (event)
+               free(event);
+
+       return err;
+}
+
+#define PATCH_LOC_KEY    "DA:"
+#define PATCH_LOC_STRING_LEN    8
+static int ps_patch_download(int fd, FILE *stream)
+{
+       char byte[3];
+       char ptr[MAX_PATCH_CMD + 1];
+       int byte_cnt;
+       int patch_count = 0;
+       char patch_loc[PATCH_LOC_STRING_LEN + 1];
+
+       byte[2] = '\0';
+
+       while (fgets(ptr, MAX_PATCH_CMD, stream)) {
+               if (strlen(ptr) <= 1)
+                       continue;
+               else if (strstr(ptr, PATCH_LOC_KEY) == ptr) {
+                       strncpy(patch_loc, &ptr[sizeof(PATCH_LOC_KEY) - 1],
+                                                       PATCH_LOC_STRING_LEN);
+                       if (set_patch_ram(fd, patch_loc, sizeof(patch_loc)) < 0)
+                               return -1;
+               } else if (isxdigit(ptr[0]))
+                       break;
+               else
+                       return -1;
+       }
+
+       byte_cnt = strtol(ptr, NULL, 16);
+
+       while (byte_cnt > 0) {
+               int i;
+               uint8_t cmd[HCI_MAX_CMD_SIZE];
+               struct patch_entry patch;
+
+               if (byte_cnt > MAX_PATCH_CMD)
+                       patch.len = MAX_PATCH_CMD;
+               else
+                       patch.len = byte_cnt;
+
+               for (i = 0; i < patch.len; i++) {
+                       if (!fgets(byte, 3, stream))
+                               return -1;
+
+                       patch.data[i] = strtoul(byte, NULL, 16);
+               }
+
+               load_hci_ps_hdr(cmd, WRITE_PATCH, patch.len, patch_count);
+               memcpy(&cmd[HCI_PS_CMD_HDR_LEN], patch.data, patch.len);
+
+               if (write_cmd(fd, cmd, patch.len + HCI_PS_CMD_HDR_LEN) < 0)
+                       return -1;
+
+               patch_count++;
+               byte_cnt = byte_cnt - MAX_PATCH_CMD;
+       }
+
+       if (write_ps_cmd(fd, ENABLE_PATCH, 0) < 0)
+               return -1;
+
+       return patch_count;
+}
+
+#define PS_RAM_SIZE 2048
+
+static int ps_config_download(int fd, int tag_count)
+{
+       if (write_ps_cmd(fd, PS_RESET, PS_RAM_SIZE) < 0)
+               return -1;
+
+       if (tag_count > 0)
+               if (write_ps_cmd(fd, PS_WRITE, tag_count) < 0)
+                       return -1;
+       return 0;
+}
+
+#define PS_ASIC_FILE                   "PS_ASIC.pst"
+#define PS_FPGA_FILE                   "PS_FPGA.pst"
+
+static void get_ps_file_name(uint32_t devtype, uint32_t rom_version,
+                                                       char *path)
+{
+       char *filename;
+
+       if (devtype == 0xdeadc0de)
+               filename = PS_ASIC_FILE;
+       else
+               filename = PS_FPGA_FILE;
+
+       snprintf(path, MAXPATHLEN, "%s%x/%s", FW_PATH, rom_version, filename);
+}
+
+#define PATCH_FILE        "RamPatch.txt"
+#define FPGA_ROM_VERSION  0x99999999
+#define ROM_DEV_TYPE      0xdeadc0de
+
+static void get_patch_file_name(uint32_t dev_type, uint32_t rom_version,
+                               uint32_t build_version, char *path)
+{
+       if (rom_version == FPGA_ROM_VERSION && dev_type != ROM_DEV_TYPE &&
+                                       dev_type != 0 && build_version == 1)
+               path[0] = '\0';
+       else
+               snprintf(path, MAXPATHLEN, "%s%x/%s",
+                               FW_PATH, rom_version, PATCH_FILE);
+}
+
+#define VERIFY_CRC   9
+#define PS_REGION    1
+#define PATCH_REGION 2
+
+static int get_ath3k_crc(int dev)
+{
+       uint8_t cmd[7];
+       uint8_t *event;
+       int err;
+
+       load_hci_ps_hdr(cmd, VERIFY_CRC, 0, PS_REGION | PATCH_REGION);
+
+       err = send_hci_cmd_sync(dev, cmd, sizeof(cmd), &event);
+       if (err < 0)
+               return err;
+       /* Send error code if CRC check patched */
+       if (read_ps_event(event, HCI_PS_CMD_OCF) >= 0)
+               err = -EILSEQ;
+
+       if (!event)
+               free(event);
+
+       return err;
+}
+
+#define DEV_REGISTER      0x4FFC
+#define GET_DEV_TYPE_OCF  0x05
+
+static int get_device_type(int dev, uint32_t *code)
+{
+       uint8_t cmd[8];
+       uint8_t *event;
+       uint32_t reg;
+       int err;
+       uint8_t *ptr = cmd;
+       hci_command_hdr *ch = (void *)cmd;
+
+       ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF,
+                                               GET_DEV_TYPE_OCF));
+       ch->plen = 5;
+       ptr += HCI_COMMAND_HDR_SIZE;
+
+       ptr[0] = (uint8_t)DEV_REGISTER;
+       ptr[1] = (uint8_t)DEV_REGISTER >> 8;
+       ptr[2] = (uint8_t)DEV_REGISTER >> 16;
+       ptr[3] = (uint8_t)DEV_REGISTER >> 24;
+       ptr[4] = 0x04;
+
+       err = send_hci_cmd_sync(dev, cmd, sizeof(cmd), &event);
+       if (err < 0)
+               return err;
+
+       err = read_ps_event(event, GET_DEV_TYPE_OCF);
+       if (err < 0)
+               goto cleanup;
+
+       reg = event[10];
+       reg = (reg << 8) | event[9];
+       reg = (reg << 8) | event[8];
+       reg = (reg << 8) | event[7];
+       *code = reg;
+
+cleanup:
+       if (event)
+               free(event);
+
+       return err;
+}
+
+#define GET_VERSION_OCF 0x1E
+
+static int read_ath3k_version(int pConfig, uint32_t *rom_version,
+                                       uint32_t *build_version)
+{
+       uint8_t cmd[3];
+       uint8_t *event;
+       int err;
+       int status;
+       hci_command_hdr *ch = (void *)cmd;
+
+       ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF,
+                                               GET_VERSION_OCF));
+       ch->plen = 0;
+
+       err = send_hci_cmd_sync(pConfig, cmd, sizeof(cmd), &event);
+       if (err < 0)
+               return err;
+
+       err = read_ps_event(event, GET_VERSION_OCF);
+       if (err < 0)
+               goto cleanup;
+
+       status = event[10];
+       status = (status << 8) | event[9];
+       status = (status << 8) | event[8];
+       status = (status << 8) | event[7];
+       *rom_version = status;
+
+       status = event[14];
+       status = (status << 8) | event[13];
+       status = (status << 8) | event[12];
+       status = (status << 8) | event[11];
+       *build_version = status;
+
+cleanup:
+       if (event)
+               free(event);
+
+       return err;
+}
+
+static void convert_bdaddr(char *str_bdaddr, char *bdaddr)
+{
+       char bdbyte[3];
+       char *str_byte = str_bdaddr;
+       int i, j;
+       int colon_present = 0;
+
+       if (strstr(str_bdaddr, ":"))
+               colon_present = 1;
+
+       bdbyte[2] = '\0';
+
+       /* Reverse the BDADDR to LSB first */
+       for (i = 0, j = 5; i < 6; i++, j--) {
+               bdbyte[0] = str_byte[0];
+               bdbyte[1] = str_byte[1];
+               bdaddr[j] = strtol(bdbyte, NULL, 16);
+
+               if (colon_present == 1)
+                       str_byte += 3;
+               else
+                       str_byte += 2;
+       }
+}
+
+static int write_bdaddr(int pConfig, char *bdaddr)
+{
+       uint8_t *event;
+       int err;
+       uint8_t cmd[13];
+       uint8_t *ptr = cmd;
+       hci_command_hdr *ch = (void *)cmd;
+
+       memset(cmd, 0, sizeof(cmd));
+
+       ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF,
+                                               HCI_PS_CMD_OCF));
+       ch->plen = 10;
+       ptr += HCI_COMMAND_HDR_SIZE;
+
+       ptr[0] = 0x01;
+       ptr[1] = 0x01;
+       ptr[2] = 0x00;
+       ptr[3] = 0x06;
+
+       convert_bdaddr(bdaddr, (char *)&ptr[4]);
+
+       err = send_hci_cmd_sync(pConfig, cmd, sizeof(cmd), &event);
+       if (err < 0)
+               return err;
+
+       err = read_ps_event(event, HCI_PS_CMD_OCF);
+
+       if (event)
+               free(event);
+
+       return err;
+}
+
+#define BDADDR_FILE "ar3kbdaddr.pst"
+
+static void write_bdaddr_from_file(int rom_version, int fd)
+{
+       FILE *stream;
+       char bdaddr[PATH_MAX];
+       char bdaddr_file[PATH_MAX];
+
+       snprintf(bdaddr_file, MAXPATHLEN, "%s%x/%s",
+                       FW_PATH, rom_version, BDADDR_FILE);
+
+       stream = fopen(bdaddr_file, "r");
+       if (!stream)
+               return;
+
+       if (fgets(bdaddr, PATH_MAX - 1, stream))
+               write_bdaddr(fd, bdaddr);
+
+       fclose(stream);
+}
+
+static int ath_ps_download(int fd)
+{
+       int err = 0;
+       int tag_count;
+       int patch_count = 0;
+       uint32_t rom_version = 0;
+       uint32_t build_version = 0;
+       uint32_t dev_type = 0;
+       char patch_file[PATH_MAX];
+       char ps_file[PATH_MAX];
+       FILE *stream;
+
+       /*
+        * Verfiy firmware version. depending on it select the PS
+        * config file to download.
+        */
+       if (get_device_type(fd, &dev_type) < 0) {
+               err = -EILSEQ;
+               goto download_cmplete;
+       }
+
+       if (read_ath3k_version(fd, &rom_version, &build_version) < 0) {
+               err = -EILSEQ;
+               goto download_cmplete;
+       }
+
+       /* Do not download configuration if CRC passes */
+       if (get_ath3k_crc(fd) < 0) {
+               err = 0;
+               goto download_cmplete;
+       }
+
+       get_ps_file_name(dev_type, rom_version, ps_file);
+       get_patch_file_name(dev_type, rom_version, build_version, patch_file);
+
+       stream = fopen(ps_file, "r");
+       if (!stream) {
+               perror("firmware file open error\n");
+               err = -EILSEQ;
+               goto download_cmplete;
+       }
+       tag_count = ath_parse_ps(stream);
+
+       fclose(stream);
+
+       if (tag_count < 0) {
+               err = -EILSEQ;
+               goto download_cmplete;
+       }
+
+       /*
+        * It is not necessary that Patch file be available,
+        * continue with PS Operations if patch file is not available.
+        */
+       if (patch_file[0] == '\0')
+               err = 0;
+
+       stream = fopen(patch_file, "r");
+       if (!stream)
+               err = 0;
+       else {
+               patch_count = ps_patch_download(fd, stream);
+               fclose(stream);
+
+               if (patch_count < 0) {
+                       err = -EILSEQ;
+                       goto download_cmplete;
+               }
+       }
+
+       err = ps_config_download(fd, tag_count);
+
+download_cmplete:
+       if (!err)
+               write_bdaddr_from_file(rom_version, fd);
+
+       return err;
+}
+
+#define HCI_SLEEP_CMD_OCF     0x04
+
+/*
+ * Atheros AR300x specific initialization post callback
+ */
+int ath3k_post(int fd, int pm)
+{
+       int dev_id, dd;
+       struct timespec tm = { 0, 50000 };
+
+       sleep(1);
+
+       dev_id = ioctl(fd, HCIUARTGETDEVICE, 0);
+       if (dev_id < 0) {
+               perror("cannot get device id");
+               return dev_id;
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               return dd;
+       }
+
+       if (ioctl(dd, HCIDEVUP, dev_id) < 0 && errno != EALREADY) {
+               perror("hci down:Power management Disabled");
+               hci_close_dev(dd);
+               return -1;
+       }
+
+       /* send vendor specific command with Sleep feature Enabled */
+       if (hci_send_cmd(dd, OGF_VENDOR_CMD, HCI_SLEEP_CMD_OCF, 1, &pm) < 0)
+               perror("PM command failed, power management Disabled");
+
+       nanosleep(&tm, NULL);
+       hci_close_dev(dd);
+
+       return 0;
+}
+
+#define HCI_VENDOR_CMD_OGF    0x3F
+#define HCI_PS_CMD_OCF        0x0B
+#define HCI_CHG_BAUD_CMD_OCF  0x0C
+
+#define WRITE_BDADDR_CMD_LEN 14
+#define WRITE_BAUD_CMD_LEN   6
+#define MAX_CMD_LEN          WRITE_BDADDR_CMD_LEN
+
+static int set_cntrlr_baud(int fd, int speed)
+{
+       int baud;
+       struct timespec tm = { 0, 500000 };
+       unsigned char cmd[MAX_CMD_LEN], rsp[HCI_MAX_EVENT_SIZE];
+       unsigned char *ptr = cmd + 1;
+       hci_command_hdr *ch = (void *)ptr;
+
+       cmd[0] = HCI_COMMAND_PKT;
+
+       /* set controller baud rate to user specified value */
+       ptr = cmd + 1;
+       ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF,
+                                               HCI_CHG_BAUD_CMD_OCF));
+       ch->plen = 2;
+       ptr += HCI_COMMAND_HDR_SIZE;
+
+       baud = speed/100;
+       ptr[0] = (char)baud;
+       ptr[1] = (char)(baud >> 8);
+
+       if (write(fd, cmd, WRITE_BAUD_CMD_LEN) != WRITE_BAUD_CMD_LEN) {
+               perror("Failed to write change baud rate command");
+               return -ETIMEDOUT;
+       }
+
+       nanosleep(&tm, NULL);
+
+       if (read_hci_event(fd, rsp, sizeof(rsp)) < 0)
+               return -ETIMEDOUT;
+
+       return 0;
+}
+
+/*
+ * Atheros AR300x specific initialization and configuration file
+ * download
+ */
+int ath3k_init(int fd, int speed, int init_speed, char *bdaddr,
+                                               struct termios *ti)
+{
+       int r;
+       int err = 0;
+       struct timespec tm = { 0, 500000 };
+       unsigned char cmd[MAX_CMD_LEN], rsp[HCI_MAX_EVENT_SIZE];
+       unsigned char *ptr = cmd + 1;
+       hci_command_hdr *ch = (void *)ptr;
+
+       cmd[0] = HCI_COMMAND_PKT;
+
+       /* set both controller and host baud rate to maximum possible value */
+       err = set_cntrlr_baud(fd, speed);
+       if (err < 0)
+               return err;
+
+       err = set_speed(fd, ti, speed);
+       if (err < 0) {
+               perror("Can't set required baud rate");
+               return err;
+       }
+
+       /* Download PS and patch */
+       r = ath_ps_download(fd);
+       if (r < 0) {
+               perror("Failed to Download configuration");
+               err = -ETIMEDOUT;
+               goto failed;
+       }
+
+       /* Write BDADDR */
+       if (bdaddr) {
+               ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF,
+                                                       HCI_PS_CMD_OCF));
+               ch->plen = 10;
+               ptr += HCI_COMMAND_HDR_SIZE;
+
+               ptr[0] = 0x01;
+               ptr[1] = 0x01;
+               ptr[2] = 0x00;
+               ptr[3] = 0x06;
+               str2ba(bdaddr, (bdaddr_t *)(ptr + 4));
+
+               if (write(fd, cmd, WRITE_BDADDR_CMD_LEN) !=
+                                       WRITE_BDADDR_CMD_LEN) {
+                       perror("Failed to write BD_ADDR command\n");
+                       err = -ETIMEDOUT;
+                       goto failed;
+               }
+
+               if (read_hci_event(fd, rsp, sizeof(rsp)) < 0) {
+                       perror("Failed to set BD_ADDR\n");
+                       err = -ETIMEDOUT;
+                       goto failed;
+               }
+       }
+
+       /* Send HCI Reset */
+       cmd[1] = 0x03;
+       cmd[2] = 0x0C;
+       cmd[3] = 0x00;
+
+       r = write(fd, cmd, 4);
+       if (r != 4) {
+               err = -ETIMEDOUT;
+               goto failed;
+       }
+
+       nanosleep(&tm, NULL);
+       if (read_hci_event(fd, rsp, sizeof(rsp)) < 0) {
+               err = -ETIMEDOUT;
+               goto failed;
+       }
+
+       err = set_cntrlr_baud(fd, speed);
+       if (err < 0)
+               return err;
+
+failed:
+       if (err < 0) {
+               set_cntrlr_baud(fd, init_speed);
+               set_speed(fd, ti, init_speed);
+       }
+
+       return err;
+}
diff --git a/tools/hciattach_qualcomm.c b/tools/hciattach_qualcomm.c
new file mode 100644 (file)
index 0000000..e4ff5e3
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <syslog.h>
+#include <termios.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "hciattach.h"
+
+#define FAILIF(x, args...) do { \
+       if (x) { \
+               fprintf(stderr, ##args); \
+               return -1; \
+       } \
+} while (0)
+
+typedef struct {
+       uint8_t uart_prefix;
+       hci_event_hdr hci_hdr;
+       evt_cmd_complete cmd_complete;
+       uint8_t status;
+       uint8_t data[16];
+} __attribute__((packed)) command_complete_t;
+
+static int read_command_complete(int fd,
+                                       unsigned short opcode,
+                                       unsigned char len)
+{
+       command_complete_t resp;
+       unsigned char vsevent[512];
+       int n;
+
+       /* Read reply. */
+       n = read_hci_event(fd, vsevent, sizeof(vsevent));
+       FAILIF(n < 0, "Failed to read response");
+
+       FAILIF(vsevent[1] != 0xFF, "Failed to read response");
+
+       n = read_hci_event(fd, (unsigned char *)&resp, sizeof(resp));
+       FAILIF(n < 0, "Failed to read response");
+
+       /* event must be event-complete */
+       FAILIF(resp.hci_hdr.evt != EVT_CMD_COMPLETE,
+               "Error in response: not a cmd-complete event, "
+               "but 0x%02x!\n", resp.hci_hdr.evt);
+
+       FAILIF(resp.hci_hdr.plen < 4, /* plen >= 4 for EVT_CMD_COMPLETE */
+               "Error in response: plen is not >= 4, but 0x%02x!\n",
+               resp.hci_hdr.plen);
+
+       /* cmd-complete event: opcode */
+       FAILIF(resp.cmd_complete.opcode != 0,
+               "Error in response: opcode is 0x%04x, not 0!",
+               resp.cmd_complete.opcode);
+
+       return resp.status == 0 ? 0 : -1;
+}
+
+static int qualcomm_load_firmware(int fd, const char *firmware, const char *bdaddr_s)
+{
+
+       int fw = open(firmware, O_RDONLY);
+
+       fprintf(stdout, "Opening firmware file: %s\n", firmware);
+
+       FAILIF(fw < 0,
+               "Could not open firmware file %s: %s (%d).\n",
+               firmware, strerror(errno), errno);
+
+       fprintf(stdout, "Uploading firmware...\n");
+       do {
+               /* Read each command and wait for a response. */
+               unsigned char data[1024];
+               unsigned char cmdp[1 + sizeof(hci_command_hdr)];
+               hci_command_hdr *cmd = (hci_command_hdr *) (cmdp + 1);
+               int nr;
+
+               nr = read(fw, cmdp, sizeof(cmdp));
+               if (!nr)
+                       break;
+
+               FAILIF(nr != sizeof(cmdp),
+                       "Could not read H4 + HCI header!\n");
+               FAILIF(*cmdp != HCI_COMMAND_PKT,
+                       "Command is not an H4 command packet!\n");
+
+               FAILIF(read(fw, data, cmd->plen) != cmd->plen,
+                               "Could not read %d bytes of data \
+                               for command with opcode %04x!\n",
+                               cmd->plen, cmd->opcode);
+
+               if ((data[0] == 1) && (data[1] == 2) && (data[2] == 6)) {
+                       bdaddr_t bdaddr;
+                       if (bdaddr_s != NULL) {
+                               str2ba(bdaddr_s, &bdaddr);
+                               memcpy(&data[3], &bdaddr, sizeof(bdaddr_t));
+                       }
+               }
+
+               {
+                       int nw;
+                       struct iovec iov_cmd[2];
+                       iov_cmd[0].iov_base = cmdp;
+                       iov_cmd[0].iov_len = sizeof(cmdp);
+                       iov_cmd[1].iov_base = data;
+                       iov_cmd[1].iov_len = cmd->plen;
+                       nw = writev(fd, iov_cmd, 2);
+                       FAILIF(nw != (int) sizeof(cmdp) + cmd->plen,
+                               "Could not send entire command \
+                               (sent only %d bytes)!\n",
+                               nw);
+               }
+
+               /* Wait for response */
+               if (read_command_complete(fd, cmd->opcode, cmd->plen) < 0)
+                       return -1;
+       } while (1);
+       fprintf(stdout, "Firmware upload successful.\n");
+
+       close(fw);
+
+       return 0;
+}
+
+int qualcomm_init(int fd, int speed, struct termios *ti, const char *bdaddr)
+{
+       struct timespec tm = {0, 50000};
+       char cmd[5];
+       unsigned char resp[100];                /* Response */
+       char fw[100];
+       int n;
+
+       memset(resp, 0, 100);
+
+       /* Get Manufacturer and LMP version */
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x01;
+       cmd[2] = 0x10;
+       cmd[3] = 0x00;
+
+       do {
+               n = write(fd, cmd, 4);
+               if (n < 4) {
+                       perror("Failed to write init command");
+                       return -1;
+               }
+
+               /* Read reply. */
+               if (read_hci_event(fd, resp, 100) < 0) {
+                       perror("Failed to read init response");
+                       return -1;
+               }
+
+               /* Wait for command complete event for our Opcode */
+       } while (resp[4] != cmd[1] && resp[5] != cmd[2]);
+
+       /* Verify manufacturer */
+       if ((resp[11] & 0xFF) != 0x1d)
+               fprintf(stderr,
+                       "WARNING : module's manufacturer is not Qualcomm\n");
+
+       /* Print LMP version */
+       fprintf(stderr,
+               "Qualcomm module LMP version : 0x%02x\n", resp[10] & 0xFF);
+
+       /* Print LMP subversion */
+       {
+               unsigned short lmp_subv = resp[13] | (resp[14] << 8);
+
+               fprintf(stderr, "Qualcomm module LMP sub-version : 0x%04x\n",
+                                                               lmp_subv);
+       }
+
+       /* Get SoC type */
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x00;
+       cmd[2] = 0xFC;
+       cmd[3] = 0x01;
+       cmd[4] = 0x06;
+
+       do {
+               n = write(fd, cmd, 5);
+               if (n < 5) {
+                       perror("Failed to write vendor init command");
+                       return -1;
+               }
+
+               /* Read reply. */
+               if ((n = read_hci_event(fd, resp, 100)) < 0) {
+                       perror("Failed to read vendor init response");
+                       return -1;
+               }
+
+       } while (resp[3] != 0 && resp[4] != 2);
+
+       snprintf(fw, sizeof(fw), "/etc/firmware/%c%c%c%c%c%c_%c%c%c%c.bin",
+                               resp[18], resp[19], resp[20], resp[21],
+                               resp[22], resp[23],
+                               resp[32], resp[33], resp[34], resp[35]);
+
+       /* Wait for command complete event for our Opcode */
+       if (read_hci_event(fd, resp, 100) < 0) {
+               perror("Failed to read init response");
+               return -1;
+       }
+
+       qualcomm_load_firmware(fd, fw, bdaddr);
+
+       /* Reset */
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x03;
+       cmd[2] = 0x0C;
+       cmd[3] = 0x00;
+
+       do {
+               n = write(fd, cmd, 4);
+               if (n < 4) {
+                       perror("Failed to write reset command");
+                       return -1;
+               }
+
+               /* Read reply. */
+               if ((n = read_hci_event(fd, resp, 100)) < 0) {
+                       perror("Failed to read reset response");
+                       return -1;
+               }
+
+       } while (resp[4] != cmd[1] && resp[5] != cmd[2]);
+
+       nanosleep(&tm, NULL);
+
+       return 0;
+}
diff --git a/tools/hciattach_st.c b/tools/hciattach_st.c
new file mode 100644 (file)
index 0000000..dbb7c47
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/param.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "hciattach.h"
+
+static int debug = 0;
+
+static int do_command(int fd, uint8_t ogf, uint16_t ocf,
+                       uint8_t *cparam, int clen, uint8_t *rparam, int rlen)
+{
+       //uint16_t opcode = (uint16_t) ((ocf & 0x03ff) | (ogf << 10));
+       unsigned char cp[260], rp[260];
+       int len, size, offset = 3;
+
+       cp[0] = 0x01;
+       cp[1] = ocf & 0xff;
+       cp[2] = ogf << 2 | ocf >> 8;
+       cp[3] = clen;
+
+       if (clen > 0)
+               memcpy(cp + 4, cparam, clen);
+
+       if (debug) {
+               int i;
+               printf("[<");
+               for (i = 0; i < clen + 4; i++)
+                       printf(" %02x", cp[i]);
+               printf("]\n");
+       }
+
+       if (write(fd, cp, clen + 4) < 0)
+               return -1;
+
+       do {
+               if (read(fd, rp, 1) < 1)
+                       return -1;
+       } while (rp[0] != 0x04);
+
+       if (read(fd, rp + 1, 2) < 2)
+               return -1;
+
+       do {
+               len = read(fd, rp + offset, sizeof(rp) - offset);
+               offset += len;
+       } while (offset < rp[2] + 3);
+
+       if (debug) {
+               int i;
+               printf("[>");
+               for (i = 0; i < offset; i++)
+                       printf(" %02x", rp[i]);
+               printf("]\n");
+       }
+
+       if (rp[0] != 0x04) {
+               errno = EIO;
+               return -1;
+       }
+
+       switch (rp[1]) {
+       case 0x0e:      /* command complete */
+               if (rp[6] != 0x00)
+                       return -ENXIO;
+               offset = 3 + 4;
+               size = rp[2] - 4;
+               break;
+       case 0x0f:      /* command status */
+               /* fall through */
+       default:
+               offset = 3;
+               size = rp[2];
+               break;
+       }
+
+       if (!rparam || rlen < size)
+               return -ENXIO;
+
+       memcpy(rparam, rp + offset, size);
+
+       return size;
+}
+
+static int load_file(int dd, uint16_t version, const char *suffix)
+{
+       DIR *dir;
+       struct dirent *d;
+       char pathname[PATH_MAX], filename[NAME_MAX], prefix[20];
+       unsigned char cmd[256];
+       unsigned char buf[256];
+       uint8_t seqnum = 0;
+       int fd, size, len, found_fw_file;
+
+       memset(filename, 0, sizeof(filename));
+
+       snprintf(prefix, sizeof(prefix), "STLC2500_R%d_%02d_",
+                                               version >> 8, version & 0xff);
+
+       strcpy(pathname, "/lib/firmware");
+       dir = opendir(pathname);
+       if (!dir) {
+               strcpy(pathname, ".");
+               dir = opendir(pathname);
+               if (!dir)
+                       return -errno;
+       }
+
+       found_fw_file = 0;
+       while (1) {
+               d = readdir(dir);
+               if (!d)
+                       break;
+
+               if (strncmp(d->d_name + strlen(d->d_name) - strlen(suffix),
+                                               suffix, strlen(suffix)))
+                       continue;
+
+               if (strncmp(d->d_name, prefix, strlen(prefix)))
+                       continue;
+
+               snprintf(filename, sizeof(filename), "%s/%s",
+                                                       pathname, d->d_name);
+               found_fw_file = 1;
+       }
+
+       closedir(dir);
+
+       if (!found_fw_file)
+               return -ENOENT;
+
+       printf("Loading file %s\n", filename);
+
+       fd = open(filename, O_RDONLY);
+       if (fd < 0) {
+               perror("Can't open firmware file");
+               return -errno;
+       }
+
+       while (1) {
+               size = read(fd, cmd + 1, 254);
+               if (size <= 0)
+                       break;
+
+               cmd[0] = seqnum;
+
+               len = do_command(dd, 0xff, 0x002e, cmd, size + 1, buf, sizeof(buf));
+               if (len < 1)
+                       break;
+
+               if (buf[0] != seqnum) {
+                       fprintf(stderr, "Sequence number mismatch\n");
+                       break;
+               }
+
+               seqnum++;
+       }
+
+       close(fd);
+
+       return 0;
+}
+
+int stlc2500_init(int dd, bdaddr_t *bdaddr)
+{
+       unsigned char cmd[16];
+       unsigned char buf[254];
+       uint16_t version;
+       int len;
+       int err;
+
+       /* Hci_Cmd_Ericsson_Read_Revision_Information */
+       len = do_command(dd, 0xff, 0x000f, NULL, 0, buf, sizeof(buf));
+       if (len < 0)
+               return -1;
+
+       printf("%s\n", buf);
+
+       /* HCI_Read_Local_Version_Information */
+       len = do_command(dd, 0x04, 0x0001, NULL, 0, buf, sizeof(buf));
+       if (len < 0)
+               return -1;
+
+       version = buf[2] << 8 | buf[1];
+
+       err = load_file(dd, version, ".ptc");
+       if (err < 0) {
+               if (err == -ENOENT)
+                       fprintf(stderr, "No ROM patch file loaded.\n");
+               else
+                       return -1;
+       }
+
+       err = load_file(dd, buf[2] << 8 | buf[1], ".ssf");
+       if (err < 0) {
+               if (err == -ENOENT)
+                       fprintf(stderr, "No static settings file loaded.\n");
+               else
+                       return -1;
+       }
+
+       cmd[0] = 0xfe;
+       cmd[1] = 0x06;
+       bacpy((bdaddr_t *) (cmd + 2), bdaddr);
+
+       /* Hci_Cmd_ST_Store_In_NVDS */
+       len = do_command(dd, 0xff, 0x0022, cmd, 8, buf, sizeof(buf));
+       if (len < 0)
+               return -1;
+
+       /* HCI_Reset : applies parameters*/
+       len = do_command(dd, 0x03, 0x0003, NULL, 0, buf, sizeof(buf));
+       if (len < 0)
+               return -1;
+
+       return 0;
+}
+
+int bgb2xx_init(int dd, bdaddr_t *bdaddr)
+{
+       unsigned char cmd[16];
+       unsigned char buf[254];
+       int len;
+
+       len = do_command(dd, 0xff, 0x000f, NULL, 0, buf, sizeof(buf));
+       if (len < 0)
+               return -1;
+
+       printf("%s\n", buf);
+
+       cmd[0] = 0xfe;
+       cmd[1] = 0x06;
+       bacpy((bdaddr_t *) (cmd + 2), bdaddr);
+
+       len = do_command(dd, 0xff, 0x0022, cmd, 8, buf, sizeof(buf));
+       if (len < 0)
+               return -1;
+
+       len = do_command(dd, 0x03, 0x0003, NULL, 0, buf, sizeof(buf));
+       if (len < 0)
+               return -1;
+
+       return 0;
+}
diff --git a/tools/hciattach_ti.c b/tools/hciattach_ti.c
new file mode 100644 (file)
index 0000000..e11397d
--- /dev/null
@@ -0,0 +1,558 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2007-2008  Texas Instruments, Inc.
+ *  Copyright (C) 2005-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "hciattach.h"
+
+#ifdef HCIATTACH_DEBUG
+#define DPRINTF(x...)  printf(x)
+#else
+#define DPRINTF(x...)
+#endif
+
+#define HCIUARTGETDEVICE       _IOR('U', 202, int)
+
+#define MAKEWORD(a, b)  ((uint16_t)(((uint8_t)(a)) | ((uint16_t)((uint8_t)(b))) << 8))
+
+#define TI_MANUFACTURER_ID     13
+
+#ifdef __TI_PATCH__
+#define FIRMWARE_DIRECTORY1    "/mnt/mmc/"
+#define FIRMWARE_DIRECTORY2    "/usr/etc/bluetooth/"
+#else
+#define FIRMWARE_DIRECTORY     "/lib/firmware/"
+#endif
+
+#define ACTION_SEND_COMMAND    1
+#define ACTION_WAIT_EVENT      2
+#define ACTION_SERIAL          3
+#define ACTION_DELAY           4
+#define ACTION_RUN_SCRIPT      5
+#define ACTION_REMARKS         6
+
+#define BRF_DEEP_SLEEP_OPCODE_BYTE_1   0x0c
+#define BRF_DEEP_SLEEP_OPCODE_BYTE_2   0xfd
+#define BRF_DEEP_SLEEP_OPCODE          \
+       (BRF_DEEP_SLEEP_OPCODE_BYTE_1 | (BRF_DEEP_SLEEP_OPCODE_BYTE_2 << 8))
+
+#define FILE_HEADER_MAGIC      0x42535442
+
+/*
+ * BRF Firmware header
+ */
+struct bts_header {
+       uint32_t        magic;
+       uint32_t        version;
+       uint8_t future[24];
+       uint8_t actions[0];
+}__attribute__ ((packed));
+
+/*
+ * BRF Actions structure
+ */
+struct bts_action {
+       uint16_t        type;
+       uint16_t        size;
+       uint8_t data[0];
+} __attribute__ ((packed));
+
+struct bts_action_send {
+       uint8_t data[0];
+} __attribute__ ((packed));
+
+struct bts_action_wait {
+       uint32_t msec;
+       uint32_t size;
+       uint8_t data[0];
+}__attribute__ ((packed));
+
+struct bts_action_delay {
+       uint32_t msec;
+}__attribute__ ((packed));
+
+struct bts_action_serial {
+       uint32_t baud;
+       uint32_t flow_control;
+}__attribute__ ((packed));
+
+static FILE *bts_load_script(const char* file_name, uint32_t* version)
+{
+       struct bts_header header;
+       FILE* fp;
+
+       fp = fopen(file_name, "rb");
+       if (!fp) {
+               perror("can't open firmware file");
+               goto out;
+       }
+
+       if (1 != fread(&header, sizeof(struct bts_header), 1, fp)) {
+               perror("can't read firmware file");
+               goto errclose;
+       }
+
+       if (header.magic != FILE_HEADER_MAGIC) {
+               fprintf(stderr, "%s not a legal TI firmware file\n", file_name);
+               goto errclose;
+       }
+
+       if (NULL != version)
+               *version = header.version;
+
+       goto out;
+
+errclose:
+       fclose(fp);
+       fp = NULL;
+out:
+       return fp;
+}
+
+static unsigned long bts_fetch_action(FILE* fp, unsigned char* action_buf,
+                               unsigned long buf_size, uint16_t* action_type)
+{
+       struct bts_action action_hdr;
+       unsigned long nread;
+
+       if (!fp)
+               return 0;
+
+       if (1 != fread(&action_hdr, sizeof(struct bts_action), 1, fp))
+               return 0;
+
+       if (action_hdr.size > buf_size) {
+               fprintf(stderr, "bts_next_action: not enough space to read next action\n");
+               return 0;
+       }
+
+       nread = fread(action_buf, sizeof(uint8_t), action_hdr.size, fp);
+       if (nread != (action_hdr.size)) {
+               fprintf(stderr, "bts_next_action: fread failed to read next action\n");
+               return 0;
+       }
+
+       *action_type = action_hdr.type;
+
+       return nread * sizeof(uint8_t);
+}
+
+static void bts_unload_script(FILE* fp)
+{
+       if (fp)
+               fclose(fp);
+}
+
+static int is_it_texas(const uint8_t *respond)
+{
+       uint16_t manufacturer_id;
+
+       manufacturer_id = MAKEWORD(respond[11], respond[12]);
+
+       return TI_MANUFACTURER_ID == manufacturer_id ? 1 : 0;
+}
+
+static const char *get_firmware_name(const uint8_t *respond)
+{
+       static char firmware_file_name[PATH_MAX] = {0};
+       uint16_t version = 0, chip = 0, min_ver = 0, maj_ver = 0;
+
+       version = MAKEWORD(respond[13], respond[14]);
+       chip =  (version & 0x7C00) >> 10;
+       min_ver = (version & 0x007F);
+       maj_ver = (version & 0x0380) >> 7;
+
+       if (version & 0x8000)
+               maj_ver |= 0x0008;
+
+#ifdef __TI_PATCH__
+    FILE *fp;
+       sprintf(firmware_file_name, FIRMWARE_DIRECTORY1 "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver);
+
+    if( (fp = fopen(firmware_file_name, "r")) == NULL )
+    {
+               extern int firmware_path;
+               if (firmware_path)
+               {
+               sprintf(firmware_file_name, FIRMWARE_DIRECTORY2 "TIInit_edutm_%d.%d.%d.bts", chip, maj_ver, min_ver);
+               }
+               else
+               {
+               sprintf(firmware_file_name, FIRMWARE_DIRECTORY2 "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver);
+               }
+    }
+    else
+    {
+        fclose(fp);
+    }
+#else
+       sprintf(firmware_file_name, FIRMWARE_DIRECTORY "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver);
+#endif
+
+       return firmware_file_name;
+}
+
+static void brf_delay(struct bts_action_delay *delay)
+{
+       usleep(1000 * delay->msec);
+}
+
+static int brf_set_serial_params(struct bts_action_serial *serial_action,
+                                               int fd, struct termios *ti)
+{
+       fprintf(stderr, "texas: changing baud rate to %u, flow control to %u\n",
+                               serial_action->baud, serial_action->flow_control );
+       tcflush(fd, TCIOFLUSH);
+
+       if (serial_action->flow_control)
+               ti->c_cflag |= CRTSCTS;
+       else
+               ti->c_cflag &= ~CRTSCTS;
+
+       if (tcsetattr(fd, TCSANOW, ti) < 0) {
+               perror("Can't set port settings");
+               return -1;
+       }
+
+       tcflush(fd, TCIOFLUSH);
+
+       if (set_speed(fd, ti, serial_action->baud) < 0) {
+               perror("Can't set baud rate");
+               return -1;
+       }
+
+       return 0;
+}
+
+static int brf_send_command_socket(int fd, struct bts_action_send* send_action)
+{
+       char response[1024] = {0};
+       hci_command_hdr *cmd = (hci_command_hdr *) send_action->data;
+       uint16_t opcode = cmd->opcode;
+
+       struct hci_request rq;
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = cmd_opcode_ogf(opcode);
+       rq.ocf    = cmd_opcode_ocf(opcode);
+       rq.event  = EVT_CMD_COMPLETE;
+       rq.cparam = &send_action->data[3];
+       rq.clen   = send_action->data[2];
+       rq.rparam = response;
+       rq.rlen   = sizeof(response);
+
+       if (hci_send_req(fd, &rq, 15) < 0) {
+               perror("Cannot send hci command to socket");
+               return -1;
+       }
+
+       /* verify success */
+       if (response[0]) {
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+static int brf_send_command_file(int fd, struct bts_action_send* send_action, long size)
+{
+       unsigned char response[1024] = {0};
+       long ret = 0;
+
+       /* send command */
+       if (size != write(fd, send_action, size)) {
+               perror("Texas: Failed to write action command");
+               return -1;
+       }
+
+       /* read response */
+       ret = read_hci_event(fd, response, sizeof(response));
+       if (ret < 0) {
+               perror("texas: failed to read command response");
+               return -1;
+       }
+
+       /* verify success */
+       if (ret < 7 || 0 != response[6]) {
+               fprintf( stderr, "TI init command failed.\n" );
+               errno = EIO;
+               return -1;
+       }
+
+       return 0;
+}
+
+
+static int brf_send_command(int fd, struct bts_action_send* send_action, long size, int hcill_installed)
+{
+       int ret = 0;
+       char *fixed_action;
+
+       /* remove packet type when giving to socket API */
+       if (hcill_installed) {
+               fixed_action = ((char *) send_action) + 1;
+               ret = brf_send_command_socket(fd, (struct bts_action_send *) fixed_action);
+       } else {
+               ret = brf_send_command_file(fd, send_action, size);
+       }
+
+       return ret;
+}
+
+static int brf_do_action(uint16_t brf_type, uint8_t *brf_action, long brf_size,
+                               int fd, struct termios *ti, int hcill_installed)
+{
+       int ret = 0;
+
+       switch (brf_type) {
+       case ACTION_SEND_COMMAND:
+               DPRINTF("W");
+               ret = brf_send_command(fd, (struct bts_action_send*) brf_action, brf_size, hcill_installed);
+               break;
+       case ACTION_WAIT_EVENT:
+               DPRINTF("R");
+               break;
+       case ACTION_SERIAL:
+               DPRINTF("S");
+               ret = brf_set_serial_params((struct bts_action_serial *) brf_action, fd, ti);
+               break;
+       case ACTION_DELAY:
+               DPRINTF("D");
+               brf_delay((struct bts_action_delay *) brf_action);
+               break;
+       case ACTION_REMARKS:
+               DPRINTF("C");
+               break;
+       default:
+               fprintf(stderr, "brf_init: unknown firmware action type (%d)\n", brf_type);
+               break;
+       }
+
+       return ret;
+}
+
+/*
+ * tests whether a given brf action is a HCI_VS_Sleep_Mode_Configurations cmd
+ */
+static int brf_action_is_deep_sleep(uint8_t *brf_action, long brf_size,
+                                                       uint16_t brf_type)
+{
+       uint16_t opcode;
+
+       if (brf_type != ACTION_SEND_COMMAND)
+               return 0;
+
+       if (brf_size < 3)
+               return 0;
+
+       if (brf_action[0] != HCI_COMMAND_PKT)
+               return 0;
+
+       /* HCI data is little endian */
+       opcode = brf_action[1] | (brf_action[2] << 8);
+
+       if (opcode != BRF_DEEP_SLEEP_OPCODE)
+               return 0;
+
+       /* action is deep sleep configuration command ! */
+       return 1;
+}
+
+/*
+ * This function is called twice.
+ * The first time it is called, it loads the brf script, and executes its
+ * commands until it reaches a deep sleep command (or its end).
+ * The second time it is called, it assumes HCILL protocol is set up,
+ * and sends rest of brf script via the supplied socket.
+ */
+static int brf_do_script(int fd, struct termios *ti, const char *bts_file)
+{
+       int ret = 0,  hcill_installed = bts_file ? 0 : 1;
+       uint32_t vers;
+       static FILE *brf_script_file = NULL;
+       static uint8_t brf_action[512];
+       static long brf_size;
+       static uint16_t brf_type;
+
+       /* is it the first time we are called ? */
+       if (0 == hcill_installed) {
+               DPRINTF("Sending script to serial device\n");
+               brf_script_file = bts_load_script(bts_file, &vers );
+               if (!brf_script_file) {
+                       fprintf(stderr, "Warning: cannot find BTS file: %s\n",
+                                       bts_file);
+                       return 0;
+               }
+
+               fprintf( stderr, "Loaded BTS script version %u\n", vers );
+
+               brf_size = bts_fetch_action(brf_script_file, brf_action,
+                                               sizeof(brf_action), &brf_type);
+               if (brf_size == 0) {
+                       fprintf(stderr, "Warning: BTS file is empty !");
+                       return 0;
+               }
+       }
+       else {
+               DPRINTF("Sending script to bluetooth socket\n");
+       }
+
+       /* execute current action and continue to parse brf script file */
+       while (brf_size != 0) {
+               ret = brf_do_action(brf_type, brf_action, brf_size,
+                                               fd, ti, hcill_installed);
+               if (ret == -1)
+                       break;
+
+               brf_size = bts_fetch_action(brf_script_file, brf_action,
+                                               sizeof(brf_action), &brf_type);
+
+               /* if this is the first time we run (no HCILL yet) */
+               /* and a deep sleep command is encountered */
+               /* we exit */
+#ifndef __TI_PATCH__
+               if (!hcill_installed &&
+                               brf_action_is_deep_sleep(brf_action,
+                                                       brf_size, brf_type))
+                       return 0;
+#endif
+       }
+
+       bts_unload_script(brf_script_file);
+       brf_script_file = NULL;
+       DPRINTF("\n");
+
+       return ret;
+}
+
+int texas_init(int fd, struct termios *ti)
+{
+       struct timespec tm = {0, 50000};
+       char cmd[4];
+       unsigned char resp[100];                /* Response */
+       const char *bts_file;
+       int n;
+
+       memset(resp,'\0', 100);
+
+       /* It is possible to get software version with manufacturer specific
+          HCI command HCI_VS_TI_Version_Number. But the only thing you get more
+          is if this is point-to-point or point-to-multipoint module */
+
+       /* Get Manufacturer and LMP version */
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x01;
+       cmd[2] = 0x10;
+       cmd[3] = 0x00;
+
+       do {
+               n = write(fd, cmd, 4);
+               if (n < 0) {
+                       perror("Failed to write init command (READ_LOCAL_VERSION_INFORMATION)");
+                       return -1;
+               }
+               if (n < 4) {
+                       fprintf(stderr, "Wanted to write 4 bytes, could only write %d. Stop\n", n);
+                       return -1;
+               }
+
+               /* Read reply. */
+               if (read_hci_event(fd, resp, 100) < 0) {
+                       perror("Failed to read init response (READ_LOCAL_VERSION_INFORMATION)");
+                       return -1;
+               }
+
+               /* Wait for command complete event for our Opcode */
+       } while (resp[4] != cmd[1] && resp[5] != cmd[2]);
+
+       /* Verify manufacturer */
+       if (! is_it_texas(resp)) {
+               fprintf(stderr,"ERROR: module's manufacturer is not Texas Instruments\n");
+               return -1;
+       }
+
+       fprintf(stderr, "Found a Texas Instruments' chip!\n");
+
+       bts_file = get_firmware_name(resp);
+       fprintf(stderr, "Firmware file : %s\n", bts_file);
+
+       n = brf_do_script(fd, ti, bts_file);
+
+       nanosleep(&tm, NULL);
+
+       return n;
+}
+
+int texas_post(int fd, struct termios *ti)
+{
+       int dev_id, dd, ret = 0;
+
+       sleep(1);
+
+       dev_id = ioctl(fd, HCIUARTGETDEVICE, 0);
+       if (dev_id < 0) {
+               perror("cannot get device id");
+               return -1;
+       }
+
+       DPRINTF("\nAdded device hci%d\n", dev_id);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               return -1;
+       }
+
+       if (ioctl(dd, HCIDEVUP, dev_id) < 0 && errno != EALREADY) {
+               fprintf(stderr, "Can't init device hci%d: %s (%d)", dev_id,
+                                                       strerror(errno), errno);
+               hci_close_dev(dd);
+               return -1;
+       }
+
+       ret = brf_do_script(dd, ti, NULL);
+
+       hci_close_dev(dd);
+
+       return ret;
+}
diff --git a/tools/hciattach_tialt.c b/tools/hciattach_tialt.c
new file mode 100644 (file)
index 0000000..1ba009c
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <syslog.h>
+#include <termios.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "hciattach.h"
+
+#define FAILIF(x, args...) do {   \
+       if (x) {                                          \
+               fprintf(stderr, ##args);  \
+               return -1;                                \
+       }                                                         \
+} while(0)
+
+typedef struct {
+       uint8_t uart_prefix;
+       hci_event_hdr hci_hdr;
+       evt_cmd_complete cmd_complete;
+       uint8_t status;
+       uint8_t data[16];
+} __attribute__((packed)) command_complete_t;
+
+static int read_command_complete(int fd, unsigned short opcode, unsigned char len) {
+       command_complete_t resp;
+       /* Read reply. */
+       FAILIF(read_hci_event(fd, (unsigned char *)&resp, sizeof(resp)) < 0,
+                  "Failed to read response");
+
+       /* Parse speed-change reply */
+       FAILIF(resp.uart_prefix != HCI_EVENT_PKT,
+                  "Error in response: not an event packet, but 0x%02x!\n",
+                  resp.uart_prefix);
+
+       FAILIF(resp.hci_hdr.evt != EVT_CMD_COMPLETE, /* event must be event-complete */
+                  "Error in response: not a cmd-complete event, "
+                  "but 0x%02x!\n", resp.hci_hdr.evt);
+
+       FAILIF(resp.hci_hdr.plen < 4, /* plen >= 4 for EVT_CMD_COMPLETE */
+                  "Error in response: plen is not >= 4, but 0x%02x!\n",
+                  resp.hci_hdr.plen);
+
+       /* cmd-complete event: opcode */
+       FAILIF(resp.cmd_complete.opcode != (uint16_t)opcode,
+                  "Error in response: opcode is 0x%04x, not 0x%04x!",
+                  resp.cmd_complete.opcode, opcode);
+
+       return resp.status == 0 ? 0 : -1;
+}
+
+typedef struct {
+       uint8_t uart_prefix;
+       hci_command_hdr hci_hdr;
+       uint32_t speed;
+} __attribute__((packed)) texas_speed_change_cmd_t;
+
+static int texas_change_speed(int fd, uint32_t speed)
+{
+       return 0;
+}
+
+static int texas_load_firmware(int fd, const char *firmware) {
+
+       int fw = open(firmware, O_RDONLY);
+
+       fprintf(stdout, "Opening firmware file: %s\n", firmware);
+
+       FAILIF(fw < 0,
+                  "Could not open firmware file %s: %s (%d).\n",
+                  firmware, strerror(errno), errno);
+
+       fprintf(stdout, "Uploading firmware...\n");
+       do {
+               /* Read each command and wait for a response. */
+               unsigned char data[1024];
+               unsigned char cmdp[1 + sizeof(hci_command_hdr)];
+               hci_command_hdr *cmd = (hci_command_hdr *)(cmdp + 1);
+               int nr;
+               nr = read(fw, cmdp, sizeof(cmdp));
+               if (!nr)
+                       break;
+               FAILIF(nr != sizeof(cmdp), "Could not read H4 + HCI header!\n");
+               FAILIF(*cmdp != HCI_COMMAND_PKT, "Command is not an H4 command packet!\n");
+
+               FAILIF(read(fw, data, cmd->plen) != cmd->plen,
+                          "Could not read %d bytes of data for command with opcode %04x!\n",
+                          cmd->plen,
+                          cmd->opcode);
+
+               {
+                       int nw;
+#if 0
+                       fprintf(stdout, "\topcode 0x%04x (%d bytes of data).\n",
+                                       cmd->opcode,
+                                       cmd->plen);
+#endif
+                       struct iovec iov_cmd[2];
+                       iov_cmd[0].iov_base = cmdp;
+                       iov_cmd[0].iov_len      = sizeof(cmdp);
+                       iov_cmd[1].iov_base = data;
+                       iov_cmd[1].iov_len      = cmd->plen;
+                       nw = writev(fd, iov_cmd, 2);
+                       FAILIF(nw != (int) sizeof(cmd) +        cmd->plen,
+                                  "Could not send entire command (sent only %d bytes)!\n",
+                                  nw);
+               }
+
+               /* Wait for response */
+               if (read_command_complete(fd,
+                                                                 cmd->opcode,
+                                                                 cmd->plen) < 0) {
+                       return -1;
+               }
+
+       } while(1);
+       fprintf(stdout, "Firmware upload successful.\n");
+
+       close(fw);
+       return 0;
+}
+
+int texasalt_init(int fd, int speed, struct termios *ti)
+{
+       struct timespec tm = {0, 50000};
+       char cmd[4];
+       unsigned char resp[100];                /* Response */
+       int n;
+
+       memset(resp,'\0', 100);
+
+       /* It is possible to get software version with manufacturer specific
+          HCI command HCI_VS_TI_Version_Number. But the only thing you get more
+          is if this is point-to-point or point-to-multipoint module */
+
+       /* Get Manufacturer and LMP version */
+       cmd[0] = HCI_COMMAND_PKT;
+       cmd[1] = 0x01;
+       cmd[2] = 0x10;
+       cmd[3] = 0x00;
+
+       do {
+               n = write(fd, cmd, 4);
+               if (n < 0) {
+                       perror("Failed to write init command (READ_LOCAL_VERSION_INFORMATION)");
+                       return -1;
+               }
+               if (n < 4) {
+                       fprintf(stderr, "Wanted to write 4 bytes, could only write %d. Stop\n", n);
+                       return -1;
+               }
+
+               /* Read reply. */
+               if (read_hci_event(fd, resp, 100) < 0) {
+                       perror("Failed to read init response (READ_LOCAL_VERSION_INFORMATION)");
+                       return -1;
+               }
+
+               /* Wait for command complete event for our Opcode */
+       } while (resp[4] != cmd[1] && resp[5] != cmd[2]);
+
+       /* Verify manufacturer */
+       if ((resp[11] & 0xFF) != 0x0d)
+               fprintf(stderr,"WARNING : module's manufacturer is not Texas Instrument\n");
+
+       /* Print LMP version */
+       fprintf(stderr, "Texas module LMP version : 0x%02x\n", resp[10] & 0xFF);
+
+       /* Print LMP subversion */
+       {
+               unsigned short lmp_subv = resp[13] | (resp[14] << 8);
+               unsigned short brf_chip = (lmp_subv & 0x7c00) >> 10;
+               static const char *c_brf_chip[8] = {
+                       "unknown",
+                       "unknown",
+                       "brf6100",
+                       "brf6150",
+                       "brf6300",
+                       "brf6350",
+                       "unknown",
+                       "wl1271"
+               };
+               char fw[100];
+
+               fprintf(stderr, "Texas module LMP sub-version : 0x%04x\n", lmp_subv);
+
+               fprintf(stderr,
+                               "\tinternal version freeze: %d\n"
+                               "\tsoftware version: %d\n"
+                               "\tchip: %s (%d)\n",
+                               lmp_subv & 0x7f,
+                               ((lmp_subv & 0x8000) >> (15-3)) | ((lmp_subv & 0x380) >> 7),
+                               ((brf_chip > 7) ? "unknown" : c_brf_chip[brf_chip]),
+                               brf_chip);
+
+               sprintf(fw, "/etc/firmware/%s.bin", c_brf_chip[brf_chip]);
+               texas_load_firmware(fd, fw);
+
+               texas_change_speed(fd, speed);
+       }
+       nanosleep(&tm, NULL);
+       return 0;
+}
diff --git a/tools/hciconfig.8 b/tools/hciconfig.8
new file mode 100644 (file)
index 0000000..35956c4
--- /dev/null
@@ -0,0 +1,277 @@
+.TH HCICONFIG 8 "Nov 11 2002" BlueZ "Linux System Administration"
+.SH NAME
+hciconfig \- configure Bluetooth devices
+.SH SYNOPSIS
+.B hciconfig
+.B \-h
+.br
+.B hciconfig
+.RB [\| \-a \|]
+.br
+.B hciconfig
+.RB [\| \-a \|]
+.B hciX
+.RI [\| command
+.RI [\| "command parameters" \|]\|]
+
+.SH DESCRIPTION
+.LP
+.B hciconfig
+is used to configure Bluetooth devices.
+.I hciX
+is the name of a Bluetooth device installed in the system. If
+.I hciX
+is not given,
+.B hciconfig
+prints name and basic information about all the Bluetooth devices installed in
+the system. If
+.I hciX
+is given but no command is given, it prints basic information on device
+.I hciX
+only. Basic information is
+interface type, BD address, ACL MTU, SCO MTU, flags (up, init, running, raw,
+page scan enabled, inquiry scan enabled, inquiry, authentication enabled,
+encryption enabled).
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+Gives a list of possible commands.
+.TP
+.B \-a, \-\-all
+Other than the basic info, print features, packet type, link policy, link mode,
+name, class, version.
+.SH COMMANDS
+.TP
+.B up
+Open and initialize HCI device.
+.TP
+.B down
+Close HCI device.
+.TP
+.B reset
+Reset HCI device.
+.TP
+.B rstat
+Reset statistic counters.
+.TP
+.B auth
+Enable authentication (sets device to security mode 3).
+.TP
+.B noauth
+Disable authentication.
+.TP
+.B encrypt
+Enable encryption (sets device to security mode 3).
+.TP
+.B noencrypt
+Disable encryption.
+.TP
+.B secmgr
+Enable security manager (current kernel support is limited).
+.TP
+.B nosecmgr
+Disable security manager.
+.TP
+.B piscan
+Enable page and inquiry scan.
+.TP
+.B noscan
+Disable page and inquiry scan.
+.TP
+.B iscan
+Enable inquiry scan, disable page scan.
+.TP
+.B pscan
+Enable page scan, disable inquiry scan.
+.TP
+\fBptype\fP [\fItype\fP]
+With no
+.I type
+, displays the current packet types. Otherwise, all the packet types specified
+by
+.I type
+are set.
+.I type
+is a comma-separated list of packet types, where the possible packet types are
+.BR DM1 ,
+.BR DM3 ,
+.BR DM5 ,
+.BR DH1 ,
+.BR DH3 ,
+.BR DH5 ,
+.BR HV1 ,
+.BR HV2 ,
+.BR HV3 .
+.TP
+.BI name " [name]"
+With no
+.IR name ,
+prints local name. Otherwise, sets local name to
+.IR name .
+.TP
+.BI class " [class]"
+With no
+.IR class ,
+prints class of device. Otherwise, sets class of device to
+.IR class .
+.I
+class
+is a 24-bit hex number describing the class of device, as specified in section
+1.2 of the Bluetooth Assigned Numers document.
+.TP
+.BI voice " [voice]"
+With no
+.IR voice ,
+prints voice setting. Otherwise, sets voice setting to
+.IR voice .
+.I voice
+is a 16-bit hex number describing the voice setting.
+.TP
+.BI iac " [iac]"
+With no
+.IR iac ,
+prints the current IAC setting. Otherwise, sets the IAC to
+.IR iac .
+.TP
+.BI inqtpl " [level]"
+With no
+.IR level ,
+prints out the current inquiry transmit power level. Otherwise, sets
+inquiry transmit power level to
+.IR level .
+.TP
+.BI inqmode " [mode]"
+With no
+.IR mode ,
+prints out the current inquiry mode. Otherwise, sets inquiry mode to
+.IR mode .
+.TP
+.BI inqdata " [data]"
+With no
+.IR name ,
+prints out the current inquiry data. Otherwise, sets inquiry data to
+.IR data .
+.TP
+.BI inqtype " [type]"
+With no
+.IR type ,
+prints out the current inquiry scan type. Otherwise, sets inquiry scan type to
+.IR type .
+.TP
+\fBinqparams\fP [\fIwin\fP:\fIint\fP]
+With no
+.IR win : int ,
+prints inquiry scan window and interval. Otherwise, sets inquiry scan window
+to
+.I win
+slots and inquiry scan interval to
+.I int
+slots.
+.TP
+\fBpageparms\fP [\fIwin\fP:\fIint\fP]
+With no
+.IR win : int ,
+prints page scan window and interval. Otherwise, sets page scan window to
+.I win
+slots and page scan interval to
+.I int
+slots.
+.TP
+.BI pageto " [to]"
+With no
+.IR to ,
+prints page timeout. Otherwise, sets page timeout
+to .I
+to
+slots.
+.TP
+.BI afhmode " [mode]"
+With no
+.IR mode ,
+prints out the current AFH mode. Otherwise, sets AFH mode to
+.IR mode .
+.TP
+.BI sspmode " [mode]"
+With no
+.IR mode ,
+prints out the current Simple Pairing mode. Otherwise, sets Simple Pairing mode to
+.IR mode .
+.TP
+\fBaclmtu\fP \fImtu\fP:\fIpkt\fP
+Sets ACL MTU to
+to
+.I mtu
+bytes and ACL buffer size to
+.I pkt
+packets.
+.TP
+\fBscomtu\fP \fImtu\fP:\fIpkt\fP
+Sets SCO MTU to
+.I mtu
+bytes and SCO buffer size to
+.I pkt
+packets.
+.TP
+.BI putkey " <bdaddr>"
+This command stores the link key for
+.I bdaddr
+on the device.
+.TP
+.BI delkey " <bdaddr>"
+This command deletes the stored link key for
+.I bdaddr
+from the device.
+.TP
+.BI oobdata
+Display local OOB data.
+.TP
+.BI commands
+Display supported commands.
+.TP
+.BI features
+Display device features.
+.TP
+.BI version
+Display version information.
+.TP
+.BI revision
+Display revision information.
+.TP
+.BI lm " [mode]"
+With no
+.I mode
+, prints link mode.
+.B MASTER
+or
+.B SLAVE
+mean, respectively, to ask to become master or to remain slave when a
+connection request comes in. The additional keyword
+.B ACCEPT
+means that baseband  connections will be accepted even if there are no
+listening
+.I AF_BLUETOOTH
+sockets.
+.I mode
+is
+.B NONE
+or a comma-separated list of keywords, where possible keywords are
+.B MASTER
+and
+.B "ACCEPT" .
+.B NONE
+sets link policy to the default behaviour of remaining slave and not accepting
+baseband connections when there are no listening
+.I AF_BLUETOOTH
+sockets. If
+.B MASTER
+is present, the device will ask to become master if a connection request comes
+in. If
+.B ACCEPT
+is present, the device will accept baseband connections even when there are no
+listening
+.I AF_BLUETOOTH
+sockets.
+.SH AUTHORS
+Written by Maxim Krasnyansky <maxk@qualcomm.com> and Marcel Holtmann <marcel@holtmann.org>
+.PP
+man page by Fabrizio Gennari <fabrizio.gennari@philips.com>
diff --git a/tools/hciconfig.c b/tools/hciconfig.c
new file mode 100644 (file)
index 0000000..aa6d009
--- /dev/null
@@ -0,0 +1,2036 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "textfile.h"
+#include "csr.h"
+
+static struct hci_dev_info di;
+static int all;
+
+static void print_dev_hdr(struct hci_dev_info *di);
+static void print_dev_info(int ctl, struct hci_dev_info *di);
+
+static void print_dev_list(int ctl, int flags)
+{
+       struct hci_dev_list_req *dl;
+       struct hci_dev_req *dr;
+       int i;
+
+       if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) +
+               sizeof(uint16_t)))) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+       dl->dev_num = HCI_MAX_DEV;
+       dr = dl->dev_req;
+
+       if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
+               perror("Can't get device list");
+               exit(1);
+       }
+
+       for (i = 0; i< dl->dev_num; i++) {
+               di.dev_id = (dr+i)->dev_id;
+               if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0)
+                       continue;
+               if (hci_test_bit(HCI_RAW, &di.flags) &&
+                               !bacmp(&di.bdaddr, BDADDR_ANY)) {
+                       int dd = hci_open_dev(di.dev_id);
+                       hci_read_bd_addr(dd, &di.bdaddr, 1000);
+                       hci_close_dev(dd);
+               }
+               print_dev_info(ctl, &di);
+       }
+}
+
+static void print_pkt_type(struct hci_dev_info *di)
+{
+       char *str;
+       str = hci_ptypetostr(di->pkt_type);
+       printf("\tPacket type: %s\n", str);
+       bt_free(str);
+}
+
+static void print_link_policy(struct hci_dev_info *di)
+{
+       printf("\tLink policy: %s\n", hci_lptostr(di->link_policy));
+}
+
+static void print_link_mode(struct hci_dev_info *di)
+{
+       char *str;
+       str =  hci_lmtostr(di->link_mode);
+       printf("\tLink mode: %s\n", str);
+       bt_free(str);
+}
+
+static void print_dev_features(struct hci_dev_info *di, int format)
+{
+       printf("\tFeatures: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x "
+                               "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n",
+               di->features[0], di->features[1], di->features[2],
+               di->features[3], di->features[4], di->features[5],
+               di->features[6], di->features[7]);
+
+       if (format) {
+               char *tmp = lmp_featurestostr(di->features, "\t\t", 63);
+               printf("%s\n", tmp);
+               bt_free(tmp);
+       }
+}
+
+static void print_le_states(uint64_t states)
+{
+       int i;
+       const char *le_states[] = {
+               "Non-connectable Advertising State" ,
+               "Scannable Advertising State",
+               "Connectable Advertising State",
+               "Directed Advertising State",
+               "Passive Scanning State",
+               "Active Scanning State",
+               "Initiating State/Connection State in Master Role",
+               "Connection State in the Slave Role",
+               "Non-connectable Advertising State and Passive Scanning State combination",
+               "Scannable Advertising State and Passive Scanning State combination",
+               "Connectable Advertising State and Passive Scanning State combination",
+               "Directed Advertising State and Passive Scanning State combination",
+               "Non-connectable Advertising State and Active Scanning State combination",
+               "Scannable Advertising State and Active Scanning State combination",
+               "Connectable Advertising State and Active Scanning State combination",
+               "Directed Advertising State and Active Scanning State combination",
+               "Non-connectable Advertising State and Initiating State combination",
+               "Scannable Advertising State and Initiating State combination",
+               "Non-connectable Advertising State and Master Role combination",
+               "Scannable Advertising State and Master Role combination",
+               "Non-connectable Advertising State and Slave Role combination",
+               "Scannable Advertising State and Slave Role combination",
+               "Passive Scanning State and Initiating State combination",
+               "Active Scanning State and Initiating State combination",
+               "Passive Scanning State and Master Role combination",
+               "Active Scanning State and Master Role combination",
+               "Passive Scanning State and Slave Role combination",
+               "Active Scanning State and Slave Role combination",
+               "Initiating State and Master Role combination/Master Role and Master Role combination",
+               NULL
+       };
+
+       printf("Supported link layer states:\n");
+       for (i = 0; le_states[i]; i++) {
+               const char *status;
+
+               status = states & (1 << i) ? "YES" : "NO ";
+               printf("\t%s %s\n", status, le_states[i]);
+       }
+}
+
+static void cmd_rstat(int ctl, int hdev, char *opt)
+{
+       /* Reset HCI device stat counters */
+       if (ioctl(ctl, HCIDEVRESTAT, hdev) < 0) {
+               fprintf(stderr, "Can't reset stats counters hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+}
+
+static void cmd_scan(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_req dr;
+
+       dr.dev_id  = hdev;
+       dr.dev_opt = SCAN_DISABLED;
+       if (!strcmp(opt, "iscan"))
+               dr.dev_opt = SCAN_INQUIRY;
+       else if (!strcmp(opt, "pscan"))
+               dr.dev_opt = SCAN_PAGE;
+       else if (!strcmp(opt, "piscan"))
+               dr.dev_opt = SCAN_PAGE | SCAN_INQUIRY;
+
+       if (ioctl(ctl, HCISETSCAN, (unsigned long) &dr) < 0) {
+               fprintf(stderr, "Can't set scan mode on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+}
+
+static void cmd_le_addr(int ctl, int hdev, char *opt)
+{
+       struct hci_request rq;
+       le_set_random_address_cp cp;
+       uint8_t status;
+       int dd, err, ret;
+
+       if (!opt)
+               return;
+
+       if (hdev < 0)
+               hdev = hci_get_route(NULL);
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               err = errno;
+               fprintf(stderr, "Could not open device: %s(%d)\n",
+                                                       strerror(err), err);
+               exit(1);
+       }
+
+       memset(&cp, 0, sizeof(cp));
+
+       str2ba(opt, &cp.bdaddr);
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_SET_RANDOM_ADDRESS;
+       rq.cparam = &cp;
+       rq.clen = LE_SET_RANDOM_ADDRESS_CP_SIZE;
+       rq.rparam = &status;
+       rq.rlen = 1;
+
+       ret = hci_send_req(dd, &rq, 1000);
+       if (status || ret < 0) {
+               err = errno;
+               fprintf(stderr, "Can't set random address for hci%d: "
+                                       "%s (%d)\n", hdev, strerror(err), err);
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_le_adv(int ctl, int hdev, char *opt)
+{
+       struct hci_request rq;
+       le_set_advertise_enable_cp advertise_cp;
+       uint8_t status;
+       int dd, ret;
+
+       if (hdev < 0)
+               hdev = hci_get_route(NULL);
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               perror("Could not open device");
+               exit(1);
+       }
+
+       memset(&advertise_cp, 0, sizeof(advertise_cp));
+       if (strcmp(opt, "noleadv") == 0)
+               advertise_cp.enable = 0x00;
+       else
+               advertise_cp.enable = 0x01;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf = OGF_LE_CTL;
+       rq.ocf = OCF_LE_SET_ADVERTISE_ENABLE;
+       rq.cparam = &advertise_cp;
+       rq.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE;
+       rq.rparam = &status;
+       rq.rlen = 1;
+
+       ret = hci_send_req(dd, &rq, 1000);
+
+       hci_close_dev(dd);
+
+       if (ret < 0) {
+               fprintf(stderr, "Can't set advertise mode on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (status) {
+               fprintf(stderr, "LE set advertise enable on hci%d returned status %d\n",
+                                               hdev, status);
+               exit(1);
+       }
+}
+
+static void cmd_le_states(int ctl, int hdev, char *opt)
+{
+       le_read_supported_states_rp rp;
+       struct hci_request rq;
+       int err, dd;
+
+       if (hdev < 0)
+               hdev = hci_get_route(NULL);
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       memset(&rp, 0, sizeof(rp));
+       memset(&rq, 0, sizeof(rq));
+
+       rq.ogf    = OGF_LE_CTL;
+       rq.ocf    = OCF_LE_READ_SUPPORTED_STATES;
+       rq.rparam = &rp;
+       rq.rlen   = LE_READ_SUPPORTED_STATES_RP_SIZE;
+
+       err = hci_send_req(dd, &rq, 1000);
+
+       hci_close_dev(dd);
+
+       if (err < 0) {
+               fprintf(stderr, "Can't read LE supported states on hci%d:"
+                               " %s(%d)\n", hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (rp.status) {
+               fprintf(stderr, "Read LE supported states on hci%d"
+                               " returned status %d\n", hdev, rp.status);
+               exit(1);
+       }
+
+       print_le_states(rp.states);
+}
+
+static void cmd_iac(int ctl, int hdev, char *opt)
+{
+       int s = hci_open_dev(hdev);
+
+       if (s < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+       if (opt) {
+               int l = strtoul(opt, 0, 16);
+               uint8_t lap[3];
+               if (!strcasecmp(opt, "giac")) {
+                       l = 0x9e8b33;
+               } else if (!strcasecmp(opt, "liac")) {
+                       l = 0x9e8b00;
+               } else if (l < 0x9e8b00 || l > 0x9e8b3f) {
+                       printf("Invalid access code 0x%x\n", l);
+                       exit(1);
+               }
+               lap[0] = (l & 0xff);
+               lap[1] = (l >> 8) & 0xff;
+               lap[2] = (l >> 16) & 0xff;
+               if (hci_write_current_iac_lap(s, 1, lap, 1000) < 0) {
+                       printf("Failed to set IAC on hci%d: %s\n", hdev, strerror(errno));
+                       exit(1);
+               }
+       } else {
+               uint8_t lap[3 * MAX_IAC_LAP];
+               int i, j;
+               uint8_t n;
+               if (hci_read_current_iac_lap(s, &n, lap, 1000) < 0) {
+                       printf("Failed to read IAC from hci%d: %s\n", hdev, strerror(errno));
+                       exit(1);
+               }
+               print_dev_hdr(&di);
+               printf("\tIAC: ");
+               for (i = 0; i < n; i++) {
+                       printf("0x");
+                       for (j = 3; j--; )
+                               printf("%02x", lap[j + 3 * i]);
+                       if (i < n - 1)
+                               printf(", ");
+               }
+               printf("\n");
+       }
+       close(s);
+}
+
+static void cmd_auth(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_req dr;
+
+       dr.dev_id = hdev;
+       if (!strcmp(opt, "auth"))
+               dr.dev_opt = AUTH_ENABLED;
+       else
+               dr.dev_opt = AUTH_DISABLED;
+
+       if (ioctl(ctl, HCISETAUTH, (unsigned long) &dr) < 0) {
+               fprintf(stderr, "Can't set auth on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+}
+
+static void cmd_encrypt(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_req dr;
+
+       dr.dev_id = hdev;
+       if (!strcmp(opt, "encrypt"))
+               dr.dev_opt = ENCRYPT_P2P;
+       else
+               dr.dev_opt = ENCRYPT_DISABLED;
+
+       if (ioctl(ctl, HCISETENCRYPT, (unsigned long) &dr) < 0) {
+               fprintf(stderr, "Can't set encrypt on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+}
+
+static void cmd_up(int ctl, int hdev, char *opt)
+{
+       /* Start HCI device */
+       if (ioctl(ctl, HCIDEVUP, hdev) < 0) {
+               if (errno == EALREADY)
+                       return;
+               fprintf(stderr, "Can't init device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+}
+
+static void cmd_down(int ctl, int hdev, char *opt)
+{
+       /* Stop HCI device */
+       if (ioctl(ctl, HCIDEVDOWN, hdev) < 0) {
+               fprintf(stderr, "Can't down device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+}
+
+static void cmd_reset(int ctl, int hdev, char *opt)
+{
+       /* Reset HCI device */
+#if 0
+       if (ioctl(ctl, HCIDEVRESET, hdev) < 0 ){
+               fprintf(stderr, "Reset failed for device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+#endif
+       cmd_down(ctl, hdev, "down");
+       cmd_up(ctl, hdev, "up");
+}
+
+static void cmd_ptype(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_req dr;
+
+       dr.dev_id = hdev;
+
+       if (hci_strtoptype(opt, &dr.dev_opt)) {
+               if (ioctl(ctl, HCISETPTYPE, (unsigned long) &dr) < 0) {
+                       fprintf(stderr, "Can't set pkttype on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               print_dev_hdr(&di);
+               print_pkt_type(&di);
+       }
+}
+
+static void cmd_lp(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_req dr;
+
+       dr.dev_id = hdev;
+
+       if (hci_strtolp(opt, &dr.dev_opt)) {
+               if (ioctl(ctl, HCISETLINKPOL, (unsigned long) &dr) < 0) {
+                       fprintf(stderr, "Can't set link policy on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               print_dev_hdr(&di);
+               print_link_policy(&di);
+       }
+}
+
+static void cmd_lm(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_req dr;
+
+       dr.dev_id = hdev;
+
+       if (hci_strtolm(opt, &dr.dev_opt)) {
+               if (ioctl(ctl, HCISETLINKMODE, (unsigned long) &dr) < 0) {
+                       fprintf(stderr, "Can't set default link mode on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               print_dev_hdr(&di);
+               print_link_mode(&di);
+       }
+}
+
+static void cmd_aclmtu(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_req dr = { dev_id: hdev };
+       uint16_t mtu, mpkt;
+
+       if (!opt)
+               return;
+
+       if (sscanf(opt, "%4hu:%4hu", &mtu, &mpkt) != 2)
+               return;
+
+       dr.dev_opt = htobl(htobs(mpkt) | (htobs(mtu) << 16));
+
+       if (ioctl(ctl, HCISETACLMTU, (unsigned long) &dr) < 0) {
+               fprintf(stderr, "Can't set ACL mtu on hci%d: %s(%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+}
+
+static void cmd_scomtu(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_req dr = { dev_id: hdev };
+       uint16_t mtu, mpkt;
+
+       if (!opt)
+               return;
+
+       if (sscanf(opt, "%4hu:%4hu", &mtu, &mpkt) != 2)
+               return;
+
+       dr.dev_opt = htobl(htobs(mpkt) | (htobs(mtu) << 16));
+
+       if (ioctl(ctl, HCISETSCOMTU, (unsigned long) &dr) < 0) {
+               fprintf(stderr, "Can't set SCO mtu on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+}
+
+static void cmd_features(int ctl, int hdev, char *opt)
+{
+       uint8_t features[8], max_page = 0;
+       char *tmp;
+       int i, dd;
+
+       if (!(di.features[7] & LMP_EXT_FEAT)) {
+               print_dev_hdr(&di);
+               print_dev_features(&di, 1);
+               return;
+       }
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (hci_read_local_ext_features(dd, 0, &max_page, features, 1000) < 0) {
+               fprintf(stderr, "Can't read extended features hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       print_dev_hdr(&di);
+       printf("\tFeatures%s: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x "
+                               "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n",
+               (max_page > 0) ? " page 0" : "",
+               features[0], features[1], features[2], features[3],
+               features[4], features[5], features[6], features[7]);
+
+       tmp = lmp_featurestostr(di.features, "\t\t", 63);
+       printf("%s\n", tmp);
+       bt_free(tmp);
+
+       for (i = 1; i <= max_page; i++) {
+               if (hci_read_local_ext_features(dd, i, NULL,
+                                                       features, 1000) < 0)
+                       continue;
+
+               printf("\tFeatures page %d: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x "
+                                       "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", i,
+                       features[0], features[1], features[2], features[3],
+                       features[4], features[5], features[6], features[7]);
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_name(int ctl, int hdev, char *opt)
+{
+       int dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (opt) {
+               if (hci_write_local_name(dd, opt, 2000) < 0) {
+                       fprintf(stderr, "Can't change local name on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               char name[249];
+               int i;
+
+               if (hci_read_local_name(dd, sizeof(name), name, 1000) < 0) {
+                       fprintf(stderr, "Can't read local name on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+
+               for (i = 0; i < 248 && name[i]; i++) {
+                       if ((unsigned char) name[i] < 32 || name[i] == 127)
+                               name[i] = '.';
+               }
+
+               name[248] = '\0';
+
+               print_dev_hdr(&di);
+               printf("\tName: '%s'\n", name);
+       }
+
+       hci_close_dev(dd);
+}
+
+/*
+ * see http://www.bluetooth.org/assigned-numbers/baseband.htm --- all
+ * strings are reproduced verbatim
+ */
+static char *get_minor_device_name(int major, int minor)
+{
+       switch (major) {
+       case 0: /* misc */
+               return "";
+       case 1: /* computer */
+               switch (minor) {
+               case 0:
+                       return "Uncategorized";
+               case 1:
+                       return "Desktop workstation";
+               case 2:
+                       return "Server";
+               case 3:
+                       return "Laptop";
+               case 4:
+                       return "Handheld";
+               case 5:
+                       return "Palm";
+               case 6:
+                       return "Wearable";
+               }
+               break;
+       case 2: /* phone */
+               switch (minor) {
+               case 0:
+                       return "Uncategorized";
+               case 1:
+                       return "Cellular";
+               case 2:
+                       return "Cordless";
+               case 3:
+                       return "Smart phone";
+               case 4:
+                       return "Wired modem or voice gateway";
+               case 5:
+                       return "Common ISDN Access";
+               case 6:
+                       return "Sim Card Reader";
+               }
+               break;
+       case 3: /* lan access */
+               if (minor == 0)
+                       return "Uncategorized";
+               switch (minor / 8) {
+               case 0:
+                       return "Fully available";
+               case 1:
+                       return "1-17% utilized";
+               case 2:
+                       return "17-33% utilized";
+               case 3:
+                       return "33-50% utilized";
+               case 4:
+                       return "50-67% utilized";
+               case 5:
+                       return "67-83% utilized";
+               case 6:
+                       return "83-99% utilized";
+               case 7:
+                       return "No service available";
+               }
+               break;
+       case 4: /* audio/video */
+               switch (minor) {
+               case 0:
+                       return "Uncategorized";
+               case 1:
+                       return "Device conforms to the Headset profile";
+               case 2:
+                       return "Hands-free";
+                       /* 3 is reserved */
+               case 4:
+                       return "Microphone";
+               case 5:
+                       return "Loudspeaker";
+               case 6:
+                       return "Headphones";
+               case 7:
+                       return "Portable Audio";
+               case 8:
+                       return "Car Audio";
+               case 9:
+                       return "Set-top box";
+               case 10:
+                       return "HiFi Audio Device";
+               case 11:
+                       return "VCR";
+               case 12:
+                       return "Video Camera";
+               case 13:
+                       return "Camcorder";
+               case 14:
+                       return "Video Monitor";
+               case 15:
+                       return "Video Display and Loudspeaker";
+               case 16:
+                       return "Video Conferencing";
+                       /* 17 is reserved */
+               case 18:
+                       return "Gaming/Toy";
+               }
+               break;
+       case 5: /* peripheral */ {
+               static char cls_str[48];
+
+               cls_str[0] = '\0';
+
+               switch (minor & 48) {
+               case 16:
+                       strncpy(cls_str, "Keyboard", sizeof(cls_str));
+                       break;
+               case 32:
+                       strncpy(cls_str, "Pointing device", sizeof(cls_str));
+                       break;
+               case 48:
+                       strncpy(cls_str, "Combo keyboard/pointing device", sizeof(cls_str));
+                       break;
+               }
+               if ((minor & 15) && (strlen(cls_str) > 0))
+                       strcat(cls_str, "/");
+
+               switch (minor & 15) {
+               case 0:
+                       break;
+               case 1:
+                       strncat(cls_str, "Joystick", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 2:
+                       strncat(cls_str, "Gamepad", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 3:
+                       strncat(cls_str, "Remote control", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 4:
+                       strncat(cls_str, "Sensing device", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 5:
+                       strncat(cls_str, "Digitizer tablet", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 6:
+                       strncat(cls_str, "Card reader", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               default:
+                       strncat(cls_str, "(reserved)", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               }
+               if (strlen(cls_str) > 0)
+                       return cls_str;
+       }
+       case 6: /* imaging */
+               if (minor & 4)
+                       return "Display";
+               if (minor & 8)
+                       return "Camera";
+               if (minor & 16)
+                       return "Scanner";
+               if (minor & 32)
+                       return "Printer";
+               break;
+       case 7: /* wearable */
+               switch (minor) {
+               case 1:
+                       return "Wrist Watch";
+               case 2:
+                       return "Pager";
+               case 3:
+                       return "Jacket";
+               case 4:
+                       return "Helmet";
+               case 5:
+                       return "Glasses";
+               }
+               break;
+       case 8: /* toy */
+               switch (minor) {
+               case 1:
+                       return "Robot";
+               case 2:
+                       return "Vehicle";
+               case 3:
+                       return "Doll / Action Figure";
+               case 4:
+                       return "Controller";
+               case 5:
+                       return "Game";
+               }
+               break;
+       case 63:        /* uncategorised */
+               return "";
+       }
+       return "Unknown (reserved) minor device class";
+}
+
+static void cmd_class(int ctl, int hdev, char *opt)
+{
+       static const char *services[] = { "Positioning",
+                                       "Networking",
+                                       "Rendering",
+                                       "Capturing",
+                                       "Object Transfer",
+                                       "Audio",
+                                       "Telephony",
+                                       "Information" };
+       static const char *major_devices[] = { "Miscellaneous",
+                                       "Computer",
+                                       "Phone",
+                                       "LAN Access",
+                                       "Audio/Video",
+                                       "Peripheral",
+                                       "Imaging",
+                                       "Uncategorized" };
+       int s = hci_open_dev(hdev);
+
+       if (s < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+       if (opt) {
+               uint32_t cod = strtoul(opt, NULL, 16);
+               if (hci_write_class_of_dev(s, cod, 2000) < 0) {
+                       fprintf(stderr, "Can't write local class of device on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint8_t cls[3];
+               if (hci_read_class_of_dev(s, cls, 1000) < 0) {
+                       fprintf(stderr, "Can't read class of device on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+               print_dev_hdr(&di);
+               printf("\tClass: 0x%02x%02x%02x\n", cls[2], cls[1], cls[0]);
+               printf("\tService Classes: ");
+               if (cls[2]) {
+                       unsigned int i;
+                       int first = 1;
+                       for (i = 0; i < (sizeof(services) / sizeof(*services)); i++)
+                               if (cls[2] & (1 << i)) {
+                                       if (!first)
+                                               printf(", ");
+                                       printf("%s", services[i]);
+                                       first = 0;
+                               }
+               } else
+                       printf("Unspecified");
+               printf("\n\tDevice Class: ");
+               if ((cls[1] & 0x1f) >= sizeof(major_devices) / sizeof(*major_devices))
+                       printf("Invalid Device Class!\n");
+               else
+                       printf("%s, %s\n", major_devices[cls[1] & 0x1f],
+                               get_minor_device_name(cls[1] & 0x1f, cls[0] >> 2));
+       }
+}
+
+static void cmd_voice(int ctl, int hdev, char *opt)
+{
+       static char *icf[] = {  "Linear",
+                               "u-Law",
+                               "A-Law",
+                               "Reserved" };
+
+       static char *idf[] = {  "1's complement",
+                               "2's complement",
+                               "Sign-Magnitude",
+                               "Reserved" };
+
+       static char *iss[] = {  "8 bit",
+                               "16 bit" };
+
+       static char *acf[] = {  "CVSD",
+                               "u-Law",
+                               "A-Law",
+                               "Reserved" };
+
+       int s = hci_open_dev(hdev);
+
+       if (s < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+       if (opt) {
+               uint16_t vs = htobs(strtoul(opt, NULL, 16));
+               if (hci_write_voice_setting(s, vs, 2000) < 0) {
+                       fprintf(stderr, "Can't write voice setting on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint16_t vs;
+               uint8_t ic;
+               if (hci_read_voice_setting(s, &vs, 1000) < 0) {
+                       fprintf(stderr, "Can't read voice setting on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+               vs = htobs(vs);
+               ic = (vs & 0x0300) >> 8;
+               print_dev_hdr(&di);
+               printf("\tVoice setting: 0x%04x%s\n", vs,
+                       ((vs & 0x03fc) == 0x0060) ? " (Default Condition)" : "");
+               printf("\tInput Coding: %s\n", icf[ic]);
+               printf("\tInput Data Format: %s\n", idf[(vs & 0xc0) >> 6]);
+
+               if (!ic) {
+                       printf("\tInput Sample Size: %s\n",
+                               iss[(vs & 0x20) >> 5]);
+                       printf("\t# of bits padding at MSB: %d\n",
+                               (vs & 0x1c) >> 2);
+               }
+               printf("\tAir Coding Format: %s\n", acf[vs & 0x03]);
+       }
+}
+
+static int get_link_key(const bdaddr_t *local, const bdaddr_t *peer,
+                       uint8_t *key)
+{
+       char filename[PATH_MAX + 1], addr[18], tmp[3], *str;
+       int i;
+
+       ba2str(local, addr);
+       create_name(filename, PATH_MAX, STORAGEDIR, addr, "linkkeys");
+
+       ba2str(peer, addr);
+       str = textfile_get(filename, addr);
+       if (!str)
+               return -EIO;
+
+       memset(tmp, 0, sizeof(tmp));
+       for (i = 0; i < 16; i++) {
+               memcpy(tmp, str + (i * 2), 2);
+               key[i] = (uint8_t) strtol(tmp, NULL, 16);
+       }
+
+       free(str);
+
+       return 0;
+}
+
+static void cmd_putkey(int ctl, int hdev, char *opt)
+{
+       struct hci_dev_info di;
+       bdaddr_t bdaddr;
+       uint8_t key[16];
+       int dd;
+
+       if (!opt)
+               return;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (hci_devinfo(hdev, &di) < 0) {
+               fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       str2ba(opt, &bdaddr);
+       if (get_link_key(&di.bdaddr, &bdaddr, key) < 0) {
+               fprintf(stderr, "Can't find link key for %s on hci%d\n", opt, hdev);
+               exit(1);
+       }
+
+       if (hci_write_stored_link_key(dd, &bdaddr, key, 1000) < 0) {
+               fprintf(stderr, "Can't write stored link key on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_delkey(int ctl, int hdev, char *opt)
+{
+       bdaddr_t bdaddr;
+       uint8_t all;
+       int dd;
+
+       if (!opt)
+               return;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (!strcasecmp(opt, "all")) {
+               bacpy(&bdaddr, BDADDR_ANY);
+               all = 1;
+       } else {
+               str2ba(opt, &bdaddr);
+               all = 0;
+       }
+
+       if (hci_delete_stored_link_key(dd, &bdaddr, all, 1000) < 0) {
+               fprintf(stderr, "Can't delete stored link key on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_oob_data(int ctl, int hdev, char *opt)
+{
+       uint8_t hash[16], randomizer[16];
+       int i, dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (hci_read_local_oob_data(dd, hash, randomizer, 1000) < 0) {
+               fprintf(stderr, "Can't read local OOB data on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       print_dev_hdr(&di);
+       printf("\tOOB Hash:  ");
+       for (i = 0; i < 16; i++)
+               printf(" %02x", hash[i]);
+       printf("\n\tRandomizer:");
+       for (i = 0; i < 16; i++)
+               printf(" %02x", randomizer[i]);
+       printf("\n");
+
+       hci_close_dev(dd);
+}
+
+static void cmd_commands(int ctl, int hdev, char *opt)
+{
+       uint8_t cmds[64];
+       char *str;
+       int i, n, dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (hci_read_local_commands(dd, cmds, 1000) < 0) {
+               fprintf(stderr, "Can't read support commands on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       print_dev_hdr(&di);
+       for (i = 0; i < 64; i++) {
+               if (!cmds[i])
+                       continue;
+
+               printf("%s Octet %-2d = 0x%02x (Bit",
+                       i ? "\t\t ": "\tCommands:", i, cmds[i]);
+               for (n = 0; n < 8; n++)
+                       if (cmds[i] & (1 << n))
+                               printf(" %d", n);
+               printf(")\n");
+       }
+
+       str = hci_commandstostr(cmds, "\t", 71);
+       printf("%s\n", str);
+       bt_free(str);
+
+       hci_close_dev(dd);
+}
+
+static void cmd_version(int ctl, int hdev, char *opt)
+{
+       struct hci_version ver;
+       char *hciver, *lmpver;
+       int dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (hci_read_local_version(dd, &ver, 1000) < 0) {
+               fprintf(stderr, "Can't read version info hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       hciver = hci_vertostr(ver.hci_ver);
+       lmpver = lmp_vertostr(ver.hci_ver);
+
+       print_dev_hdr(&di);
+       printf("\tHCI Version: %s (0x%x)  Revision: 0x%x\n"
+               "\tLMP Version: %s (0x%x)  Subversion: 0x%x\n"
+               "\tManufacturer: %s (%d)\n",
+               hciver ? hciver : "n/a", ver.hci_ver, ver.hci_rev,
+               lmpver ? lmpver : "n/a", ver.lmp_ver, ver.lmp_subver,
+               bt_compidtostr(ver.manufacturer), ver.manufacturer);
+
+       if (hciver)
+               bt_free(hciver);
+       if (lmpver)
+               bt_free(lmpver);
+
+       hci_close_dev(dd);
+}
+
+static void cmd_inq_tpl(int ctl, int hdev, char *opt)
+{
+       int dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (opt) {
+               int8_t level = atoi(opt);
+
+               if (hci_write_inquiry_transmit_power_level(dd, level, 2000) < 0) {
+                       fprintf(stderr, "Can't set inquiry transmit power level on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               int8_t level;
+
+               if (hci_read_inq_response_tx_power_level(dd, &level, 1000) < 0) {
+                       fprintf(stderr, "Can't read inquiry transmit power level on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+
+               print_dev_hdr(&di);
+               printf("\tInquiry transmit power level: %d\n", level);
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_inq_mode(int ctl, int hdev, char *opt)
+{
+       int dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (opt) {
+               uint8_t mode = atoi(opt);
+
+               if (hci_write_inquiry_mode(dd, mode, 2000) < 0) {
+                       fprintf(stderr, "Can't set inquiry mode on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint8_t mode;
+
+               if (hci_read_inquiry_mode(dd, &mode, 1000) < 0) {
+                       fprintf(stderr, "Can't read inquiry mode on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+
+               print_dev_hdr(&di);
+               printf("\tInquiry mode: ");
+               switch (mode) {
+               case 0:
+                       printf("Standard Inquiry\n");
+                       break;
+               case 1:
+                       printf("Inquiry with RSSI\n");
+                       break;
+               case 2:
+                       printf("Inquiry with RSSI or Extended Inquiry\n");
+                       break;
+               default:
+                       printf("Unknown (0x%02x)\n", mode);
+                       break;
+               }
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_inq_data(int ctl, int hdev, char *opt)
+{
+       int i, dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (opt) {
+               uint8_t fec = 0, data[240];
+               char tmp[3];
+               int i, size;
+
+               memset(data, 0, sizeof(data));
+
+               memset(tmp, 0, sizeof(tmp));
+               size = (strlen(opt) + 1) / 2;
+               if (size > 240)
+                       size = 240;
+
+               for (i = 0; i < size; i++) {
+                       memcpy(tmp, opt + (i * 2), 2);
+                       data[i] = strtol(tmp, NULL, 16);
+               }
+
+               if (hci_write_ext_inquiry_response(dd, fec, data, 2000) < 0) {
+                       fprintf(stderr, "Can't set extended inquiry response on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint8_t fec, data[240], len, type, *ptr;
+               char *str;
+
+               if (hci_read_ext_inquiry_response(dd, &fec, data, 1000) < 0) {
+                       fprintf(stderr, "Can't read extended inquiry response on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+
+               print_dev_hdr(&di);
+               printf("\tFEC %s\n\t\t", fec ? "enabled" : "disabled");
+               for (i = 0; i < 240; i++)
+                       printf("%02x%s%s", data[i], (i + 1) % 8 ? "" : " ",
+                               (i + 1) % 16 ? " " : (i < 239 ? "\n\t\t" : "\n"));
+
+               ptr = data;
+               while (*ptr) {
+                       len = *ptr++;
+                       type = *ptr++;
+                       switch (type) {
+                       case 0x01:
+                               printf("\tFlags:");
+                               for (i = 0; i < len - 1; i++)
+                                       printf(" 0x%2.2x", *((uint8_t *) (ptr + i)));
+                               printf("\n");
+                               break;
+                       case 0x02:
+                       case 0x03:
+                               printf("\t%s service classes:",
+                                       type == 0x02 ? "Shortened" : "Complete");
+                               for (i = 0; i < (len - 1) / 2; i++) {
+                                       uint16_t val = btohs(bt_get_unaligned((uint16_t *) (ptr + (i * 2))));
+                                       printf(" 0x%4.4x", val);
+                               }
+                               printf("\n");
+                               break;
+                       case 0x08:
+                       case 0x09:
+                               str = malloc(len);
+                               if (str) {
+                                       snprintf(str, len, "%s", ptr);
+                                       for (i = 0; i < len - 1; i++) {
+                                               if ((unsigned char) str[i] < 32 || str[i] == 127)
+                                                       str[i] = '.';
+                                       }
+                                       printf("\t%s local name: \'%s\'\n",
+                                               type == 0x08 ? "Shortened" : "Complete", str);
+                                       free(str);
+                               }
+                               break;
+                       case 0x0a:
+                               printf("\tTX power level: %d\n", *((int8_t *) ptr));
+                               break;
+                       case 0x10:
+                               printf("\tDevice ID with %d bytes data\n",
+                                                               len - 1);
+                               break;
+                       default:
+                               printf("\tUnknown type 0x%02x with %d bytes data\n",
+                                                               type, len - 1);
+                               break;
+                       }
+
+                       ptr += (len - 1);
+               }
+
+               printf("\n");
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_inq_type(int ctl, int hdev, char *opt)
+{
+       int dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (opt) {
+               uint8_t type = atoi(opt);
+
+               if (hci_write_inquiry_scan_type(dd, type, 2000) < 0) {
+                       fprintf(stderr, "Can't set inquiry scan type on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint8_t type;
+
+               if (hci_read_inquiry_scan_type(dd, &type, 1000) < 0) {
+                       fprintf(stderr, "Can't read inquiry scan type on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+
+               print_dev_hdr(&di);
+               printf("\tInquiry scan type: %s\n",
+                       type == 1 ? "Interlaced Inquiry Scan" : "Standard Inquiry Scan");
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_inq_parms(int ctl, int hdev, char *opt)
+{
+       struct hci_request rq;
+       int s;
+
+       if ((s = hci_open_dev(hdev)) < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       memset(&rq, 0, sizeof(rq));
+
+       if (opt) {
+               unsigned int window, interval;
+               write_inq_activity_cp cp;
+
+               if (sscanf(opt,"%4u:%4u", &window, &interval) != 2) {
+                       printf("Invalid argument format\n");
+                       exit(1);
+               }
+
+               rq.ogf = OGF_HOST_CTL;
+               rq.ocf = OCF_WRITE_INQ_ACTIVITY;
+               rq.cparam = &cp;
+               rq.clen = WRITE_INQ_ACTIVITY_CP_SIZE;
+
+               cp.window = htobs((uint16_t) window);
+               cp.interval = htobs((uint16_t) interval);
+
+               if (window < 0x12 || window > 0x1000)
+                       printf("Warning: inquiry window out of range!\n");
+
+               if (interval < 0x12 || interval > 0x1000)
+                       printf("Warning: inquiry interval out of range!\n");
+
+               if (hci_send_req(s, &rq, 2000) < 0) {
+                       fprintf(stderr, "Can't set inquiry parameters name on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint16_t window, interval;
+               read_inq_activity_rp rp;
+
+               rq.ogf = OGF_HOST_CTL;
+               rq.ocf = OCF_READ_INQ_ACTIVITY;
+               rq.rparam = &rp;
+               rq.rlen = READ_INQ_ACTIVITY_RP_SIZE;
+
+               if (hci_send_req(s, &rq, 1000) < 0) {
+                       fprintf(stderr, "Can't read inquiry parameters on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+               if (rp.status) {
+                       printf("Read inquiry parameters on hci%d returned status %d\n",
+                                                       hdev, rp.status);
+                       exit(1);
+               }
+               print_dev_hdr(&di);
+
+               window   = btohs(rp.window);
+               interval = btohs(rp.interval);
+               printf("\tInquiry interval: %u slots (%.2f ms), window: %u slots (%.2f ms)\n",
+                               interval, (float)interval * 0.625, window, (float)window * 0.625);
+       }
+}
+
+static void cmd_page_parms(int ctl, int hdev, char *opt)
+{
+       struct hci_request rq;
+       int s;
+
+       if ((s = hci_open_dev(hdev)) < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       memset(&rq, 0, sizeof(rq));
+
+       if (opt) {
+               unsigned int window, interval;
+               write_page_activity_cp cp;
+
+               if (sscanf(opt,"%4u:%4u", &window, &interval) != 2) {
+                       printf("Invalid argument format\n");
+                       exit(1);
+               }
+
+               rq.ogf = OGF_HOST_CTL;
+               rq.ocf = OCF_WRITE_PAGE_ACTIVITY;
+               rq.cparam = &cp;
+               rq.clen = WRITE_PAGE_ACTIVITY_CP_SIZE;
+
+               cp.window = htobs((uint16_t) window);
+               cp.interval = htobs((uint16_t) interval);
+
+               if (window < 0x12 || window > 0x1000)
+                       printf("Warning: page window out of range!\n");
+
+               if (interval < 0x12 || interval > 0x1000)
+                       printf("Warning: page interval out of range!\n");
+
+               if (hci_send_req(s, &rq, 2000) < 0) {
+                       fprintf(stderr, "Can't set page parameters name on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint16_t window, interval;
+               read_page_activity_rp rp;
+
+               rq.ogf = OGF_HOST_CTL;
+               rq.ocf = OCF_READ_PAGE_ACTIVITY;
+               rq.rparam = &rp;
+               rq.rlen = READ_PAGE_ACTIVITY_RP_SIZE;
+
+               if (hci_send_req(s, &rq, 1000) < 0) {
+                       fprintf(stderr, "Can't read page parameters on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+               if (rp.status) {
+                       printf("Read page parameters on hci%d returned status %d\n",
+                                                       hdev, rp.status);
+                       exit(1);
+               }
+               print_dev_hdr(&di);
+
+               window   = btohs(rp.window);
+               interval = btohs(rp.interval);
+               printf("\tPage interval: %u slots (%.2f ms), "
+                       "window: %u slots (%.2f ms)\n",
+                       interval, (float)interval * 0.625,
+                       window, (float)window * 0.625);
+       }
+}
+
+static void cmd_page_to(int ctl, int hdev, char *opt)
+{
+       struct hci_request rq;
+       int s;
+
+       if ((s = hci_open_dev(hdev)) < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       memset(&rq, 0, sizeof(rq));
+
+       if (opt) {
+               unsigned int timeout;
+               write_page_timeout_cp cp;
+
+               if (sscanf(opt,"%5u", &timeout) != 1) {
+                       printf("Invalid argument format\n");
+                       exit(1);
+               }
+
+               rq.ogf = OGF_HOST_CTL;
+               rq.ocf = OCF_WRITE_PAGE_TIMEOUT;
+               rq.cparam = &cp;
+               rq.clen = WRITE_PAGE_TIMEOUT_CP_SIZE;
+
+               cp.timeout = htobs((uint16_t) timeout);
+
+               if (timeout < 0x01 || timeout > 0xFFFF)
+                       printf("Warning: page timeout out of range!\n");
+
+               if (hci_send_req(s, &rq, 2000) < 0) {
+                       fprintf(stderr, "Can't set page timeout on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint16_t timeout;
+               read_page_timeout_rp rp;
+
+               rq.ogf = OGF_HOST_CTL;
+               rq.ocf = OCF_READ_PAGE_TIMEOUT;
+               rq.rparam = &rp;
+               rq.rlen = READ_PAGE_TIMEOUT_RP_SIZE;
+
+               if (hci_send_req(s, &rq, 1000) < 0) {
+                       fprintf(stderr, "Can't read page timeout on hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+                       exit(1);
+               }
+               if (rp.status) {
+                       printf("Read page timeout on hci%d returned status %d\n",
+                                                       hdev, rp.status);
+                       exit(1);
+               }
+               print_dev_hdr(&di);
+
+               timeout = btohs(rp.timeout);
+               printf("\tPage timeout: %u slots (%.2f ms)\n",
+                               timeout, (float)timeout * 0.625);
+       }
+}
+
+static void cmd_afh_mode(int ctl, int hdev, char *opt)
+{
+       int dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (opt) {
+               uint8_t mode = atoi(opt);
+
+               if (hci_write_afh_mode(dd, mode, 2000) < 0) {
+                       fprintf(stderr, "Can't set AFH mode on hci%d: %s (%d)\n",
+                                       hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint8_t mode;
+
+               if (hci_read_afh_mode(dd, &mode, 1000) < 0) {
+                       fprintf(stderr, "Can't read AFH mode on hci%d: %s (%d)\n",
+                                       hdev, strerror(errno), errno);
+                       exit(1);
+               }
+
+               print_dev_hdr(&di);
+               printf("\tAFH mode: %s\n", mode == 1 ? "Enabled" : "Disabled");
+       }
+}
+
+static void cmd_ssp_mode(int ctl, int hdev, char *opt)
+{
+       int dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (opt) {
+               uint8_t mode = atoi(opt);
+
+               if (hci_write_simple_pairing_mode(dd, mode, 2000) < 0) {
+                       fprintf(stderr, "Can't set Simple Pairing mode on hci%d: %s (%d)\n",
+                                       hdev, strerror(errno), errno);
+                       exit(1);
+               }
+       } else {
+               uint8_t mode;
+
+               if (hci_read_simple_pairing_mode(dd, &mode, 1000) < 0) {
+                       fprintf(stderr, "Can't read Simple Pairing mode on hci%d: %s (%d)\n",
+                                       hdev, strerror(errno), errno);
+                       exit(1);
+               }
+
+               print_dev_hdr(&di);
+               printf("\tSimple Pairing mode: %s\n",
+                       mode == 1 ? "Enabled" : "Disabled");
+       }
+}
+
+static void print_rev_ericsson(int dd)
+{
+       struct hci_request rq;
+       unsigned char buf[102];
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x000f;
+       rq.cparam = NULL;
+       rq.clen   = 0;
+       rq.rparam = &buf;
+       rq.rlen   = sizeof(buf);
+
+       if (hci_send_req(dd, &rq, 1000) < 0) {
+               printf("\nCan't read revision info: %s (%d)\n",
+                       strerror(errno), errno);
+               return;
+       }
+
+       printf("\t%s\n", buf + 1);
+}
+
+static void print_rev_csr(int dd, uint16_t rev)
+{
+       uint16_t buildid, chipver, chiprev, maxkeylen, mapsco;
+
+       if (csr_read_varid_uint16(dd, 0, CSR_VARID_BUILDID, &buildid) < 0) {
+               printf("\t%s\n", csr_buildidtostr(rev));
+               return;
+       }
+
+       printf("\t%s\n", csr_buildidtostr(buildid));
+
+       if (!csr_read_varid_uint16(dd, 1, CSR_VARID_CHIPVER, &chipver)) {
+               if (csr_read_varid_uint16(dd, 2, CSR_VARID_CHIPREV, &chiprev) < 0)
+                       chiprev = 0;
+               printf("\tChip version: %s\n", csr_chipvertostr(chipver, chiprev));
+       }
+
+       if (!csr_read_varid_uint16(dd, 3, CSR_VARID_MAX_CRYPT_KEY_LENGTH, &maxkeylen))
+               printf("\tMax key size: %d bit\n", maxkeylen * 8);
+
+       if (!csr_read_pskey_uint16(dd, 4, CSR_PSKEY_HOSTIO_MAP_SCO_PCM, 0x0000, &mapsco))
+               printf("\tSCO mapping:  %s\n", mapsco ? "PCM" : "HCI");
+}
+
+static void print_rev_digianswer(int dd)
+{
+       struct hci_request rq;
+       unsigned char req[] = { 0x07 };
+       unsigned char buf[102];
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_VENDOR_CMD;
+       rq.ocf    = 0x000e;
+       rq.cparam = req;
+       rq.clen   = sizeof(req);
+       rq.rparam = &buf;
+       rq.rlen   = sizeof(buf);
+
+       if (hci_send_req(dd, &rq, 1000) < 0) {
+               printf("\nCan't read revision info: %s (%d)\n",
+                       strerror(errno), errno);
+               return;
+       }
+
+       printf("\t%s\n", buf + 1);
+}
+
+static void print_rev_broadcom(uint16_t hci_rev, uint16_t lmp_subver)
+{
+       printf("\tFirmware %d.%d / %d\n",
+               hci_rev & 0xff, lmp_subver >> 8, lmp_subver & 0xff);
+}
+
+static void print_rev_avm(uint16_t hci_rev, uint16_t lmp_subver)
+{
+       if (lmp_subver == 0x01)
+               printf("\tFirmware 03.%d.%d\n", hci_rev >> 8, hci_rev & 0xff);
+       else
+               printf("\tUnknown type\n");
+}
+
+static void cmd_revision(int ctl, int hdev, char *opt)
+{
+       struct hci_version ver;
+       int dd;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               return;
+       }
+
+       if (hci_read_local_version(dd, &ver, 1000) < 0) {
+               fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               return;
+       }
+
+       print_dev_hdr(&di);
+       switch (ver.manufacturer) {
+       case 0:
+       case 37:
+       case 48:
+               print_rev_ericsson(dd);
+               break;
+       case 10:
+               print_rev_csr(dd, ver.hci_rev);
+               break;
+       case 12:
+               print_rev_digianswer(dd);
+               break;
+       case 15:
+               print_rev_broadcom(ver.hci_rev, ver.lmp_subver);
+               break;
+       case 31:
+               print_rev_avm(ver.hci_rev, ver.lmp_subver);
+               break;
+       default:
+               printf("\tUnsupported manufacturer\n");
+               break;
+       }
+       return;
+}
+
+static void cmd_block(int ctl, int hdev, char *opt)
+{
+       bdaddr_t bdaddr;
+       int dd;
+
+       if (!opt)
+               return;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       str2ba(opt, &bdaddr);
+
+       if (ioctl(dd, HCIBLOCKADDR, &bdaddr) < 0) {
+               perror("ioctl(HCIBLOCKADDR)");
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+}
+
+static void cmd_unblock(int ctl, int hdev, char *opt)
+{
+       bdaddr_t bdaddr;
+       int dd;
+
+       if (!opt)
+               return;
+
+       dd = hci_open_dev(hdev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               hdev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (!strcasecmp(opt, "all"))
+               bacpy(&bdaddr, BDADDR_ANY);
+       else
+               str2ba(opt, &bdaddr);
+
+       if (ioctl(dd, HCIUNBLOCKADDR, &bdaddr) < 0) {
+               perror("ioctl(HCIUNBLOCKADDR)");
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+}
+
+static void print_dev_hdr(struct hci_dev_info *di)
+{
+       static int hdr = -1;
+       char addr[18];
+
+       if (hdr == di->dev_id)
+               return;
+       hdr = di->dev_id;
+
+       ba2str(&di->bdaddr, addr);
+
+       printf("%s:\tType: %s  Bus: %s\n", di->name,
+                                       hci_typetostr(di->type >> 4),
+                                       hci_bustostr(di->type & 0x0f));
+       printf("\tBD Address: %s  ACL MTU: %d:%d  SCO MTU: %d:%d\n",
+                                       addr, di->acl_mtu, di->acl_pkts,
+                                               di->sco_mtu, di->sco_pkts);
+}
+
+static void print_dev_info(int ctl, struct hci_dev_info *di)
+{
+       struct hci_dev_stats *st = &di->stat;
+       char *str;
+
+       print_dev_hdr(di);
+
+       str = hci_dflagstostr(di->flags);
+       printf("\t%s\n", str);
+       bt_free(str);
+
+       printf("\tRX bytes:%d acl:%d sco:%d events:%d errors:%d\n",
+               st->byte_rx, st->acl_rx, st->sco_rx, st->evt_rx, st->err_rx);
+
+       printf("\tTX bytes:%d acl:%d sco:%d commands:%d errors:%d\n",
+               st->byte_tx, st->acl_tx, st->sco_tx, st->cmd_tx, st->err_tx);
+
+       if (all && !hci_test_bit(HCI_RAW, &di->flags) &&
+                       bacmp(&di->bdaddr, BDADDR_ANY)) {
+               print_dev_features(di, 0);
+               print_pkt_type(di);
+               print_link_policy(di);
+               print_link_mode(di);
+
+               if (hci_test_bit(HCI_UP, &di->flags)) {
+                       cmd_name(ctl, di->dev_id, NULL);
+                       cmd_class(ctl, di->dev_id, NULL);
+                       cmd_version(ctl, di->dev_id, NULL);
+               }
+       }
+
+       printf("\n");
+}
+
+static struct {
+       char *cmd;
+       void (*func)(int ctl, int hdev, char *opt);
+       char *opt;
+       char *doc;
+} command[] = {
+       { "up",         cmd_up,         0,              "Open and initialize HCI device" },
+       { "down",       cmd_down,       0,              "Close HCI device" },
+       { "reset",      cmd_reset,      0,              "Reset HCI device" },
+       { "rstat",      cmd_rstat,      0,              "Reset statistic counters" },
+       { "auth",       cmd_auth,       0,              "Enable Authentication" },
+       { "noauth",     cmd_auth,       0,              "Disable Authentication" },
+       { "encrypt",    cmd_encrypt,    0,              "Enable Encryption" },
+       { "noencrypt",  cmd_encrypt,    0,              "Disable Encryption" },
+       { "piscan",     cmd_scan,       0,              "Enable Page and Inquiry scan" },
+       { "noscan",     cmd_scan,       0,              "Disable scan" },
+       { "iscan",      cmd_scan,       0,              "Enable Inquiry scan" },
+       { "pscan",      cmd_scan,       0,              "Enable Page scan" },
+       { "ptype",      cmd_ptype,      "[type]",       "Get/Set default packet type" },
+       { "lm",         cmd_lm,         "[mode]",       "Get/Set default link mode"   },
+       { "lp",         cmd_lp,         "[policy]",     "Get/Set default link policy" },
+       { "name",       cmd_name,       "[name]",       "Get/Set local name" },
+       { "class",      cmd_class,      "[class]",      "Get/Set class of device" },
+       { "voice",      cmd_voice,      "[voice]",      "Get/Set voice setting" },
+       { "iac",        cmd_iac,        "[iac]",        "Get/Set inquiry access code" },
+       { "inqtpl",     cmd_inq_tpl,    "[level]",      "Get/Set inquiry transmit power level" },
+       { "inqmode",    cmd_inq_mode,   "[mode]",       "Get/Set inquiry mode" },
+       { "inqdata",    cmd_inq_data,   "[data]",       "Get/Set inquiry data" },
+       { "inqtype",    cmd_inq_type,   "[type]",       "Get/Set inquiry scan type" },
+       { "inqparms",   cmd_inq_parms,  "[win:int]",    "Get/Set inquiry scan window and interval" },
+       { "pageparms",  cmd_page_parms, "[win:int]",    "Get/Set page scan window and interval" },
+       { "pageto",     cmd_page_to,    "[to]",         "Get/Set page timeout" },
+       { "afhmode",    cmd_afh_mode,   "[mode]",       "Get/Set AFH mode" },
+       { "sspmode",    cmd_ssp_mode,   "[mode]",       "Get/Set Simple Pairing Mode" },
+       { "aclmtu",     cmd_aclmtu,     "<mtu:pkt>",    "Set ACL MTU and number of packets" },
+       { "scomtu",     cmd_scomtu,     "<mtu:pkt>",    "Set SCO MTU and number of packets" },
+       { "putkey",     cmd_putkey,     "<bdaddr>",     "Store link key on the device" },
+       { "delkey",     cmd_delkey,     "<bdaddr>",     "Delete link key from the device" },
+       { "oobdata",    cmd_oob_data,   0,              "Display local OOB data" },
+       { "commands",   cmd_commands,   0,              "Display supported commands" },
+       { "features",   cmd_features,   0,              "Display device features" },
+       { "version",    cmd_version,    0,              "Display version information" },
+       { "revision",   cmd_revision,   0,              "Display revision information" },
+       { "block",      cmd_block,      "<bdaddr>",     "Add a device to the blacklist" },
+       { "unblock",    cmd_unblock,    "<bdaddr>",     "Remove a device from the blacklist" },
+       { "lerandaddr", cmd_le_addr,    "<bdaddr>",     "Set LE Random Address" },
+       { "leadv",      cmd_le_adv,     0,              "Enable LE advertising" },
+       { "noleadv",    cmd_le_adv,     0,              "Disable LE advertising" },
+       { "lestates",   cmd_le_states,  0,              "Display the supported LE states" },
+       { NULL, NULL, 0 }
+};
+
+static void usage(void)
+{
+       int i;
+
+       printf("hciconfig - HCI device configuration utility\n");
+       printf("Usage:\n"
+               "\thciconfig\n"
+               "\thciconfig [-a] hciX [command ...]\n");
+       printf("Commands:\n");
+       for (i = 0; command[i].cmd; i++)
+               printf("\t%-10s %-8s\t%s\n", command[i].cmd,
+               command[i].opt ? command[i].opt : " ",
+               command[i].doc);
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "all",        0, 0, 'a' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       int opt, ctl, i, cmd = 0;
+
+       while ((opt = getopt_long(argc, argv, "ah", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'a':
+                       all = 1;
+                       break;
+
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       /* Open HCI socket  */
+       if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {
+               perror("Can't open HCI socket.");
+               exit(1);
+       }
+
+       if (argc < 1) {
+               print_dev_list(ctl, 0);
+               exit(0);
+       }
+
+       di.dev_id = atoi(argv[0] + 3);
+       argc--; argv++;
+
+       if (ioctl(ctl, HCIGETDEVINFO, (void *) &di)) {
+               perror("Can't get device info");
+               exit(1);
+       }
+
+       if (hci_test_bit(HCI_RAW, &di.flags) &&
+                       !bacmp(&di.bdaddr, BDADDR_ANY)) {
+               int dd = hci_open_dev(di.dev_id);
+               hci_read_bd_addr(dd, &di.bdaddr, 1000);
+               hci_close_dev(dd);
+       }
+
+       while (argc > 0) {
+               for (i = 0; command[i].cmd; i++) {
+                       if (strncmp(command[i].cmd,
+                                       *argv, strlen(command[i].cmd)))
+                               continue;
+
+                       if (command[i].opt) {
+                               argc--; argv++;
+                       }
+
+                       command[i].func(ctl, di.dev_id, *argv);
+                       cmd = 1;
+                       break;
+               }
+
+               if (command[i].cmd == 0)
+                       fprintf(stderr, "Warning: unknown command - \"%s\"\n",
+                                       *argv);
+
+               argc--; argv++;
+       }
+
+       if (!cmd)
+               print_dev_info(ctl, &di);
+
+       close(ctl);
+       return 0;
+}
diff --git a/tools/hcieventmask.c b/tools/hcieventmask.c
new file mode 100644 (file)
index 0000000..87beac9
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+static struct option main_options[] = {
+       { "device",     1, 0, 'i' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       uint8_t events[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00 };
+       struct hci_dev_info di;
+       struct hci_version ver;
+       int dd, opt, dev = 0;
+
+       while ((opt=getopt_long(argc, argv, "+i:", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'i':
+                       dev = hci_devid(optarg);
+                       if (dev < 0) {
+                               perror("Invalid device");
+                               exit(1);
+                       }
+                       break;
+               }
+       }
+
+       dd = hci_open_dev(dev);
+       if (dd < 0) {
+               fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               exit(1);
+       }
+
+       if (hci_devinfo(dev, &di) < 0) {
+               fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               exit(1);
+       }
+
+       if (hci_read_local_version(dd, &ver, 1000) < 0) {
+               fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n",
+                                               dev, strerror(errno), errno);
+               hci_close_dev(dd);
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+
+       if (ver.hci_ver > 1) {
+               if (di.features[5] & LMP_SNIFF_SUBR)
+                       events[5] |= 0x20;
+
+               if (di.features[5] & LMP_PAUSE_ENC)
+                       events[5] |= 0x80;
+
+               if (di.features[6] & LMP_EXT_INQ)
+                       events[5] |= 0x40;
+
+               if (di.features[6] & LMP_NFLUSH_PKTS)
+                       events[7] |= 0x01;
+
+               if (di.features[7] & LMP_LSTO)
+                       events[6] |= 0x80;
+
+               if (di.features[6] & LMP_SIMPLE_PAIR) {
+                       events[6] |= 0x01;      /* IO Capability Request */
+                       events[6] |= 0x02;      /* IO Capability Response */
+                       events[6] |= 0x04;      /* User Confirmation Request */
+                       events[6] |= 0x08;      /* User Passkey Request */
+                       events[6] |= 0x10;      /* Remote OOB Data Request */
+                       events[6] |= 0x20;      /* Simple Pairing Complete */
+                       events[7] |= 0x04;      /* User Passkey Notification */
+                       events[7] |= 0x08;      /* Keypress Notification */
+                       events[7] |= 0x10;      /* Remote Host Supported
+                                                * Features Notification */
+               }
+
+               if (di.features[4] & LMP_LE)
+                       events[7] |= 0x20;
+
+               if (di.features[6] & LMP_LE_BREDR)
+                       events[7] |= 0x20;
+       }
+
+       printf("Setting event mask:\n");
+       printf("\thcitool cmd 0x%02x 0x%04x  "
+                                       "0x%02x 0x%02x 0x%02x 0x%02x "
+                                       "0x%02x 0x%02x 0x%02x 0x%02x\n",
+                               OGF_HOST_CTL, OCF_SET_EVENT_MASK,
+                               events[0], events[1], events[2], events[3],
+                               events[4], events[5], events[6], events[7]);
+
+       return 0;
+}
diff --git a/tools/hcisecfilter.c b/tools/hcisecfilter.c
new file mode 100644 (file)
index 0000000..9ad4ce0
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+int main(void)
+{
+       uint32_t type_mask;
+       uint32_t event_mask[2];
+       uint32_t ocf_mask[4];
+
+       /* Packet types */
+       memset(&type_mask, 0, sizeof(type_mask));
+       hci_set_bit(HCI_EVENT_PKT, &type_mask);
+
+       printf("Type mask:        { 0x%02x }\n", type_mask);
+
+       /* Events */
+       memset(event_mask, 0, sizeof(event_mask));
+       hci_set_bit(EVT_INQUIRY_COMPLETE,                       event_mask);
+       hci_set_bit(EVT_INQUIRY_RESULT,                         event_mask);
+       hci_set_bit(EVT_CONN_COMPLETE,                          event_mask);
+       hci_set_bit(EVT_CONN_REQUEST,                           event_mask);
+       hci_set_bit(EVT_DISCONN_COMPLETE,                       event_mask);
+       hci_set_bit(EVT_AUTH_COMPLETE,                          event_mask);
+       hci_set_bit(EVT_REMOTE_NAME_REQ_COMPLETE,               event_mask);
+       hci_set_bit(EVT_ENCRYPT_CHANGE,                         event_mask);
+       hci_set_bit(EVT_READ_REMOTE_FEATURES_COMPLETE,          event_mask);
+       hci_set_bit(EVT_READ_REMOTE_VERSION_COMPLETE,           event_mask);
+       hci_set_bit(EVT_CMD_COMPLETE,                           event_mask);
+       hci_set_bit(EVT_CMD_STATUS,                             event_mask);
+       hci_set_bit(EVT_READ_CLOCK_OFFSET_COMPLETE,             event_mask);
+       hci_set_bit(EVT_INQUIRY_RESULT_WITH_RSSI,               event_mask);
+       hci_set_bit(EVT_READ_REMOTE_EXT_FEATURES_COMPLETE,      event_mask);
+       hci_set_bit(EVT_SYNC_CONN_COMPLETE,                     event_mask);
+       hci_set_bit(EVT_SYNC_CONN_CHANGED,                      event_mask);
+       hci_set_bit(EVT_EXTENDED_INQUIRY_RESULT,                event_mask);
+
+       printf("Event mask:       { 0x%08x, 0x%08x }\n",
+                                       event_mask[0], event_mask[1]);
+
+       /* OGF_LINK_CTL */
+       memset(ocf_mask, 0, sizeof(ocf_mask));
+       hci_set_bit(OCF_INQUIRY,                        ocf_mask);
+       hci_set_bit(OCF_INQUIRY_CANCEL,                 ocf_mask);
+       hci_set_bit(OCF_REMOTE_NAME_REQ,                ocf_mask);
+       hci_set_bit(OCF_REMOTE_NAME_REQ_CANCEL,         ocf_mask);
+       hci_set_bit(OCF_READ_REMOTE_FEATURES,           ocf_mask);
+       hci_set_bit(OCF_READ_REMOTE_EXT_FEATURES,       ocf_mask);
+       hci_set_bit(OCF_READ_REMOTE_VERSION,            ocf_mask);
+       hci_set_bit(OCF_READ_CLOCK_OFFSET,              ocf_mask);
+       hci_set_bit(OCF_READ_LMP_HANDLE,                ocf_mask);
+
+       printf("OGF_LINK_CTL:     { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n",
+                       ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]);
+
+       /* OGF_LINK_POLICY */
+       memset(ocf_mask, 0, sizeof(ocf_mask));
+       hci_set_bit(OCF_ROLE_DISCOVERY,                 ocf_mask);
+       hci_set_bit(OCF_READ_LINK_POLICY,               ocf_mask);
+       hci_set_bit(OCF_READ_DEFAULT_LINK_POLICY,       ocf_mask);
+
+       printf("OGF_LINK_POLICY:  { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n",
+                       ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]);
+
+       /* OGF_HOST_CTL */
+       memset(ocf_mask, 0, sizeof(ocf_mask));
+       hci_set_bit(OCF_READ_PIN_TYPE,                  ocf_mask);
+       hci_set_bit(OCF_READ_LOCAL_NAME,                ocf_mask);
+       hci_set_bit(OCF_READ_CONN_ACCEPT_TIMEOUT,       ocf_mask);
+       hci_set_bit(OCF_READ_PAGE_TIMEOUT,              ocf_mask);
+       hci_set_bit(OCF_READ_SCAN_ENABLE,               ocf_mask);
+       hci_set_bit(OCF_READ_PAGE_ACTIVITY,             ocf_mask);
+       hci_set_bit(OCF_READ_INQ_ACTIVITY,              ocf_mask);
+       hci_set_bit(OCF_READ_AUTH_ENABLE,               ocf_mask);
+       hci_set_bit(OCF_READ_ENCRYPT_MODE,              ocf_mask);
+       hci_set_bit(OCF_READ_CLASS_OF_DEV,              ocf_mask);
+       hci_set_bit(OCF_READ_VOICE_SETTING,             ocf_mask);
+       hci_set_bit(OCF_READ_AUTOMATIC_FLUSH_TIMEOUT,   ocf_mask);
+       hci_set_bit(OCF_READ_NUM_BROADCAST_RETRANS,     ocf_mask);
+       hci_set_bit(OCF_READ_HOLD_MODE_ACTIVITY,        ocf_mask);
+       hci_set_bit(OCF_READ_TRANSMIT_POWER_LEVEL,      ocf_mask);
+       hci_set_bit(OCF_READ_LINK_SUPERVISION_TIMEOUT,  ocf_mask);
+       hci_set_bit(OCF_READ_NUM_SUPPORTED_IAC,         ocf_mask);
+       hci_set_bit(OCF_READ_CURRENT_IAC_LAP,           ocf_mask);
+       hci_set_bit(OCF_READ_PAGE_SCAN_PERIOD_MODE,     ocf_mask);
+       hci_set_bit(OCF_READ_PAGE_SCAN_MODE,            ocf_mask);
+       hci_set_bit(OCF_READ_INQUIRY_SCAN_TYPE,         ocf_mask);
+       hci_set_bit(OCF_READ_INQUIRY_MODE,              ocf_mask);
+       hci_set_bit(OCF_READ_PAGE_SCAN_TYPE,            ocf_mask);
+       hci_set_bit(OCF_READ_AFH_MODE,                  ocf_mask);
+       hci_set_bit(OCF_READ_EXT_INQUIRY_RESPONSE,      ocf_mask);
+       hci_set_bit(OCF_READ_SIMPLE_PAIRING_MODE,       ocf_mask);
+       hci_set_bit(OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL,       ocf_mask);
+       hci_set_bit(OCF_READ_DEFAULT_ERROR_DATA_REPORTING,      ocf_mask);
+
+       printf("OGF_HOST_CTL:     { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n",
+                       ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]);
+
+       /* OGF_INFO_PARAM */
+       memset(ocf_mask, 0, sizeof(ocf_mask));
+       hci_set_bit(OCF_READ_LOCAL_VERSION,             ocf_mask);
+       hci_set_bit(OCF_READ_LOCAL_COMMANDS,            ocf_mask);
+       hci_set_bit(OCF_READ_LOCAL_FEATURES,            ocf_mask);
+       hci_set_bit(OCF_READ_LOCAL_EXT_FEATURES,        ocf_mask);
+       hci_set_bit(OCF_READ_BUFFER_SIZE,               ocf_mask);
+       hci_set_bit(OCF_READ_COUNTRY_CODE,              ocf_mask);
+       hci_set_bit(OCF_READ_BD_ADDR,                   ocf_mask);
+
+       printf("OGF_INFO_PARAM:   { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n",
+                       ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]);
+
+       /* OGF_STATUS_PARAM */
+       memset(ocf_mask, 0, sizeof(ocf_mask));
+       hci_set_bit(OCF_READ_FAILED_CONTACT_COUNTER,    ocf_mask);
+       hci_set_bit(OCF_READ_LINK_QUALITY,              ocf_mask);
+       hci_set_bit(OCF_READ_RSSI,                      ocf_mask);
+       hci_set_bit(OCF_READ_AFH_MAP,                   ocf_mask);
+       hci_set_bit(OCF_READ_CLOCK,                     ocf_mask);
+
+       printf("OGF_STATUS_PARAM: { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n",
+                       ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]);
+
+       return 0;
+}
diff --git a/tools/hcitool.1 b/tools/hcitool.1
new file mode 100644 (file)
index 0000000..85498dc
--- /dev/null
@@ -0,0 +1,209 @@
+.TH HCITOOL 1 "Nov 12 2002" BlueZ "Linux System Administration"
+.SH NAME
+hcitool \- configure Bluetooth connections
+.SH SYNOPSIS
+.B hcitool [-h]
+.br
+.B hcitool [-i <hciX>] [command [command parameters]]
+
+.SH DESCRIPTION
+.LP
+.B
+hcitool
+is used to configure Bluetooth connections and send some special command to
+Bluetooth devices. If no
+.B
+command
+is given, or if the option
+.B
+-h
+is used,
+.B
+hcitool
+prints some usage information and exits.
+.SH OPTIONS
+.TP
+.BI -h
+Gives a list of possible commands
+.TP
+.BI -i " <hciX>"
+The command is applied to device
+.I
+hciX
+, which must be the name of an installed Bluetooth device. If not specified,
+the command will be sent to the first available Bluetooth device.
+.SH COMMANDS
+.TP
+.BI dev
+Display local devices
+.TP
+.BI inq
+Inquire remote devices. For each discovered device, Bluetooth device address,
+clock offset and class are printed.
+.TP
+.BI scan
+Inquire remote devices. For each discovered device, device name are printed.
+.TP
+.BI name " <bdaddr>"
+Print device name of remote device with Bluetooth address
+.IR bdaddr .
+.TP
+.BI info " <bdaddr>"
+Print device name, version and supported features of remote device with
+Bluetooth address
+.IR bdaddr .
+.TP
+.BI spinq
+Start periodic inquiry process. No inquiry results are printed.
+.TP
+.BI epinq
+Exit periodic inquiry process.
+.TP
+.BI cmd " <ogf> <ocf> [parameters]"
+Submit an arbitrary HCI command to local device.
+.IR ogf ,
+.IR ocf
+and
+.IR parameters
+are hexadecimal bytes.
+.TP
+.BI con
+Display active baseband connections
+.TP
+.BI cc " [--role=m|s] [--pkt-type=<ptype>] <bdaddr>"
+Create baseband connection to remote device with Bluetooth address
+.IR bdaddr .
+Option
+.I
+--pkt-type
+specifies a list of allowed packet types.
+.I
+<ptype>
+is a comma-separated list of packet types, where the possible packet types are
+.BR DM1 ,
+.BR DM3 ,
+.BR DM5 ,
+.BR DH1 ,
+.BR DH3 ,
+.BR DH5 ,
+.BR HV1 ,
+.BR HV2 ,
+.BR HV3 .
+Default is to allow all packet types. Option
+.I
+--role
+can have value
+.I
+m
+(do not allow role switch, stay master) or
+.I
+s
+(allow role switch, become slave if the peer asks to become master). Default is
+.IR m .
+.TP
+.BI dc " <bdaddr> [reason]"
+Delete baseband connection from remote device with Bluetooth address
+.IR bdaddr .
+The reason can be one of the Bluetooth HCI error codes. Default is
+.IR 19
+for user ended connections. The value must be given in decimal.
+.TP
+.BI sr " <bdaddr> <role>"
+Switch role for the baseband connection from the remote device to
+.BR master
+or
+.BR slave .
+.TP
+.BI cpt " <bdaddr> <packet types>"
+Change packet types for baseband connection to device with Bluetooth address
+.IR bdaddr .
+.I
+packet types
+is a comma-separated list of packet types, where the possible packet types are
+.BR DM1 ,
+.BR DM3 ,
+.BR DM5 ,
+.BR DH1 ,
+.BR DH3 ,
+.BR DH5 ,
+.BR HV1 ,
+.BR HV2 ,
+.BR HV3 .
+.TP
+.BI rssi " <bdaddr>"
+Display received signal strength information for the connection to the device
+with Bluetooth address
+.IR bdaddr .
+.TP
+.BI lq " <bdaddr>"
+Display link quality for the connection to the device with Bluetooth address
+.IR bdaddr .
+.TP
+.BI tpl " <bdaddr> [type]"
+Display transmit power level for the connection to the device with Bluetooth address
+.IR bdaddr .
+The type can be
+.BR 0
+for the current transmit power level (which is default) or
+.BR 1
+for the maximum transmit power level.
+.TP
+.BI afh " <bdaddr>"
+Display AFH channel map for the connection to the device with Bluetooth address
+.IR bdaddr .
+.TP
+.BI lp " <bdaddr> [value]"
+With no
+.IR value ,
+displays link policy settings for the connection to the device with Bluetooth address
+.IR bdaddr .
+If
+.IR value
+is given, sets the link policy settings for that connection to
+.IR value .
+Possible values are RSWITCH, HOLD, SNIFF and PARK.
+.TP
+.BI lst " <bdaddr> [value]"
+With no
+.IR value ,
+displays link supervision timeout for the connection to the device with Bluetooth address
+.IR bdaddr .
+If
+.I
+value
+is given, sets the link supervision timeout for that connection to
+.I
+value
+slots, or to infinite if
+.I
+value
+is 0.
+.TP
+.BI auth " <bdaddr>"
+Request authentication for the device with Bluetooth address
+.IR bdaddr .
+.TP
+.BI enc " <bdaddr> [encrypt enable]"
+Enable or disable the encryption for the device with Bluetooth address
+.IR bdaddr .
+.TP
+.BI key " <bdaddr>"
+Change the connection link key for the device with Bluetooth address
+.IR bdaddr .
+.TP
+.BI clkoff " <bdaddr>"
+Read the clock offset for the device with Bluetooth address
+.IR bdaddr .
+.TP
+.BI clock " [bdaddr] [which clock]"
+Read the clock for the device with Bluetooth address
+.IR bdaddr .
+The clock can be
+.BR 0
+for the local clock or
+.BR 1
+for the piconet clock (which is default).
+.SH AUTHORS
+Written by Maxim Krasnyansky <maxk@qualcomm.com> and Marcel Holtmann <marcel@holtmann.org>
+.PP
+man page by Fabrizio Gennari <fabrizio.gennari@philips.com>
diff --git a/tools/hcitool.c b/tools/hcitool.c
new file mode 100644 (file)
index 0000000..a117449
--- /dev/null
@@ -0,0 +1,3007 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "textfile.h"
+#include "oui.h"
+
+/* Unofficial value, might still change */
+#define LE_LINK                0x03
+
+#define FLAGS_AD_TYPE 0x01
+#define FLAGS_LIMITED_MODE_BIT 0x01
+#define FLAGS_GENERAL_MODE_BIT 0x02
+
+#define for_each_opt(opt, long, short) while ((opt=getopt_long(argc, argv, short ? short:"+", long, NULL)) != -1)
+
+static void usage(void);
+
+static int dev_info(int s, int dev_id, long arg)
+{
+       struct hci_dev_info di = { dev_id: dev_id };
+       char addr[18];
+
+       if (ioctl(s, HCIGETDEVINFO, (void *) &di))
+               return 0;
+
+       ba2str(&di.bdaddr, addr);
+       printf("\t%s\t%s\n", di.name, addr);
+       return 0;
+}
+
+static void helper_arg(int min_num_arg, int max_num_arg, int *argc,
+                       char ***argv, const char *usage)
+{
+       *argc -= optind;
+       /* too many arguments, but when "max_num_arg < min_num_arg" then no
+                limiting (prefer "max_num_arg=-1" to gen infinity)
+       */
+       if ( (*argc > max_num_arg) && (max_num_arg >= min_num_arg ) ) {
+               fprintf(stderr, "%s: too many arguments (maximal: %i)\n",
+                               *argv[0], max_num_arg);
+               printf("%s", usage);
+               exit(1);
+       }
+
+       /* print usage */
+       if (*argc < min_num_arg) {
+               fprintf(stderr, "%s: too few arguments (minimal: %i)\n",
+                               *argv[0], min_num_arg);
+               printf("%s", usage);
+               exit(0);
+       }
+
+       *argv += optind;
+}
+
+static char *type2str(uint8_t type)
+{
+       switch (type) {
+       case SCO_LINK:
+               return "SCO";
+       case ACL_LINK:
+               return "ACL";
+       case ESCO_LINK:
+               return "eSCO";
+       case LE_LINK:
+               return "LE";
+       default:
+               return "Unknown";
+       }
+}
+
+static int conn_list(int s, int dev_id, long arg)
+{
+       struct hci_conn_list_req *cl;
+       struct hci_conn_info *ci;
+       int id = arg;
+       int i;
+
+       if (id != -1 && dev_id != id)
+               return 0;
+
+       if (!(cl = malloc(10 * sizeof(*ci) + sizeof(*cl)))) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+       cl->dev_id = dev_id;
+       cl->conn_num = 10;
+       ci = cl->conn_info;
+
+       if (ioctl(s, HCIGETCONNLIST, (void *) cl)) {
+               perror("Can't get connection list");
+               exit(1);
+       }
+
+       for (i = 0; i < cl->conn_num; i++, ci++) {
+               char addr[18];
+               char *str;
+               ba2str(&ci->bdaddr, addr);
+               str = hci_lmtostr(ci->link_mode);
+               printf("\t%s %s %s handle %d state %d lm %s\n",
+                       ci->out ? "<" : ">", type2str(ci->type),
+                       addr, ci->handle, ci->state, str);
+               bt_free(str);
+       }
+
+       free(cl);
+       return 0;
+}
+
+static int find_conn(int s, int dev_id, long arg)
+{
+       struct hci_conn_list_req *cl;
+       struct hci_conn_info *ci;
+       int i;
+
+       if (!(cl = malloc(10 * sizeof(*ci) + sizeof(*cl)))) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+       cl->dev_id = dev_id;
+       cl->conn_num = 10;
+       ci = cl->conn_info;
+
+       if (ioctl(s, HCIGETCONNLIST, (void *) cl)) {
+               perror("Can't get connection list");
+               exit(1);
+       }
+
+       for (i = 0; i < cl->conn_num; i++, ci++)
+               if (!bacmp((bdaddr_t *) arg, &ci->bdaddr)) {
+                       free(cl);
+                       return 1;
+               }
+
+       free(cl);
+       return 0;
+}
+
+static void hex_dump(char *pref, int width, unsigned char *buf, int len)
+{
+       register int i,n;
+
+       for (i = 0, n = 1; i < len; i++, n++) {
+               if (n == 1)
+                       printf("%s", pref);
+               printf("%2.2X ", buf[i]);
+               if (n == width) {
+                       printf("\n");
+                       n = 0;
+               }
+       }
+       if (i && n!=1)
+               printf("\n");
+}
+
+static char *get_minor_device_name(int major, int minor)
+{
+       switch (major) {
+       case 0: /* misc */
+               return "";
+       case 1: /* computer */
+               switch (minor) {
+               case 0:
+                       return "Uncategorized";
+               case 1:
+                       return "Desktop workstation";
+               case 2:
+                       return "Server";
+               case 3:
+                       return "Laptop";
+               case 4:
+                       return "Handheld";
+               case 5:
+                       return "Palm";
+               case 6:
+                       return "Wearable";
+               }
+               break;
+       case 2: /* phone */
+               switch (minor) {
+               case 0:
+                       return "Uncategorized";
+               case 1:
+                       return "Cellular";
+               case 2:
+                       return "Cordless";
+               case 3:
+                       return "Smart phone";
+               case 4:
+                       return "Wired modem or voice gateway";
+               case 5:
+                       return "Common ISDN Access";
+               case 6:
+                       return "Sim Card Reader";
+               }
+               break;
+       case 3: /* lan access */
+               if (minor == 0)
+                       return "Uncategorized";
+               switch (minor / 8) {
+               case 0:
+                       return "Fully available";
+               case 1:
+                       return "1-17% utilized";
+               case 2:
+                       return "17-33% utilized";
+               case 3:
+                       return "33-50% utilized";
+               case 4:
+                       return "50-67% utilized";
+               case 5:
+                       return "67-83% utilized";
+               case 6:
+                       return "83-99% utilized";
+               case 7:
+                       return "No service available";
+               }
+               break;
+       case 4: /* audio/video */
+               switch (minor) {
+               case 0:
+                       return "Uncategorized";
+               case 1:
+                       return "Device conforms to the Headset profile";
+               case 2:
+                       return "Hands-free";
+                       /* 3 is reserved */
+               case 4:
+                       return "Microphone";
+               case 5:
+                       return "Loudspeaker";
+               case 6:
+                       return "Headphones";
+               case 7:
+                       return "Portable Audio";
+               case 8:
+                       return "Car Audio";
+               case 9:
+                       return "Set-top box";
+               case 10:
+                       return "HiFi Audio Device";
+               case 11:
+                       return "VCR";
+               case 12:
+                       return "Video Camera";
+               case 13:
+                       return "Camcorder";
+               case 14:
+                       return "Video Monitor";
+               case 15:
+                       return "Video Display and Loudspeaker";
+               case 16:
+                       return "Video Conferencing";
+                       /* 17 is reserved */
+               case 18:
+                       return "Gaming/Toy";
+               }
+               break;
+       case 5: /* peripheral */ {
+               static char cls_str[48]; cls_str[0] = 0;
+
+               switch (minor & 48) {
+               case 16:
+                       strncpy(cls_str, "Keyboard", sizeof(cls_str));
+                       break;
+               case 32:
+                       strncpy(cls_str, "Pointing device", sizeof(cls_str));
+                       break;
+               case 48:
+                       strncpy(cls_str, "Combo keyboard/pointing device", sizeof(cls_str));
+                       break;
+               }
+               if ((minor & 15) && (strlen(cls_str) > 0))
+                       strcat(cls_str, "/");
+
+               switch (minor & 15) {
+               case 0:
+                       break;
+               case 1:
+                       strncat(cls_str, "Joystick", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 2:
+                       strncat(cls_str, "Gamepad", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 3:
+                       strncat(cls_str, "Remote control", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 4:
+                       strncat(cls_str, "Sensing device", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               case 5:
+                       strncat(cls_str, "Digitizer tablet", sizeof(cls_str) - strlen(cls_str));
+               break;
+               case 6:
+                       strncat(cls_str, "Card reader", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               default:
+                       strncat(cls_str, "(reserved)", sizeof(cls_str) - strlen(cls_str));
+                       break;
+               }
+               if (strlen(cls_str) > 0)
+                       return cls_str;
+       }
+       case 6: /* imaging */
+               if (minor & 4)
+                       return "Display";
+               if (minor & 8)
+                       return "Camera";
+               if (minor & 16)
+                       return "Scanner";
+               if (minor & 32)
+                       return "Printer";
+               break;
+       case 7: /* wearable */
+               switch (minor) {
+               case 1:
+                       return "Wrist Watch";
+               case 2:
+                       return "Pager";
+               case 3:
+                       return "Jacket";
+               case 4:
+                       return "Helmet";
+               case 5:
+                       return "Glasses";
+               }
+               break;
+       case 8: /* toy */
+               switch (minor) {
+               case 1:
+                       return "Robot";
+               case 2:
+                       return "Vehicle";
+               case 3:
+                       return "Doll / Action Figure";
+               case 4:
+                       return "Controller";
+               case 5:
+                       return "Game";
+               }
+               break;
+       case 63:        /* uncategorised */
+               return "";
+       }
+       return "Unknown (reserved) minor device class";
+}
+
+static char *major_classes[] = {
+       "Miscellaneous", "Computer", "Phone", "LAN Access",
+       "Audio/Video", "Peripheral", "Imaging", "Uncategorized"
+};
+
+static char *get_device_name(const bdaddr_t *local, const bdaddr_t *peer)
+{
+       char filename[PATH_MAX + 1], addr[18];
+
+       ba2str(local, addr);
+       create_name(filename, PATH_MAX, STORAGEDIR, addr, "names");
+
+       ba2str(peer, addr);
+       return textfile_get(filename, addr);
+}
+
+/* Display local devices */
+
+static struct option dev_options[] = {
+       { "help",       0, 0, 'h' },
+       {0, 0, 0, 0 }
+};
+
+static const char *dev_help =
+       "Usage:\n"
+       "\tdev\n";
+
+static void cmd_dev(int dev_id, int argc, char **argv)
+{
+       int opt;
+
+       for_each_opt(opt, dev_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", dev_help);
+                       return;
+               }
+       }
+       helper_arg(0, 0, &argc, &argv, dev_help);
+
+       printf("Devices:\n");
+
+       hci_for_each_dev(HCI_UP, dev_info, 0);
+}
+
+/* Inquiry */
+
+static struct option inq_options[] = {
+       { "help",       0, 0, 'h' },
+       { "length",     1, 0, 'l' },
+       { "numrsp",     1, 0, 'n' },
+       { "iac",        1, 0, 'i' },
+       { "flush",      0, 0, 'f' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *inq_help =
+       "Usage:\n"
+       "\tinq [--length=N] maximum inquiry duration in 1.28 s units\n"
+       "\t    [--numrsp=N] specify maximum number of inquiry responses\n"
+       "\t    [--iac=lap]  specify the inquiry access code\n"
+       "\t    [--flush]    flush the inquiry cache\n";
+
+static void cmd_inq(int dev_id, int argc, char **argv)
+{
+       inquiry_info *info = NULL;
+       uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
+       int num_rsp, length, flags;
+       char addr[18];
+       int i, l, opt;
+
+       length  = 8;    /* ~10 seconds */
+       num_rsp = 0;
+       flags   = 0;
+
+       for_each_opt(opt, inq_options, NULL) {
+               switch (opt) {
+               case 'l':
+                       length = atoi(optarg);
+                       break;
+
+               case 'n':
+                       num_rsp = atoi(optarg);
+                       break;
+
+               case 'i':
+                       l = strtoul(optarg, 0, 16);
+                       if (!strcasecmp(optarg, "giac")) {
+                               l = 0x9e8b33;
+                       } else if (!strcasecmp(optarg, "liac")) {
+                               l = 0x9e8b00;
+                       } if (l < 0x9e8b00 || l > 0x9e8b3f) {
+                               printf("Invalid access code 0x%x\n", l);
+                               exit(1);
+                       }
+                       lap[0] = (l & 0xff);
+                       lap[1] = (l >> 8) & 0xff;
+                       lap[2] = (l >> 16) & 0xff;
+                       break;
+
+               case 'f':
+                       flags |= IREQ_CACHE_FLUSH;
+                       break;
+
+               default:
+                       printf("%s", inq_help);
+                       return;
+               }
+       }
+       helper_arg(0, 0, &argc, &argv, inq_help);
+
+       printf("Inquiring ...\n");
+
+       num_rsp = hci_inquiry(dev_id, length, num_rsp, lap, &info, flags);
+       if (num_rsp < 0) {
+               perror("Inquiry failed.");
+               exit(1);
+       }
+
+       for (i = 0; i < num_rsp; i++) {
+               ba2str(&(info+i)->bdaddr, addr);
+               printf("\t%s\tclock offset: 0x%4.4x\tclass: 0x%2.2x%2.2x%2.2x\n",
+                       addr, btohs((info+i)->clock_offset),
+                       (info+i)->dev_class[2],
+                       (info+i)->dev_class[1],
+                       (info+i)->dev_class[0]);
+       }
+
+       bt_free(info);
+}
+
+/* Device scanning */
+
+static struct option scan_options[] = {
+       { "help",       0, 0, 'h' },
+       { "length",     1, 0, 'l' },
+       { "numrsp",     1, 0, 'n' },
+       { "iac",        1, 0, 'i' },
+       { "flush",      0, 0, 'f' },
+       { "refresh",    0, 0, 'r' },
+       { "class",      0, 0, 'C' },
+       { "info",       0, 0, 'I' },
+       { "oui",        0, 0, 'O' },
+       { "all",        0, 0, 'A' },
+       { "ext",        0, 0, 'A' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *scan_help =
+       "Usage:\n"
+       "\tscan [--length=N] [--numrsp=N] [--iac=lap] [--flush] [--class] [--info] [--oui] [--refresh]\n";
+
+static void cmd_scan(int dev_id, int argc, char **argv)
+{
+       inquiry_info *info = NULL;
+       uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
+       int num_rsp, length, flags;
+       uint8_t cls[3], features[8];
+       char addr[18], name[249], oui[9], *comp, *tmp;
+       struct hci_version version;
+       struct hci_dev_info di;
+       struct hci_conn_info_req *cr;
+       int refresh = 0, extcls = 0, extinf = 0, extoui = 0;
+       int i, n, l, opt, dd, cc, nc;
+
+       length  = 8;    /* ~10 seconds */
+       num_rsp = 0;
+       flags   = 0;
+
+       for_each_opt(opt, scan_options, NULL) {
+               switch (opt) {
+               case 'l':
+                       length = atoi(optarg);
+                       break;
+
+               case 'n':
+                       num_rsp = atoi(optarg);
+                       break;
+
+               case 'i':
+                       l = strtoul(optarg, 0, 16);
+                       if (!strcasecmp(optarg, "giac")) {
+                               l = 0x9e8b33;
+                       } else if (!strcasecmp(optarg, "liac")) {
+                               l = 0x9e8b00;
+                       } else if (l < 0x9e8b00 || l > 0x9e8b3f) {
+                               printf("Invalid access code 0x%x\n", l);
+                               exit(1);
+                       }
+                       lap[0] = (l & 0xff);
+                       lap[1] = (l >> 8) & 0xff;
+                       lap[2] = (l >> 16) & 0xff;
+                       break;
+
+               case 'f':
+                       flags |= IREQ_CACHE_FLUSH;
+                       break;
+
+               case 'r':
+                       refresh = 1;
+                       break;
+
+               case 'C':
+                       extcls = 1;
+                       break;
+
+               case 'I':
+                       extinf = 1;
+                       break;
+
+               case 'O':
+                       extoui = 1;
+                       break;
+
+               case 'A':
+                       extcls = 1;
+                       extinf = 1;
+                       extoui = 1;
+                       break;
+
+               default:
+                       printf("%s", scan_help);
+                       return;
+               }
+       }
+       helper_arg(0, 0, &argc, &argv, scan_help);
+
+       if (dev_id < 0) {
+               dev_id = hci_get_route(NULL);
+               if (dev_id < 0) {
+                       perror("Device is not available");
+                       exit(1);
+               }
+       }
+
+       if (hci_devinfo(dev_id, &di) < 0) {
+               perror("Can't get device info");
+               exit(1);
+       }
+
+       printf("Scanning ...\n");
+       num_rsp = hci_inquiry(dev_id, length, num_rsp, lap, &info, flags);
+       if (num_rsp < 0) {
+               perror("Inquiry failed");
+               exit(1);
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               free(info);
+               exit(1);
+       }
+
+       if (extcls || extinf || extoui)
+               printf("\n");
+
+       for (i = 0; i < num_rsp; i++) {
+               uint16_t handle = 0;
+
+               if (!refresh) {
+                       memset(name, 0, sizeof(name));
+                       tmp = get_device_name(&di.bdaddr, &(info+i)->bdaddr);
+                       if (tmp) {
+                               strncpy(name, tmp, 249);
+                               free(tmp);
+                               nc = 1;
+                       } else
+                               nc = 0;
+               } else
+                       nc = 0;
+
+               if (!extcls && !extinf && !extoui) {
+                       ba2str(&(info+i)->bdaddr, addr);
+
+                       if (nc) {
+                               printf("\t%s\t%s\n", addr, name);
+                               continue;
+                       }
+
+                       if (hci_read_remote_name_with_clock_offset(dd,
+                                       &(info+i)->bdaddr,
+                                       (info+i)->pscan_rep_mode,
+                                       (info+i)->clock_offset | 0x8000,
+                                       sizeof(name), name, 100000) < 0)
+                               strcpy(name, "n/a");
+
+                       for (n = 0; n < 248 && name[n]; n++) {
+                               if ((unsigned char) name[i] < 32 || name[i] == 127)
+                                       name[i] = '.';
+                       }
+
+                       name[248] = '\0';
+
+                       printf("\t%s\t%s\n", addr, name);
+                       continue;
+               }
+
+               ba2str(&(info+i)->bdaddr, addr);
+               printf("BD Address:\t%s [mode %d, clkoffset 0x%4.4x]\n", addr,
+                       (info+i)->pscan_rep_mode, btohs((info+i)->clock_offset));
+
+               if (extoui) {
+                       ba2oui(&(info+i)->bdaddr, oui);
+                       comp = ouitocomp(oui);
+                       if (comp) {
+                               printf("OUI company:\t%s (%s)\n", comp, oui);
+                               free(comp);
+                       }
+               }
+
+               cc = 0;
+
+               if (extinf) {
+                       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+                       if (cr) {
+                               bacpy(&cr->bdaddr, &(info+i)->bdaddr);
+                               cr->type = ACL_LINK;
+                               if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+                                       handle = 0;
+                                       cc = 1;
+                               } else {
+                                       handle = htobs(cr->conn_info->handle);
+                                       cc = 0;
+                               }
+                               free(cr);
+                       }
+
+                       if (cc) {
+                               if (hci_create_connection(dd, &(info+i)->bdaddr,
+                                               htobs(di.pkt_type & ACL_PTYPE_MASK),
+                                               (info+i)->clock_offset | 0x8000,
+                                               0x01, &handle, 25000) < 0) {
+                                       handle = 0;
+                                       cc = 0;
+                               }
+                       }
+               }
+
+               if (handle > 0 || !nc) {
+                       if (hci_read_remote_name_with_clock_offset(dd,
+                                       &(info+i)->bdaddr,
+                                       (info+i)->pscan_rep_mode,
+                                       (info+i)->clock_offset | 0x8000,
+                                       sizeof(name), name, 100000) < 0) {
+                               if (!nc)
+                                       strcpy(name, "n/a");
+                       } else {
+                               for (n = 0; n < 248 && name[n]; n++) {
+                                       if ((unsigned char) name[i] < 32 || name[i] == 127)
+                                               name[i] = '.';
+                               }
+
+                               name[248] = '\0';
+                               nc = 0;
+                       }
+               }
+
+               if (strlen(name) > 0)
+                       printf("Device name:\t%s%s\n", name, nc ? " [cached]" : "");
+
+               if (extcls) {
+                       memcpy(cls, (info+i)->dev_class, 3);
+                       printf("Device class:\t");
+                       if ((cls[1] & 0x1f) > sizeof(major_classes) / sizeof(char *))
+                               printf("Invalid");
+                       else
+                               printf("%s, %s", major_classes[cls[1] & 0x1f],
+                                       get_minor_device_name(cls[1] & 0x1f, cls[0] >> 2));
+                       printf(" (0x%2.2x%2.2x%2.2x)\n", cls[2], cls[1], cls[0]);
+               }
+
+               if (extinf && handle > 0) {
+                       if (hci_read_remote_version(dd, handle, &version, 20000) == 0) {
+                               char *ver = lmp_vertostr(version.lmp_ver);
+                               printf("Manufacturer:\t%s (%d)\n",
+                                       bt_compidtostr(version.manufacturer),
+                                       version.manufacturer);
+                               printf("LMP version:\t%s (0x%x) [subver 0x%x]\n",
+                                       ver ? ver : "n/a",
+                                       version.lmp_ver, version.lmp_subver);
+                               if (ver)
+                                       bt_free(ver);
+                       }
+
+                       if (hci_read_remote_features(dd, handle, features, 20000) == 0) {
+                               char *tmp = lmp_featurestostr(features, "\t\t", 63);
+                               printf("LMP features:\t0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x"
+                                       " 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n",
+                                       features[0], features[1],
+                                       features[2], features[3],
+                                       features[4], features[5],
+                                       features[6], features[7]);
+                               printf("%s\n", tmp);
+                               bt_free(tmp);
+                       }
+
+                       if (cc) {
+                               usleep(10000);
+                               hci_disconnect(dd, handle, HCI_OE_USER_ENDED_CONNECTION, 10000);
+                       }
+               }
+
+               printf("\n");
+       }
+
+       bt_free(info);
+
+       hci_close_dev(dd);
+}
+
+/* Remote name */
+
+static struct option name_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *name_help =
+       "Usage:\n"
+       "\tname <bdaddr>\n";
+
+static void cmd_name(int dev_id, int argc, char **argv)
+{
+       bdaddr_t bdaddr;
+       char name[248];
+       int opt, dd;
+
+       for_each_opt(opt, name_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", name_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, name_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_get_route(&bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Device is not available.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       if (hci_read_remote_name(dd, &bdaddr, sizeof(name), name, 25000) == 0)
+               printf("%s\n", name);
+
+       hci_close_dev(dd);
+}
+
+/* Info about remote device */
+
+static struct option info_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *info_help =
+       "Usage:\n"
+       "\tinfo <bdaddr>\n";
+
+static void cmd_info(int dev_id, int argc, char **argv)
+{
+       bdaddr_t bdaddr;
+       uint16_t handle;
+       uint8_t features[8], max_page = 0;
+       char name[249], oui[9], *comp, *tmp;
+       struct hci_version version;
+       struct hci_dev_info di;
+       struct hci_conn_info_req *cr;
+       int i, opt, dd, cc = 0;
+
+       for_each_opt(opt, info_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", info_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, info_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0)
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(&bdaddr);
+
+       if (dev_id < 0) {
+               fprintf(stderr, "Device is not available or not connected.\n");
+               exit(1);
+       }
+
+       if (hci_devinfo(dev_id, &di) < 0) {
+               perror("Can't get device info");
+               exit(1);
+       }
+
+       printf("Requesting information ...\n");
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't get connection info");
+               close(dd);
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               if (hci_create_connection(dd, &bdaddr,
+                                       htobs(di.pkt_type & ACL_PTYPE_MASK),
+                                       0, 0x01, &handle, 25000) < 0) {
+                       perror("Can't create connection");
+                       close(dd);
+                       exit(1);
+               }
+               sleep(1);
+               cc = 1;
+       } else
+               handle = htobs(cr->conn_info->handle);
+
+       printf("\tBD Address:  %s\n", argv[0]);
+
+       ba2oui(&bdaddr, oui);
+       comp = ouitocomp(oui);
+       if (comp) {
+               printf("\tOUI Company: %s (%s)\n", comp, oui);
+               free(comp);
+       }
+
+       if (hci_read_remote_name(dd, &bdaddr, sizeof(name), name, 25000) == 0)
+               printf("\tDevice Name: %s\n", name);
+
+       if (hci_read_remote_version(dd, handle, &version, 20000) == 0) {
+               char *ver = lmp_vertostr(version.lmp_ver);
+               printf("\tLMP Version: %s (0x%x) LMP Subversion: 0x%x\n"
+                       "\tManufacturer: %s (%d)\n",
+                       ver ? ver : "n/a",
+                       version.lmp_ver,
+                       version.lmp_subver,
+                       bt_compidtostr(version.manufacturer),
+                       version.manufacturer);
+               if (ver)
+                       bt_free(ver);
+       }
+
+       memset(features, 0, sizeof(features));
+       hci_read_remote_features(dd, handle, features, 20000);
+
+       if ((di.features[7] & LMP_EXT_FEAT) && (features[7] & LMP_EXT_FEAT))
+               hci_read_remote_ext_features(dd, handle, 0, &max_page,
+                                                       features, 20000);
+
+       printf("\tFeatures%s: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x "
+                               "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n",
+               (max_page > 0) ? " page 0" : "",
+               features[0], features[1], features[2], features[3],
+               features[4], features[5], features[6], features[7]);
+
+       tmp = lmp_featurestostr(features, "\t\t", 63);
+       printf("%s\n", tmp);
+       bt_free(tmp);
+
+       for (i = 1; i <= max_page; i++) {
+               if (hci_read_remote_ext_features(dd, handle, i, NULL,
+                                                       features, 20000) < 0)
+                       continue;
+
+               printf("\tFeatures page %d: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x "
+                                       "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", i,
+                       features[0], features[1], features[2], features[3],
+                       features[4], features[5], features[6], features[7]);
+       }
+
+       if (cc) {
+               usleep(10000);
+               hci_disconnect(dd, handle, HCI_OE_USER_ENDED_CONNECTION, 10000);
+       }
+
+       hci_close_dev(dd);
+}
+
+/* Start periodic inquiry */
+
+static struct option spinq_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *spinq_help =
+       "Usage:\n"
+       "\tspinq\n";
+
+static void cmd_spinq(int dev_id, int argc, char **argv)
+{
+       uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
+       struct hci_request rq;
+       periodic_inquiry_cp cp;
+       int opt, dd;
+
+       for_each_opt(opt, spinq_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", spinq_help);
+                       return;
+               }
+       }
+       helper_arg(0, 0, &argc, &argv, spinq_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Device open failed");
+               exit(EXIT_FAILURE);
+       }
+
+       memset(&cp, 0, sizeof(cp));
+       memcpy(cp.lap, lap, 3);
+       cp.max_period = htobs(16);
+       cp.min_period = htobs(10);
+       cp.length     = 8;
+       cp.num_rsp    = 0;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_PERIODIC_INQUIRY;
+       rq.cparam = &cp;
+       rq.clen   = PERIODIC_INQUIRY_CP_SIZE;
+
+       if (hci_send_req(dd, &rq, 100) < 0) {
+               perror("Periodic inquiry failed");
+               exit(EXIT_FAILURE);
+       }
+
+       hci_close_dev(dd);
+}
+
+/* Exit periodic inquiry */
+
+static struct option epinq_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *epinq_help =
+       "Usage:\n"
+       "\tepinq\n";
+
+static void cmd_epinq(int dev_id, int argc, char **argv)
+{
+       int opt, dd;
+
+       for_each_opt(opt, epinq_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", epinq_help);
+                       return;
+               }
+       }
+       helper_arg(0, 0, &argc, &argv, epinq_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Device open failed");
+               exit(EXIT_FAILURE);
+       }
+
+       if (hci_send_cmd(dd, OGF_LINK_CTL,
+                               OCF_EXIT_PERIODIC_INQUIRY, 0, NULL) < 0) {
+               perror("Exit periodic inquiry failed");
+               exit(EXIT_FAILURE);
+       }
+
+       hci_close_dev(dd);
+}
+
+/* Send arbitrary HCI commands */
+
+static struct option cmd_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *cmd_help =
+       "Usage:\n"
+       "\tcmd <ogf> <ocf> [parameters]\n"
+       "Example:\n"
+       "\tcmd 0x03 0x0013 0x41 0x42 0x43 0x44\n";
+
+static void cmd_cmd(int dev_id, int argc, char **argv)
+{
+       unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr = buf;
+       struct hci_filter flt;
+       hci_event_hdr *hdr;
+       int i, opt, len, dd;
+       uint16_t ocf;
+       uint8_t ogf;
+
+       for_each_opt(opt, cmd_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", cmd_help);
+                       return;
+               }
+       }
+       helper_arg(2, -1, &argc, &argv, cmd_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       errno = 0;
+       ogf = strtol(argv[0], NULL, 16);
+       ocf = strtol(argv[1], NULL, 16);
+       if (errno == ERANGE || (ogf > 0x3f) || (ocf > 0x3ff)) {
+               printf("%s", cmd_help);
+               return;
+       }
+
+       for (i = 2, len = 0; i < argc && len < (int) sizeof(buf); i++, len++)
+               *ptr++ = (uint8_t) strtol(argv[i], NULL, 16);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Device open failed");
+               exit(EXIT_FAILURE);
+       }
+
+       /* Setup filter */
+       hci_filter_clear(&flt);
+       hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
+       hci_filter_all_events(&flt);
+       if (setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
+               perror("HCI filter setup failed");
+               exit(EXIT_FAILURE);
+       }
+
+       printf("< HCI Command: ogf 0x%02x, ocf 0x%04x, plen %d\n", ogf, ocf, len);
+       hex_dump("  ", 20, buf, len); fflush(stdout);
+
+       if (hci_send_cmd(dd, ogf, ocf, len, buf) < 0) {
+               perror("Send failed");
+               exit(EXIT_FAILURE);
+       }
+
+       len = read(dd, buf, sizeof(buf));
+       if (len < 0) {
+               perror("Read failed");
+               exit(EXIT_FAILURE);
+       }
+
+       hdr = (void *)(buf + 1);
+       ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
+       len -= (1 + HCI_EVENT_HDR_SIZE);
+
+       printf("> HCI Event: 0x%02x plen %d\n", hdr->evt, hdr->plen);
+       hex_dump("  ", 20, ptr, len); fflush(stdout);
+
+       hci_close_dev(dd);
+}
+
+/* Display active connections */
+
+static struct option con_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *con_help =
+       "Usage:\n"
+       "\tcon\n";
+
+static void cmd_con(int dev_id, int argc, char **argv)
+{
+       int opt;
+
+       for_each_opt(opt, con_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", con_help);
+                       return;
+               }
+       }
+       helper_arg(0, 0, &argc, &argv, con_help);
+
+       printf("Connections:\n");
+
+       hci_for_each_dev(HCI_UP, conn_list, dev_id);
+}
+
+/* Create connection */
+
+static struct option cc_options[] = {
+       { "help",       0, 0, 'h' },
+       { "role",       1, 0, 'r' },
+       { "ptype",      1, 0, 'p' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *cc_help =
+       "Usage:\n"
+       "\tcc [--role=m|s] [--ptype=pkt_types] <bdaddr>\n"
+       "Example:\n"
+       "\tcc --ptype=dm1,dh3,dh5 01:02:03:04:05:06\n"
+       "\tcc --role=m 01:02:03:04:05:06\n";
+
+static void cmd_cc(int dev_id, int argc, char **argv)
+{
+       bdaddr_t bdaddr;
+       uint16_t handle;
+       uint8_t role;
+       unsigned int ptype;
+       int dd, opt;
+
+       role = 0x01;
+       ptype = HCI_DM1 | HCI_DM3 | HCI_DM5 | HCI_DH1 | HCI_DH3 | HCI_DH5;
+
+       for_each_opt(opt, cc_options, NULL) {
+               switch (opt) {
+               case 'p':
+                       hci_strtoptype(optarg, &ptype);
+                       break;
+
+               case 'r':
+                       role = optarg[0] == 'm' ? 0 : 1;
+                       break;
+
+               default:
+                       printf("%s", cc_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, cc_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_get_route(&bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Device is not available.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       if (hci_create_connection(dd, &bdaddr, htobs(ptype),
+                               htobs(0x0000), role, &handle, 25000) < 0)
+               perror("Can't create connection");
+
+       hci_close_dev(dd);
+}
+
+/* Close connection */
+
+static struct option dc_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *dc_help =
+       "Usage:\n"
+       "\tdc <bdaddr> [reason]\n";
+
+static void cmd_dc(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint8_t reason;
+       int opt, dd;
+
+       for_each_opt(opt, dc_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", dc_help);
+                       return;
+               }
+       }
+       helper_arg(1, 2, &argc, &argv, dc_help);
+
+       str2ba(argv[0], &bdaddr);
+       reason = (argc > 1) ? atoi(argv[1]) : HCI_OE_USER_ENDED_CONNECTION;
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (hci_disconnect(dd, htobs(cr->conn_info->handle),
+                                               reason, 10000) < 0)
+               perror("Disconnect failed");
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Role switch */
+
+static struct option sr_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *sr_help =
+       "Usage:\n"
+       "\tsr <bdaddr> <role>\n";
+
+static void cmd_sr(int dev_id, int argc, char **argv)
+{
+       bdaddr_t bdaddr;
+       uint8_t role;
+       int opt, dd;
+
+       for_each_opt(opt, sr_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", sr_help);
+                       return;
+               }
+       }
+       helper_arg(2, 2, &argc, &argv, sr_help);
+
+       str2ba(argv[0], &bdaddr);
+       switch (argv[1][0]) {
+       case 'm':
+               role = 0;
+               break;
+       case 's':
+               role = 1;
+               break;
+       default:
+               role = atoi(argv[1]);
+               break;
+       }
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       if (hci_switch_role(dd, &bdaddr, role, 10000) < 0) {
+               perror("Switch role request failed");
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+}
+
+/* Read RSSI */
+
+static struct option rssi_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *rssi_help =
+       "Usage:\n"
+       "\trssi <bdaddr>\n";
+
+static void cmd_rssi(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       int8_t rssi;
+       int opt, dd;
+
+       for_each_opt(opt, rssi_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", rssi_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, rssi_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (hci_read_rssi(dd, htobs(cr->conn_info->handle), &rssi, 1000) < 0) {
+               perror("Read RSSI failed");
+               exit(1);
+       }
+
+       printf("RSSI return value: %d\n", rssi);
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Get link quality */
+
+static struct option lq_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lq_help =
+       "Usage:\n"
+       "\tlq <bdaddr>\n";
+
+static void cmd_lq(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint8_t lq;
+       int opt, dd;
+
+       for_each_opt(opt, lq_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", lq_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, lq_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (hci_read_link_quality(dd, htobs(cr->conn_info->handle), &lq, 1000) < 0) {
+               perror("HCI read_link_quality request failed");
+               exit(1);
+       }
+
+       printf("Link quality: %d\n", lq);
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Get transmit power level */
+
+static struct option tpl_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *tpl_help =
+       "Usage:\n"
+       "\ttpl <bdaddr> [type]\n";
+
+static void cmd_tpl(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint8_t type;
+       int8_t level;
+       int opt, dd;
+
+       for_each_opt(opt, tpl_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", tpl_help);
+                       return;
+               }
+       }
+       helper_arg(1, 2, &argc, &argv, tpl_help);
+
+       str2ba(argv[0], &bdaddr);
+       type = (argc > 1) ? atoi(argv[1]) : 0;
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (hci_read_transmit_power_level(dd, htobs(cr->conn_info->handle), type, &level, 1000) < 0) {
+               perror("HCI read transmit power level request failed");
+               exit(1);
+       }
+
+       printf("%s transmit power level: %d\n",
+               (type == 0) ? "Current" : "Maximum", level);
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Get AFH channel map */
+
+static struct option afh_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *afh_help =
+       "Usage:\n"
+       "\tafh <bdaddr>\n";
+
+static void cmd_afh(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint16_t handle;
+       uint8_t mode, map[10];
+       int opt, dd;
+
+       for_each_opt(opt, afh_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", afh_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, afh_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       handle = htobs(cr->conn_info->handle);
+
+       if (hci_read_afh_map(dd, handle, &mode, map, 1000) < 0) {
+               perror("HCI read AFH map request failed");
+               exit(1);
+       }
+
+       if (mode == 0x01) {
+               int i;
+               printf("AFH map: 0x");
+               for (i = 0; i < 10; i++)
+                       printf("%02x", map[i]);
+               printf("\n");
+       } else
+               printf("AFH disabled\n");
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Set connection packet type */
+
+static struct option cpt_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *cpt_help =
+       "Usage:\n"
+       "\tcpt <bdaddr> <packet_types>\n";
+
+static void cmd_cpt(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       struct hci_request rq;
+       set_conn_ptype_cp cp;
+       evt_conn_ptype_changed rp;
+       bdaddr_t bdaddr;
+       unsigned int ptype;
+       int dd, opt;
+
+       for_each_opt(opt, cpt_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", cpt_help);
+                       return;
+               }
+       }
+       helper_arg(2, 2, &argc, &argv, cpt_help);
+
+       str2ba(argv[0], &bdaddr);
+       hci_strtoptype(argv[1], &ptype);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       cp.handle   = htobs(cr->conn_info->handle);
+       cp.pkt_type = ptype;
+
+       memset(&rq, 0, sizeof(rq));
+       rq.ogf    = OGF_LINK_CTL;
+       rq.ocf    = OCF_SET_CONN_PTYPE;
+       rq.cparam = &cp;
+       rq.clen   = SET_CONN_PTYPE_CP_SIZE;
+       rq.rparam = &rp;
+       rq.rlen   = EVT_CONN_PTYPE_CHANGED_SIZE;
+       rq.event  = EVT_CONN_PTYPE_CHANGED;
+
+       if (hci_send_req(dd, &rq, 100) < 0) {
+               perror("Packet type change failed");
+               exit(1);
+       }
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Get/Set link policy settings */
+
+static struct option lp_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lp_help =
+       "Usage:\n"
+       "\tlp <bdaddr> [link policy]\n";
+
+static void cmd_lp(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint16_t policy;
+       int opt, dd;
+
+       for_each_opt(opt, lp_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", lp_help);
+                       return;
+               }
+       }
+       helper_arg(1, 2, &argc, &argv, lp_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (argc == 1) {
+               char *str;
+               if (hci_read_link_policy(dd, htobs(cr->conn_info->handle),
+                                                       &policy, 1000) < 0) {
+                       perror("HCI read_link_policy_settings request failed");
+                       exit(1);
+               }
+
+               policy = btohs(policy);
+               str = hci_lptostr(policy);
+               if (str) {
+                       printf("Link policy settings: %s\n", str);
+                       bt_free(str);
+               } else {
+                       fprintf(stderr, "Invalig settings\n");
+                       exit(1);
+               }
+       } else {
+               unsigned int val;
+               if (hci_strtolp(argv[1], &val) < 0) {
+                       fprintf(stderr, "Invalig arguments\n");
+                       exit(1);
+               }
+               policy = val;
+
+               if (hci_write_link_policy(dd, htobs(cr->conn_info->handle),
+                                               htobs(policy), 1000) < 0) {
+                       perror("HCI write_link_policy_settings request failed");
+                       exit(1);
+               }
+       }
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Get/Set link supervision timeout */
+
+static struct option lst_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lst_help =
+       "Usage:\n"
+       "\tlst <bdaddr> [new value in slots]\n";
+
+static void cmd_lst(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint16_t timeout;
+       int opt, dd;
+
+       for_each_opt(opt, lst_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", lst_help);
+                       return;
+               }
+       }
+       helper_arg(1, 2, &argc, &argv, lst_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (argc == 1) {
+               if (hci_read_link_supervision_timeout(dd, htobs(cr->conn_info->handle),
+                                                       &timeout, 1000) < 0) {
+                       perror("HCI read_link_supervision_timeout request failed");
+                       exit(1);
+               }
+
+               timeout = btohs(timeout);
+
+               if (timeout)
+                       printf("Link supervision timeout: %u slots (%.2f msec)\n",
+                               timeout, (float) timeout * 0.625);
+               else
+                       printf("Link supervision timeout never expires\n");
+       } else {
+               timeout = strtol(argv[1], NULL, 10);
+
+               if (hci_write_link_supervision_timeout(dd, htobs(cr->conn_info->handle),
+                                                       htobs(timeout), 1000) < 0) {
+                       perror("HCI write_link_supervision_timeout request failed");
+                       exit(1);
+               }
+       }
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Request authentication */
+
+static struct option auth_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *auth_help =
+       "Usage:\n"
+       "\tauth <bdaddr>\n";
+
+static void cmd_auth(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       int opt, dd;
+
+       for_each_opt(opt, auth_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", auth_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, auth_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (hci_authenticate_link(dd, htobs(cr->conn_info->handle), 25000) < 0) {
+               perror("HCI authentication request failed");
+               exit(1);
+       }
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Activate encryption */
+
+static struct option enc_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *enc_help =
+       "Usage:\n"
+       "\tenc <bdaddr> [encrypt enable]\n";
+
+static void cmd_enc(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint8_t encrypt;
+       int opt, dd;
+
+       for_each_opt(opt, enc_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", enc_help);
+                       return;
+               }
+       }
+       helper_arg(1, 2, &argc, &argv, enc_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       encrypt = (argc > 1) ? atoi(argv[1]) : 1;
+
+       if (hci_encrypt_link(dd, htobs(cr->conn_info->handle), encrypt, 25000) < 0) {
+               perror("HCI set encryption request failed");
+               exit(1);
+       }
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Change connection link key */
+
+static struct option key_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *key_help =
+       "Usage:\n"
+       "\tkey <bdaddr>\n";
+
+static void cmd_key(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       int opt, dd;
+
+       for_each_opt(opt, key_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", key_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, key_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (hci_change_link_key(dd, htobs(cr->conn_info->handle), 25000) < 0) {
+               perror("Changing link key failed");
+               exit(1);
+       }
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Read clock offset */
+
+static struct option clkoff_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *clkoff_help =
+       "Usage:\n"
+       "\tclkoff <bdaddr>\n";
+
+static void cmd_clkoff(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint16_t offset;
+       int opt, dd;
+
+       for_each_opt(opt, clkoff_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", clkoff_help);
+                       return;
+               }
+       }
+       helper_arg(1, 1, &argc, &argv, clkoff_help);
+
+       str2ba(argv[0], &bdaddr);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+       if (!cr) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       bacpy(&cr->bdaddr, &bdaddr);
+       cr->type = ACL_LINK;
+       if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+               perror("Get connection info failed");
+               exit(1);
+       }
+
+       if (hci_read_clock_offset(dd, htobs(cr->conn_info->handle), &offset, 1000) < 0) {
+               perror("Reading clock offset failed");
+               exit(1);
+       }
+
+       printf("Clock offset: 0x%4.4x\n", btohs(offset));
+
+       free(cr);
+
+       hci_close_dev(dd);
+}
+
+/* Read clock */
+
+static struct option clock_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *clock_help =
+       "Usage:\n"
+       "\tclock [bdaddr] [which clock]\n";
+
+static void cmd_clock(int dev_id, int argc, char **argv)
+{
+       struct hci_conn_info_req *cr;
+       bdaddr_t bdaddr;
+       uint8_t which;
+       uint32_t handle, clock;
+       uint16_t accuracy;
+       int opt, dd;
+
+       for_each_opt(opt, clock_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", clock_help);
+                       return;
+               }
+       }
+       helper_arg(0, 2, &argc, &argv, clock_help);
+
+       if (argc > 0)
+               str2ba(argv[0], &bdaddr);
+       else
+               bacpy(&bdaddr, BDADDR_ANY);
+
+       if (dev_id < 0 && !bacmp(&bdaddr, BDADDR_ANY))
+               dev_id = hci_get_route(NULL);
+
+       if (dev_id < 0) {
+               dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
+               if (dev_id < 0) {
+                       fprintf(stderr, "Not connected.\n");
+                       exit(1);
+               }
+       }
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("HCI device open failed");
+               exit(1);
+       }
+
+       if (bacmp(&bdaddr, BDADDR_ANY)) {
+               cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
+               if (!cr) {
+                       perror("Can't allocate memory");
+                       exit(1);
+               }
+
+               bacpy(&cr->bdaddr, &bdaddr);
+               cr->type = ACL_LINK;
+               if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+                       perror("Get connection info failed");
+                       free(cr);
+                       exit(1);
+               }
+
+               handle = htobs(cr->conn_info->handle);
+               which = (argc > 1) ? atoi(argv[1]) : 0x01;
+
+               free(cr);
+       } else {
+               handle = 0x00;
+               which = 0x00;
+       }
+
+       if (hci_read_clock(dd, handle, which, &clock, &accuracy, 1000) < 0) {
+               perror("Reading clock failed");
+               exit(1);
+       }
+
+       accuracy = btohs(accuracy);
+
+       printf("Clock:    0x%4.4x\n", btohl(clock));
+       printf("Accuracy: %.2f msec\n", (float) accuracy * 0.3125);
+
+       hci_close_dev(dd);
+}
+
+static int read_flags(uint8_t *flags, const uint8_t *data, size_t size)
+{
+       unsigned int offset;
+
+       if (!flags || !data)
+               return -EINVAL;
+
+       offset = 0;
+       while (offset < size) {
+               uint8_t len = data[offset];
+               uint8_t type = data[offset + 1];
+
+               /* Check if it is the end of the significant part */
+               if (len == 0)
+                       break;
+
+               if (type == FLAGS_AD_TYPE) {
+                       *flags = data[offset + 2];
+                       return 0;
+               }
+
+               offset += 1 + len;
+       }
+
+       return -ENOENT;
+}
+
+static int check_report_filter(uint8_t procedure, le_advertising_info *info)
+{
+       uint8_t flags;
+
+       /* If no discovery procedure is set, all reports are treat as valid */
+       if (procedure == 0)
+               return 1;
+
+       /* Read flags AD type value from the advertising report if it exists */
+       if (read_flags(&flags, info->data, info->length))
+               return 0;
+
+       switch (procedure) {
+       case 'l': /* Limited Discovery Procedure */
+               if (flags & FLAGS_LIMITED_MODE_BIT)
+                       return 1;
+               break;
+       case 'g': /* General Discovery Procedure */
+               if (flags & (FLAGS_LIMITED_MODE_BIT | FLAGS_GENERAL_MODE_BIT))
+                       return 1;
+               break;
+       default:
+               fprintf(stderr, "Unknown discovery procedure\n");
+       }
+
+       return 0;
+}
+
+static int print_advertising_devices(int dd, uint8_t filter_type)
+{
+       unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
+       struct hci_filter nf, of;
+       socklen_t olen;
+       hci_event_hdr *hdr;
+       int num, len;
+
+       olen = sizeof(of);
+       if (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) {
+               printf("Could not get socket options\n");
+               return -1;
+       }
+
+       hci_filter_clear(&nf);
+       hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
+       hci_filter_set_event(EVT_LE_META_EVENT, &nf);
+
+       if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
+               printf("Could not set socket options\n");
+               return -1;
+       }
+
+       /* Wait for 10 report events */
+       num = 10;
+       while (num--) {
+               evt_le_meta_event *meta;
+               le_advertising_info *info;
+               char addr[18];
+
+               while ((len = read(dd, buf, sizeof(buf))) < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       goto done;
+               }
+
+               hdr = (void *) (buf + 1);
+               ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
+               len -= (1 + HCI_EVENT_HDR_SIZE);
+
+               meta = (void *) ptr;
+
+               if (meta->subevent != 0x02)
+                       goto done;
+
+               /* Ignoring multiple reports */
+               info = (le_advertising_info *) (meta->data + 1);
+               if (check_report_filter(filter_type, info)) {
+                       ba2str(&info->bdaddr, addr);
+                       printf("%s\n", addr);
+               }
+       }
+
+done:
+       setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
+
+       if (len < 0)
+               return -1;
+
+       return 0;
+}
+
+static struct option lescan_options[] = {
+       { "help",       0, 0, 'h' },
+       { "privacy",    0, 0, 'p' },
+       { "passive",    0, 0, 'P' },
+       { "discovery",  1, 0, 'd' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lescan_help =
+       "Usage:\n"
+       "\tlescan [--privacy] enable privacy\n"
+       "\tlescan [--passive] set scan type passive (default active)\n"
+       "\tlescan [--discovery=g|l] enable general or limited discovery"
+               "procedure\n";
+
+static void cmd_lescan(int dev_id, int argc, char **argv)
+{
+       int err, opt, dd;
+       uint8_t own_type = 0x00;
+       uint8_t scan_type = 0x01;
+       uint8_t filter_type = 0;
+       uint16_t interval = htobs(0x0010);
+       uint16_t window = htobs(0x0010);
+
+       for_each_opt(opt, lescan_options, NULL) {
+               switch (opt) {
+               case 'p':
+                       own_type = 0x01; /* Random */
+                       break;
+               case 'P':
+                       scan_type = 0x00; /* Passive */
+                       break;
+               case 'd':
+                       filter_type = optarg[0];
+                       if (filter_type != 'g' && filter_type != 'l') {
+                               fprintf(stderr, "Unknown discovery procedure\n");
+                               exit(1);
+                       }
+
+                       interval = htobs(0x0012);
+                       window = htobs(0x0012);
+                       break;
+               default:
+                       printf("%s", lescan_help);
+                       return;
+               }
+       }
+       helper_arg(0, 1, &argc, &argv, lescan_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Could not open device");
+               exit(1);
+       }
+
+       err = hci_le_set_scan_parameters(dd, scan_type, interval, window,
+                                                       own_type, 0x00, 1000);
+       if (err < 0) {
+               perror("Set scan parameters failed");
+               exit(1);
+       }
+
+       err = hci_le_set_scan_enable(dd, 0x01, 0x00, 1000);
+       if (err < 0) {
+               perror("Enable scan failed");
+               exit(1);
+       }
+
+       printf("LE Scan ...\n");
+
+       err = print_advertising_devices(dd, filter_type);
+       if (err < 0) {
+               perror("Could not receive advertising events");
+               exit(1);
+       }
+
+       err = hci_le_set_scan_enable(dd, 0x00, 0x00, 1000);
+       if (err < 0) {
+               perror("Disable scan failed");
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+}
+
+static struct option lecc_options[] = {
+       { "help",       0, 0, 'h' },
+       { "random",     0, 0, 'r' },
+       { "whitelist",  0, 0, 'w' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lecc_help =
+       "Usage:\n"
+       "\tlecc [--random] <bdaddr>\n"
+       "\tlecc --whitelist\n";
+
+static void cmd_lecc(int dev_id, int argc, char **argv)
+{
+       int err, opt, dd;
+       bdaddr_t bdaddr;
+       uint16_t interval, latency, max_ce_length, max_interval, min_ce_length;
+       uint16_t min_interval, supervision_timeout, window, handle;
+       uint8_t initiator_filter, own_bdaddr_type, peer_bdaddr_type;
+
+       peer_bdaddr_type = LE_PUBLIC_ADDRESS;
+       initiator_filter = 0; /* Use peer address */
+
+       for_each_opt(opt, lecc_options, NULL) {
+               switch (opt) {
+               case 'r':
+                       peer_bdaddr_type = LE_RANDOM_ADDRESS;
+                       break;
+               case 'w':
+                       initiator_filter = 0x01; /* Use white list */
+                       break;
+               default:
+                       printf("%s", lecc_help);
+                       return;
+               }
+       }
+       helper_arg(0, 1, &argc, &argv, lecc_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Could not open device");
+               exit(1);
+       }
+
+       memset(&bdaddr, 0, sizeof(bdaddr_t));
+       if (argv[0])
+               str2ba(argv[0], &bdaddr);
+
+       interval = htobs(0x0004);
+       window = htobs(0x0004);
+       own_bdaddr_type = 0x00;
+       min_interval = htobs(0x000F);
+       max_interval = htobs(0x000F);
+       latency = htobs(0x0000);
+       supervision_timeout = htobs(0x0C80);
+       min_ce_length = htobs(0x0001);
+       max_ce_length = htobs(0x0001);
+
+       err = hci_le_create_conn(dd, interval, window, initiator_filter,
+                       peer_bdaddr_type, bdaddr, own_bdaddr_type, min_interval,
+                       max_interval, latency, supervision_timeout,
+                       min_ce_length, max_ce_length, &handle, 25000);
+       if (err < 0) {
+               perror("Could not create connection");
+               exit(1);
+       }
+
+       printf("Connection handle %d\n", handle);
+
+       hci_close_dev(dd);
+}
+
+static struct option lewladd_options[] = {
+       { "help",       0, 0, 'h' },
+       { "random",     0, 0, 'r' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lewladd_help =
+       "Usage:\n"
+       "\tlewladd [--random] <bdaddr>\n";
+
+static void cmd_lewladd(int dev_id, int argc, char **argv)
+{
+       int err, opt, dd;
+       bdaddr_t bdaddr;
+       uint8_t bdaddr_type = LE_PUBLIC_ADDRESS;
+
+       for_each_opt(opt, lewladd_options, NULL) {
+               switch (opt) {
+               case 'r':
+                       bdaddr_type = LE_RANDOM_ADDRESS;
+                       break;
+               default:
+                       printf("%s", lewladd_help);
+                       return;
+               }
+       }
+
+       helper_arg(1, 1, &argc, &argv, lewladd_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Could not open device");
+               exit(1);
+       }
+
+       str2ba(argv[0], &bdaddr);
+
+       err = hci_le_add_white_list(dd, &bdaddr, bdaddr_type, 1000);
+       hci_close_dev(dd);
+
+       if (err < 0) {
+               err = errno;
+               fprintf(stderr, "Can't add to white list: %s(%d)\n",
+                                                       strerror(err), err);
+               exit(1);
+       }
+}
+
+static struct option lewlrm_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lewlrm_help =
+       "Usage:\n"
+       "\tlewlrm <bdaddr>\n";
+
+static void cmd_lewlrm(int dev_id, int argc, char **argv)
+{
+       int err, opt, dd;
+       bdaddr_t bdaddr;
+
+       for_each_opt(opt, lewlrm_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", lewlrm_help);
+                       return;
+               }
+       }
+
+       helper_arg(1, 1, &argc, &argv, lewlrm_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Could not open device");
+               exit(1);
+       }
+
+       str2ba(argv[0], &bdaddr);
+
+       err = hci_le_rm_white_list(dd, &bdaddr, LE_PUBLIC_ADDRESS, 1000);
+       hci_close_dev(dd);
+
+       if (err < 0) {
+               err = errno;
+               fprintf(stderr, "Can't remove from white list: %s(%d)\n",
+                                                       strerror(err), err);
+               exit(1);
+       }
+}
+
+static struct option lewlsz_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lewlsz_help =
+       "Usage:\n"
+       "\tlewlsz\n";
+
+static void cmd_lewlsz(int dev_id, int argc, char **argv)
+{
+       int err, dd, opt;
+       uint8_t size;
+
+       for_each_opt(opt, lewlsz_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", lewlsz_help);
+                       return;
+               }
+       }
+
+       helper_arg(0, 0, &argc, &argv, lewlsz_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Could not open device");
+               exit(1);
+       }
+
+       err = hci_le_read_white_list_size(dd, &size, 1000);
+       hci_close_dev(dd);
+
+       if (err < 0) {
+               err = errno;
+               fprintf(stderr, "Can't read white list size: %s(%d)\n",
+                                                       strerror(err), err);
+               exit(1);
+       }
+
+       printf("White list size: %d\n", size);
+}
+
+static struct option lewlclr_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lewlclr_help =
+       "Usage:\n"
+       "\tlewlclr\n";
+
+static void cmd_lewlclr(int dev_id, int argc, char **argv)
+{
+       int err, dd, opt;
+
+       for_each_opt(opt, lewlclr_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", lewlclr_help);
+                       return;
+               }
+       }
+
+       helper_arg(0, 0, &argc, &argv, lewlclr_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Could not open device");
+               exit(1);
+       }
+
+       err = hci_le_clear_white_list(dd, 1000);
+       hci_close_dev(dd);
+
+       if (err < 0) {
+               err = errno;
+               fprintf(stderr, "Can't clear white list: %s(%d)\n",
+                                                       strerror(err), err);
+               exit(1);
+       }
+}
+
+static struct option ledc_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *ledc_help =
+       "Usage:\n"
+       "\tledc <handle> [reason]\n";
+
+static void cmd_ledc(int dev_id, int argc, char **argv)
+{
+       int err, opt, dd;
+       uint16_t handle;
+       uint8_t reason;
+
+       for_each_opt(opt, ledc_options, NULL) {
+               switch (opt) {
+               default:
+                       printf("%s", ledc_help);
+                       return;
+               }
+       }
+       helper_arg(1, 2, &argc, &argv, ledc_help);
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               perror("Could not open device");
+               exit(1);
+       }
+
+       handle = atoi(argv[0]);
+
+       reason = (argc > 1) ? atoi(argv[1]) : HCI_OE_USER_ENDED_CONNECTION;
+
+       err = hci_disconnect(dd, handle, reason, 10000);
+       if (err < 0) {
+               perror("Could not disconnect");
+               exit(1);
+       }
+
+       hci_close_dev(dd);
+}
+
+static struct option lecup_options[] = {
+       { "help",       0, 0, 'h' },
+       { "handle",     1, 0, 'H' },
+       { "min",        1, 0, 'm' },
+       { "max",        1, 0, 'M' },
+       { "latency",    1, 0, 'l' },
+       { "timeout",    1, 0, 't' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *lecup_help =
+       "Usage:\n"
+       "\tlecup <handle> <min> <max> <latency> <timeout>\n"
+       "\tOptions:\n"
+       "\t    -H, --handle <0xXXXX>  LE connection handle\n"
+       "\t    -m, --min <interval>   Range: 0x0006 to 0x0C80\n"
+       "\t    -M, --max <interval>   Range: 0x0006 to 0x0C80\n"
+       "\t    -l, --latency <range>  Slave latency. Range: 0x0000 to 0x03E8\n"
+       "\t    -t, --timeout  <time>  N * 10ms. Range: 0x000A to 0x0C80\n"
+       "\n\t min/max range: 7.5ms to 4s. Multiply factor: 1.25ms"
+       "\n\t timeout range: 100ms to 32.0s. Larger than max interval\n";
+
+static void cmd_lecup(int dev_id, int argc, char **argv)
+{
+       uint16_t handle = 0, min, max, latency, timeout;
+       int opt, dd, base;
+
+       /* Aleatory valid values */
+       min = 0x0C8;
+       max = 0x0960;
+       latency = 0x0007;
+       timeout = 0x0C80;
+
+       for_each_opt(opt, lecup_options, NULL) {
+               if (optarg && strncasecmp("0x", optarg, 2) == 0)
+                       base = 16;
+               else
+                       base = 10;
+
+               switch (opt) {
+               case 'H':
+                       handle = strtoul(optarg, NULL, base);
+                       break;
+               case 'm':
+                       min = strtoul(optarg, NULL, base);
+                       break;
+               case 'M':
+                       max = strtoul(optarg, NULL, base);
+                       break;
+               case 'l':
+                       latency = strtoul(optarg, NULL, base);
+                       break;
+               case 't':
+                       timeout = strtoul(optarg, NULL, base);
+                       break;
+               default:
+                       printf("%s", lecup_help);
+                       return;
+               }
+       }
+
+       if (handle == 0) {
+               printf("%s", lecup_help);
+               return;
+       }
+
+       if (dev_id < 0)
+               dev_id = hci_get_route(NULL);
+
+       dd = hci_open_dev(dev_id);
+       if (dd < 0) {
+               fprintf(stderr, "HCI device open failed\n");
+               exit(1);
+       }
+
+       if (hci_le_conn_update(dd, htobs(handle), htobs(min), htobs(max),
+                               htobs(latency), htobs(timeout), 5000) < 0) {
+               int err = errno;
+               fprintf(stderr, "Could not change connection params: %s(%d)\n",
+                                                       strerror(err), err);
+       }
+
+       hci_close_dev(dd);
+}
+
+static struct {
+       char *cmd;
+       void (*func)(int dev_id, int argc, char **argv);
+       char *doc;
+} command[] = {
+       { "dev",      cmd_dev,     "Display local devices"                },
+       { "inq",      cmd_inq,     "Inquire remote devices"               },
+       { "scan",     cmd_scan,    "Scan for remote devices"              },
+       { "name",     cmd_name,    "Get name from remote device"          },
+       { "info",     cmd_info,    "Get information from remote device"   },
+       { "spinq",    cmd_spinq,   "Start periodic inquiry"               },
+       { "epinq",    cmd_epinq,   "Exit periodic inquiry"                },
+       { "cmd",      cmd_cmd,     "Submit arbitrary HCI commands"        },
+       { "con",      cmd_con,     "Display active connections"           },
+       { "cc",       cmd_cc,      "Create connection to remote device"   },
+       { "dc",       cmd_dc,      "Disconnect from remote device"        },
+       { "sr",       cmd_sr,      "Switch master/slave role"             },
+       { "cpt",      cmd_cpt,     "Change connection packet type"        },
+       { "rssi",     cmd_rssi,    "Display connection RSSI"              },
+       { "lq",       cmd_lq,      "Display link quality"                 },
+       { "tpl",      cmd_tpl,     "Display transmit power level"         },
+       { "afh",      cmd_afh,     "Display AFH channel map"              },
+       { "lp",       cmd_lp,      "Set/display link policy settings"     },
+       { "lst",      cmd_lst,     "Set/display link supervision timeout" },
+       { "auth",     cmd_auth,    "Request authentication"               },
+       { "enc",      cmd_enc,     "Set connection encryption"            },
+       { "key",      cmd_key,     "Change connection link key"           },
+       { "clkoff",   cmd_clkoff,  "Read clock offset"                    },
+       { "clock",    cmd_clock,   "Read local or remote clock"           },
+       { "lescan",   cmd_lescan,  "Start LE scan"                        },
+       { "lewladd",  cmd_lewladd, "Add device to LE White List"          },
+       { "lewlrm",   cmd_lewlrm,  "Remove device from LE White List"     },
+       { "lewlsz",   cmd_lewlsz,  "Read size of LE White List"           },
+       { "lewlclr",  cmd_lewlclr, "Clear LE White list"                  },
+       { "lecc",     cmd_lecc,    "Create a LE Connection"               },
+       { "ledc",     cmd_ledc,    "Disconnect a LE Connection"           },
+       { "lecup",    cmd_lecup,   "LE Connection Update"                 },
+       { NULL, NULL, 0 }
+};
+
+static void usage(void)
+{
+       int i;
+
+       printf("hcitool - HCI Tool ver %s\n", VERSION);
+       printf("Usage:\n"
+               "\thcitool [options] <command> [command parameters]\n");
+       printf("Options:\n"
+               "\t--help\tDisplay help\n"
+               "\t-i dev\tHCI device\n");
+       printf("Commands:\n");
+       for (i = 0; command[i].cmd; i++)
+               printf("\t%-4s\t%s\n", command[i].cmd,
+               command[i].doc);
+       printf("\n"
+               "For more information on the usage of each command use:\n"
+               "\thcitool <command> --help\n" );
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "device",     1, 0, 'i' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       int opt, i, dev_id = -1;
+       bdaddr_t ba;
+
+       while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'i':
+                       dev_id = hci_devid(optarg);
+                       if (dev_id < 0) {
+                               perror("Invalid device");
+                               exit(1);
+                       }
+                       break;
+
+               case 'h':
+               default:
+                       usage();
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(0);
+       }
+
+       if (dev_id != -1 && hci_devba(dev_id, &ba) < 0) {
+               perror("Device is not available");
+               exit(1);
+       }
+
+       for (i = 0; command[i].cmd; i++) {
+               if (strncmp(command[i].cmd,
+                               argv[0], strlen(command[i].cmd)))
+                       continue;
+
+               command[i].func(dev_id, argc, argv);
+               break;
+       }
+
+       if (command[i].cmd == 0) {
+               fprintf(stderr, "Unknown command - \"%s\"\n", *argv);
+               exit(1);
+       }
+
+       return 0;
+}
diff --git a/tools/hid2hci.8 b/tools/hid2hci.8
new file mode 100644 (file)
index 0000000..5d35274
--- /dev/null
@@ -0,0 +1,51 @@
+.\"
+.\"    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., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.TH HID2HCI 8 "MAY 15, 2009" "" ""
+
+.SH NAME
+hid2hci \- Bluetooth HID to HCI mode switching utility
+.SH SYNOPSIS
+.BR "hid2hci
+[
+.I options
+]
+.SH DESCRIPTION
+.B hid2hci
+is used to set up switch supported Bluetooth devices into the HCI
+mode and back.
+.SH OPTIONS
+.TP
+.BI -h
+Gives a list of possible options.
+.TP
+.BI -q
+Don't display any messages.
+.TP
+.BI -r [hid,hci]
+Sets the mode to switch the device into
+.TP
+.BI -v
+Specifies the 4 digit vendor ID assigned to the device being switched
+.TP
+.BI -p
+Specifies the 4 digit product ID assigned to the device being switched
+.TP
+.BI -m [csr, logitech, dell]
+Which vendor method to use for switching the device.
+.SH AUTHOR
+Written by Marcel Holtmann <marcel@holtmann.org>.
+.br
diff --git a/tools/hid2hci.c b/tools/hid2hci.c
new file mode 100644 (file)
index 0000000..a640772
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+
+#include <usb.h>
+
+#ifdef NEED_USB_GET_BUSSES
+static inline struct usb_bus *usb_get_busses(void)
+{
+       return usb_busses;
+}
+#endif
+
+#ifndef USB_DIR_OUT
+#define USB_DIR_OUT    0x00
+#endif
+
+static char devpath[PATH_MAX + 1] = "/dev";
+
+struct hiddev_devinfo {
+       unsigned int bustype;
+       unsigned int busnum;
+       unsigned int devnum;
+       unsigned int ifnum;
+       short vendor;
+       short product;
+       short version;
+       unsigned num_applications;
+};
+
+struct hiddev_report_info {
+       unsigned report_type;
+       unsigned report_id;
+       unsigned num_fields;
+};
+
+typedef __signed__ int __s32;
+
+struct hiddev_usage_ref {
+       unsigned report_type;
+       unsigned report_id;
+       unsigned field_index;
+       unsigned usage_index;
+       unsigned usage_code;
+       __s32 value;
+};
+
+#define HIDIOCGDEVINFO         _IOR('H', 0x03, struct hiddev_devinfo)
+#define HIDIOCINITREPORT       _IO('H', 0x05)
+#define HIDIOCSREPORT          _IOW('H', 0x08, struct hiddev_report_info)
+#define HIDIOCSUSAGE           _IOW('H', 0x0C, struct hiddev_usage_ref)
+
+#define HID_REPORT_TYPE_OUTPUT 2
+
+#define HCI 0
+#define HID 1
+
+struct device_info {
+       struct usb_device *dev;
+       int mode;
+       uint16_t vendor;
+       uint16_t product;
+};
+
+static int switch_csr(struct device_info *devinfo)
+{
+       struct usb_dev_handle *udev;
+       int err;
+
+       udev = usb_open(devinfo->dev);
+       if (!udev)
+               return -errno;
+
+       err = usb_control_msg(udev, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+                               0, devinfo->mode, 0, NULL, 0, 10000);
+
+       if (err == 0) {
+               err = -1;
+               errno = EALREADY;
+       } else {
+               if (errno == ETIMEDOUT)
+                       err = 0;
+       }
+
+       usb_close(udev);
+
+       return err;
+}
+
+static int send_report(int fd, const char *buf, size_t size)
+{
+       struct hiddev_report_info rinfo;
+       struct hiddev_usage_ref uref;
+       unsigned int i;
+       int err;
+
+       for (i = 0; i < size; i++) {
+               memset(&uref, 0, sizeof(uref));
+               uref.report_type = HID_REPORT_TYPE_OUTPUT;
+               uref.report_id   = 0x10;
+               uref.field_index = 0;
+               uref.usage_index = i;
+               uref.usage_code  = 0xff000001;
+               uref.value       = buf[i] & 0x000000ff;
+               err = ioctl(fd, HIDIOCSUSAGE, &uref);
+               if (err < 0)
+                       return err;
+       }
+
+       memset(&rinfo, 0, sizeof(rinfo));
+       rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
+       rinfo.report_id   = 0x10;
+       rinfo.num_fields  = 1;
+       err = ioctl(fd, HIDIOCSREPORT, &rinfo);
+
+       return err;
+}
+
+static int switch_logitech(struct device_info *devinfo)
+{
+       char devname[PATH_MAX + 1];
+       int i, fd, err = -1;
+
+       for (i = 0; i < 16; i++) {
+               struct hiddev_devinfo dinfo;
+               char rep1[] = { 0xff, 0x80, 0x80, 0x01, 0x00, 0x00 };
+               char rep2[] = { 0xff, 0x80, 0x00, 0x00, 0x30, 0x00 };
+               char rep3[] = { 0xff, 0x81, 0x80, 0x00, 0x00, 0x00 };
+
+               sprintf(devname, "%s/hiddev%d", devpath, i);
+               fd = open(devname, O_RDWR);
+               if (fd < 0) {
+                       sprintf(devname, "%s/usb/hiddev%d", devpath, i);
+                       fd = open(devname, O_RDWR);
+                       if (fd < 0) {
+                               sprintf(devname, "%s/usb/hid/hiddev%d", devpath, i);
+                               fd = open(devname, O_RDWR);
+                               if (fd < 0)
+                                       continue;
+                       }
+               }
+
+               memset(&dinfo, 0, sizeof(dinfo));
+               err = ioctl(fd, HIDIOCGDEVINFO, &dinfo);
+               if (err < 0 || (int) dinfo.busnum != atoi(devinfo->dev->bus->dirname) ||
+                               (int) dinfo.devnum != atoi(devinfo->dev->filename)) {
+                       close(fd);
+                       continue;
+               }
+
+               err = ioctl(fd, HIDIOCINITREPORT, 0);
+               if (err < 0) {
+                       close(fd);
+                       break;
+               }
+
+               err = send_report(fd, rep1, sizeof(rep1));
+               if (err < 0) {
+                       close(fd);
+                       break;
+               }
+
+               err = send_report(fd, rep2, sizeof(rep2));
+               if (err < 0) {
+                       close(fd);
+                       break;
+               }
+
+               err = send_report(fd, rep3, sizeof(rep3));
+               close(fd);
+               break;
+       }
+
+       return err;
+}
+
+static int switch_dell(struct device_info *devinfo)
+{
+       char report[] = { 0x7f, 0x00, 0x00, 0x00 };
+
+       struct usb_dev_handle *handle;
+       int err;
+
+       switch (devinfo->mode) {
+       case HCI:
+               report[1] = 0x13;
+               break;
+       case HID:
+               report[1] = 0x14;
+               break;
+       }
+
+       handle = usb_open(devinfo->dev);
+       if (!handle)
+               return -EIO;
+
+       /* Don't need to check return, as might not be in use */
+       usb_detach_kernel_driver_np(handle, 0);
+
+       if (usb_claim_interface(handle, 0) < 0) {
+               usb_close(handle);
+               return -EIO;
+       }
+
+       err = usb_control_msg(handle,
+               USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       USB_REQ_SET_CONFIGURATION, 0x7f | (0x03 << 8), 0,
+                                               report, sizeof(report), 5000);
+
+       if (err == 0) {
+               err = -1;
+               errno = EALREADY;
+       } else {
+               if (errno == ETIMEDOUT)
+                       err = 0;
+       }
+
+       usb_close(handle);
+
+       return err;
+}
+
+static int find_device(struct device_info* devinfo)
+{
+       struct usb_bus *bus;
+       struct usb_device *dev;
+
+       usb_find_busses();
+       usb_find_devices();
+
+       for (bus = usb_get_busses(); bus; bus = bus->next)
+               for (dev = bus->devices; dev; dev = dev->next) {
+                       if (dev->descriptor.idVendor == devinfo->vendor &&
+                           dev->descriptor.idProduct == devinfo->product) {
+                               devinfo->dev=dev;
+                               return 1;
+                       }
+               }
+       return 0;
+}
+
+static void usage(char* error)
+{
+       if (error)
+               fprintf(stderr,"\n%s\n", error);
+       else
+               printf("hid2hci - Bluetooth HID to HCI mode switching utility\n\n");
+
+       printf("Usage:\n"
+               "\thid2hci [options]\n"
+               "\n");
+
+       printf("Options:\n"
+               "\t-h, --help           Display help\n"
+               "\t-q, --quiet          Don't display any messages\n"
+               "\t-r, --mode=          Mode to switch to [hid, hci]\n"
+               "\t-v, --vendor=        Vendor ID to act upon\n"
+               "\t-p, --product=       Product ID to act upon\n"
+               "\t-m, --method=        Method to use to switch [csr, logitech, dell]\n"
+               "\n");
+       if (error)
+               exit(1);
+}
+
+static struct option main_options[] = {
+       { "help",       no_argument, 0, 'h' },
+       { "quiet",      no_argument, 0, 'q' },
+       { "mode",       required_argument, 0, 'r' },
+       { "vendor",     required_argument, 0, 'v' },
+       { "product",    required_argument, 0, 'p' },
+       { "method",     required_argument, 0, 'm' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       struct device_info dev = { NULL, HCI, 0, 0 };
+       int opt, quiet = 0;
+       int (*method)(struct device_info *dev) = NULL;
+
+       while ((opt = getopt_long(argc, argv, "+r:v:p:m:qh", main_options, NULL)) != -1) {
+               switch (opt) {
+               case 'r':
+                       if (optarg && !strcmp(optarg, "hid"))
+                               dev.mode = HID;
+                       else if (optarg && !strcmp(optarg, "hci"))
+                               dev.mode = HCI;
+                       else
+                               usage("ERROR: Undefined radio mode\n");
+                       break;
+               case 'v':
+                       sscanf(optarg, "%4hx", &dev.vendor);
+                       break;
+               case 'p':
+                       sscanf(optarg, "%4hx", &dev.product);
+                       break;
+               case 'm':
+                       if (optarg && !strcmp(optarg, "csr"))
+                               method = switch_csr;
+                       else if (optarg && !strcmp(optarg, "logitech"))
+                               method = switch_logitech;
+                       else if (optarg && !strcmp(optarg, "dell"))
+                               method = switch_dell;
+                       else
+                               usage("ERROR: Undefined switching method\n");
+                       break;
+               case 'q':
+                       quiet = 1;
+                       break;
+               case 'h':
+                       usage(NULL);
+               default:
+                       exit(0);
+               }
+       }
+
+       if (!quiet && (!dev.vendor || !dev.product || !method))
+               usage("ERROR: Vendor ID, Product ID, and Switching Method must all be defined.\n");
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       usb_init();
+
+       if (!find_device(&dev)) {
+               if (!quiet)
+                       fprintf(stderr, "Device %04x:%04x not found on USB bus.\n",
+                               dev.vendor, dev.product);
+               exit(1);
+       }
+
+       if (!quiet)
+               printf("Attempting to switch device %04x:%04x to %s mode ",
+                       dev.vendor, dev.product, dev.mode ? "HID" : "HCI");
+       fflush(stdout);
+
+       if (method(&dev) < 0 && !quiet)
+               printf("failed (%s)\n", strerror(errno));
+       else if (!quiet)
+               printf("was successful\n");
+
+       return errno;
+}
diff --git a/tools/kword.c b/tools/kword.c
new file mode 100644 (file)
index 0000000..62e24fe
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+#include "kword.h"
+#include "parser.h"
+
+int lineno;
+
+struct keyword_t rfcomm_keyword[] = {
+       { "bind",       K_BIND          },
+       { "device",     K_DEVICE        },
+       { "channel",    K_CHANNEL       },
+       { "comment",    K_COMMENT       },
+
+       { "yes",        K_YES           },
+       { "no",         K_NO            },
+       { "enable",     K_YES           },
+       { "disable",    K_NO            },
+
+       { NULL , 0 }
+};
+
+int rfcomm_find_keyword(struct keyword_t *keyword, char *string)
+{
+       while (keyword->string) {
+               if (!strcmp(string, keyword->string))
+                       return keyword->type;
+               keyword++;
+       }
+
+       return -1;
+}
+
+struct rfcomm_opts rfcomm_opts[RFCOMM_MAX_DEV];
diff --git a/tools/kword.h b/tools/kword.h
new file mode 100644 (file)
index 0000000..81a2a88
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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
+ *
+ */
+
+extern int lineno;
+
+struct keyword_t {
+       char *string;
+       int type;
+};
+
+extern struct keyword_t rfcomm_keyword[];
+
+int rfcomm_find_keyword(struct keyword_t *keyword, char *string);
+
+#define MAXCOMMENTLEN  100
+
+struct rfcomm_opts {
+       int bind;
+       bdaddr_t bdaddr;
+       int channel;
+       char comment[MAXCOMMENTLEN + 1];
+};
+
+extern struct rfcomm_opts rfcomm_opts[RFCOMM_MAX_DEV];
+
+int rfcomm_read_config(char *filename);
diff --git a/tools/l2ping.8 b/tools/l2ping.8
new file mode 100644 (file)
index 0000000..8b77ee2
--- /dev/null
@@ -0,0 +1,76 @@
+.TH L2PING 8 "Jan 22 2002" BlueZ "Linux System Administration"
+.SH NAME
+l2ping \- Send L2CAP echo request and receive answer
+.SH SYNOPSIS
+.B l2ping
+.RB [\| \-i
+.IR <hciX> \|]
+.RB [\| \-s
+.IR size \|]
+.RB [\| \-c
+.IR count \|]
+.RB [\| \-t
+.IR timeout \|]
+.RB [\| \-d
+.IR delay \|]
+.RB [\| \-f \|]
+.RB [\| \-r \|]
+.RB [\| \-v \|]
+.I bd_addr
+
+.SH DESCRIPTION
+.LP
+L2ping sends a L2CAP echo request to the Bluetooth MAC address
+.I bd_addr
+given in dotted hex notation.
+.SH OPTIONS
+.TP
+.BI \-i " <hciX>"
+The command is applied to device
+.BI
+hciX
+, which must be the name of an installed Bluetooth device (X = 0, 1, 2, ...)
+If not specified, the command will be sent to the first available Bluetooth
+device.
+.TP
+.BI \-s " size"
+The
+.I size
+of the data packets to be sent.
+.TP
+.BI \-c " count"
+Send
+.I count
+number of packets then exit.
+.TP
+.BI \-t " timeout"
+Wait
+.I timeout
+seconds for the response.
+.TP
+.BI \-d " delay"
+Wait
+.I delay
+seconds between pings.
+.TP
+.B \-f
+Kind of flood ping. Use with care! It reduces the delay time between packets
+to 0.
+.TP
+.B \-r
+Reverse ping (gnip?). Send echo response instead of echo request.
+.TP
+.B \-v
+Verify response payload is identical to request payload. It is not required for
+remote stacks to return the request payload, but most stacks do (including
+Bluez).
+.TP
+.I bd_addr
+The Bluetooth MAC address to be pinged in dotted hex notation like
+.B 01:02:03:ab:cd:ef
+or
+.B 01:EF:cd:aB:02:03
+.SH AUTHORS
+Written by Maxim Krasnyansky <maxk@qualcomm.com> and Marcel Holtmann <marcel@holtmann.org>
+.PP
+man page by Nils Faerber <nils@kernelconcepts.de>, Adam Laurie <adam@algroup.co.uk>.
diff --git a/tools/l2ping.c b/tools/l2ping.c
new file mode 100644 (file)
index 0000000..29fb3d0
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+
+/* Defaults */
+static bdaddr_t bdaddr;
+static int size    = 44;
+static int ident   = 200;
+static int delay   = 1;
+static int count   = -1;
+static int timeout = 10;
+static int reverse = 0;
+static int verify = 0;
+
+/* Stats */
+static int sent_pkt = 0;
+static int recv_pkt = 0;
+
+static float tv2fl(struct timeval tv)
+{
+       return (float)(tv.tv_sec*1000.0) + (float)(tv.tv_usec/1000.0);
+}
+
+static void stat(int sig)
+{
+       int loss = sent_pkt ? (float)((sent_pkt-recv_pkt)/(sent_pkt/100.0)) : 0;
+       printf("%d sent, %d received, %d%% loss\n", sent_pkt, recv_pkt, loss);
+       exit(0);
+}
+
+static void ping(char *svr)
+{
+       struct sigaction sa;
+       struct sockaddr_l2 addr;
+       socklen_t optlen;
+       unsigned char *send_buf;
+       unsigned char *recv_buf;
+       char str[18];
+       int i, sk, lost;
+       uint8_t id;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = stat;
+       sigaction(SIGINT, &sa, NULL);
+
+       send_buf = malloc(L2CAP_CMD_HDR_SIZE + size);
+       recv_buf = malloc(L2CAP_CMD_HDR_SIZE + size);
+       if (!send_buf || !recv_buf) {
+               perror("Can't allocate buffer");
+               exit(1);
+       }
+
+       /* Create socket */
+       sk = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
+       if (sk < 0) {
+               perror("Can't create socket");
+               goto error;
+       }
+
+       /* Bind to local address */
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       bacpy(&addr.l2_bdaddr, &bdaddr);
+
+       if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("Can't bind socket");
+               goto error;
+       }
+
+       /* Connect to remote device */
+       memset(&addr, 0, sizeof(addr));
+       addr.l2_family = AF_BLUETOOTH;
+       str2ba(svr, &addr.l2_bdaddr);
+
+       if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               perror("Can't connect");
+               goto error;
+       }
+
+       /* Get local address */
+       memset(&addr, 0, sizeof(addr));
+       optlen = sizeof(addr);
+
+       if (getsockname(sk, (struct sockaddr *) &addr, &optlen) < 0) {
+               perror("Can't get local address");
+               goto error;
+       }
+
+       ba2str(&addr.l2_bdaddr, str);
+       printf("Ping: %s from %s (data size %d) ...\n", svr, str, size);
+
+       /* Initialize send buffer */
+       for (i = 0; i < size; i++)
+               send_buf[L2CAP_CMD_HDR_SIZE + i] = (i % 40) + 'A';
+
+       id = ident;
+
+       while (count == -1 || count-- > 0) {
+               struct timeval tv_send, tv_recv, tv_diff;
+               l2cap_cmd_hdr *send_cmd = (l2cap_cmd_hdr *) send_buf;
+               l2cap_cmd_hdr *recv_cmd = (l2cap_cmd_hdr *) recv_buf;
+
+               /* Build command header */
+               send_cmd->ident = id;
+               send_cmd->len   = htobs(size);
+
+               if (reverse)
+                       send_cmd->code = L2CAP_ECHO_RSP;
+               else
+                       send_cmd->code = L2CAP_ECHO_REQ;
+
+               gettimeofday(&tv_send, NULL);
+
+               /* Send Echo Command */
+               if (send(sk, send_buf, L2CAP_CMD_HDR_SIZE + size, 0) <= 0) {
+                       perror("Send failed");
+                       goto error;
+               }
+
+               /* Wait for Echo Response */
+               lost = 0;
+               while (1) {
+                       struct pollfd pf[1];
+                       int err;
+
+                       pf[0].fd = sk;
+                       pf[0].events = POLLIN;
+
+                       if ((err = poll(pf, 1, timeout * 1000)) < 0) {
+                               perror("Poll failed");
+                               goto error;
+                       }
+
+                       if (!err) {
+                               lost = 1;
+                               break;
+                       }
+
+                       if ((err = recv(sk, recv_buf, L2CAP_CMD_HDR_SIZE + size, 0)) < 0) {
+                               perror("Recv failed");
+                               goto error;
+                       }
+
+                       if (!err){
+                               printf("Disconnected\n");
+                               goto error;
+                       }
+
+                       recv_cmd->len = btohs(recv_cmd->len);
+
+                       /* Check for our id */
+                       if (recv_cmd->ident != id)
+                               continue;
+
+                       /* Check type */
+                       if (!reverse && recv_cmd->code == L2CAP_ECHO_RSP)
+                               break;
+
+                       if (recv_cmd->code == L2CAP_COMMAND_REJ) {
+                               printf("Peer doesn't support Echo packets\n");
+                               goto error;
+                       }
+
+               }
+               sent_pkt++;
+
+               if (!lost) {
+                       recv_pkt++;
+
+                       gettimeofday(&tv_recv, NULL);
+                       timersub(&tv_recv, &tv_send, &tv_diff);
+
+                       if (verify) {
+                               /* Check payload length */
+                               if (recv_cmd->len != size) {
+                                       fprintf(stderr, "Received %d bytes, expected %d\n",
+                                                  recv_cmd->len, size);
+                                       goto error;
+                               }
+
+                               /* Check payload */
+                               if (memcmp(&send_buf[L2CAP_CMD_HDR_SIZE],
+                                                  &recv_buf[L2CAP_CMD_HDR_SIZE], size)) {
+                                       fprintf(stderr, "Response payload different.\n");
+                                       goto error;
+                               }
+                       }
+
+                       printf("%d bytes from %s id %d time %.2fms\n", recv_cmd->len, svr,
+                                  id - ident, tv2fl(tv_diff));
+
+                       if (delay)
+                               sleep(delay);
+               } else {
+                       printf("no response from %s: id %d\n", svr, id - ident);
+               }
+
+               if (++id > 254)
+                       id = ident;
+       }
+       stat(0);
+       free(send_buf);
+       free(recv_buf);
+       return;
+
+error:
+       close(sk);
+       free(send_buf);
+       free(recv_buf);
+       exit(1);
+}
+
+static void usage(void)
+{
+       printf("l2ping - L2CAP ping\n");
+       printf("Usage:\n");
+       printf("\tl2ping [-i device] [-s size] [-c count] [-t timeout] [-d delay] [-f] [-r] [-v] <bdaddr>\n");
+       printf("\t-f  Flood ping (delay = 0)\n");
+       printf("\t-r  Reverse ping\n");
+       printf("\t-v  Verify request and response payload\n");
+}
+
+int main(int argc, char *argv[])
+{
+       int opt;
+
+       /* Default options */
+       bacpy(&bdaddr, BDADDR_ANY);
+
+       while ((opt=getopt(argc,argv,"i:d:s:c:t:frv")) != EOF) {
+               switch(opt) {
+               case 'i':
+                       if (!strncasecmp(optarg, "hci", 3))
+                               hci_devba(atoi(optarg + 3), &bdaddr);
+                       else
+                               str2ba(optarg, &bdaddr);
+                       break;
+
+               case 'd':
+                       delay = atoi(optarg);
+                       break;
+
+               case 'f':
+                       /* Kinda flood ping */
+                       delay = 0;
+                       break;
+
+               case 'r':
+                       /* Use responses instead of requests */
+                       reverse = 1;
+                       break;
+
+               case 'v':
+                       verify = 1;
+                       break;
+
+               case 'c':
+                       count = atoi(optarg);
+                       break;
+
+               case 't':
+                       timeout = atoi(optarg);
+                       break;
+
+               case 's':
+                       size = atoi(optarg);
+                       break;
+
+               default:
+                       usage();
+                       exit(1);
+               }
+       }
+
+       if (!(argc - optind)) {
+               usage();
+               exit(1);
+       }
+
+       ping(argv[optind]);
+
+       return 0;
+}
diff --git a/tools/lexer.l b/tools/lexer.l
new file mode 100644 (file)
index 0000000..ff9ce81
--- /dev/null
@@ -0,0 +1,120 @@
+%{
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+/* Nasty workaround, but flex defines isatty() twice */
+#define _UNISTD_H
+
+#include <stdio.h>
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+#include "kword.h"
+#include "parser.h"
+
+int yylex(void);
+
+#define YY_NO_INPUT
+
+#define ECHO {;}
+#define YY_DECL int yylex(void)
+
+int yyerror(char *str);
+
+%}
+
+%option nounput
+
+space          [ \t]
+linebreak      \n
+comment                \#.*\n
+keyword                [A-Za-z0-9\_\-]+
+
+number         [0-9]+
+string         \".*\"
+bdaddr         [A-Za-z0-9]{2}:[A-Za-z0-9]{2}:[A-Za-z0-9]{2}:[A-Za-z0-9]{2}:[A-Za-z0-9]{2}:[A-Za-z0-9]{2}
+
+%%
+
+{space}                {
+                       /* Skip spaces and tabs */
+                       ;
+               }
+
+{comment}      {
+                       /* Skip comments */
+                       lineno++; 
+               }
+
+{number}       {
+                       yylval.number = atoi(yytext);
+                       return NUMBER;
+               }
+
+{string}       {
+                       yylval.string = yytext;
+                       return STRING;
+               }
+
+{bdaddr}       {
+                       bdaddr_t *ba = malloc(sizeof(bdaddr_t));
+                       str2ba(yytext, ba);
+                       yylval.bdaddr = ba;
+                       return BDADDR;
+               }
+
+{keyword}      {
+                       int keyword = rfcomm_find_keyword(rfcomm_keyword, yytext);
+                       if (keyword != -1)
+                               return keyword;
+
+                       if (strncmp(yytext, "rfcomm", 6) == 0) {
+                               yylval.number = atoi(yytext + 6);
+                               return RFCOMM;
+                       }
+
+                       yylval.string = yytext;
+                       return WORD;
+               }
+
+{linebreak}    {
+                       lineno++;
+               }
+
+.              {
+                       return *yytext;
+               }
+
+%%
+
+int yywrap(void) 
+{
+       return 1;
+}
diff --git a/tools/parser.y b/tools/parser.y
new file mode 100644 (file)
index 0000000..96e6a56
--- /dev/null
@@ -0,0 +1,171 @@
+%{
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+#include "kword.h"
+
+int yylex(void);
+int yyerror(char *s); 
+
+struct rfcomm_opts *opts;
+
+%}
+
+%union {
+       int number;
+       char *string;
+       bdaddr_t *bdaddr;
+}
+
+%token K_BIND K_DEVICE K_CHANNEL K_COMMENT
+%token K_YES K_NO
+
+%token <number> NUMBER RFCOMM
+%token <string> STRING WORD
+%token <bdaddr> BDADDR
+
+%type <number> bool
+
+%%
+
+config         :
+               | statement
+               | config statement
+               ;
+
+statement      : section '{' rfcomm_options '}'
+               | rfcomm  '{' rfcomm_options '}'
+               | WORD
+                       {
+                       }
+               | error
+                       {
+                               yyclearin;
+                               yyerrok;
+                       }
+               ;
+
+section                : WORD
+                       {
+                               opts = NULL;
+                       }
+               ;
+
+rfcomm         : RFCOMM
+                       {
+                               if (($1 >= 0) && ($1 < RFCOMM_MAX_DEV))
+                                       opts = &rfcomm_opts[$1];
+                               else
+                                       opts = NULL;
+                       }
+               ;
+
+rfcomm_options : rfcomm_option ';'
+               | error ';'
+               | rfcomm_options rfcomm_option ';'
+               ;
+
+rfcomm_option  : K_BIND bool
+                       {
+                               if (opts)
+                                       opts->bind = $2;
+                       }
+               | K_DEVICE BDADDR
+                       {
+                               if (opts)
+                                       bacpy(&opts->bdaddr, $2);
+                       }
+               | K_CHANNEL NUMBER
+                       {
+                               if (opts)
+                                       opts->channel = $2;
+                       }
+               | K_COMMENT STRING
+                       {
+                               if (opts)
+                                       snprintf(opts->comment, MAXCOMMENTLEN, "%s", $2);
+                       }
+               | WORD
+                       {
+                               // Unknown option
+                       }
+               ;
+
+bool           : K_YES { $$ = 1; }
+               | K_NO  { $$ = 0; }
+               ;
+
+%%
+
+int yyerror(char *s) 
+{
+       fprintf(stderr, "%s line %d\n", s, lineno);
+       return 0;
+}
+
+int rfcomm_read_config(char *filename)
+{
+       extern FILE *yyin;
+       char file[MAXPATHLEN + 1];
+       int i;
+
+       for (i = 0; i < RFCOMM_MAX_DEV; i++) {
+               rfcomm_opts[i].bind = 0;
+               bacpy(&rfcomm_opts[i].bdaddr, BDADDR_ANY);
+               rfcomm_opts[i].channel = 1;
+       }
+
+       if (filename) {
+               snprintf(file, MAXPATHLEN,  "%s", filename);
+       } else {
+               snprintf(file, MAXPATHLEN, "%s/.bluetooth/rfcomm.conf", getenv("HOME"));
+
+               if ((getuid() == 0) || (access(file, R_OK) < 0))
+                       snprintf(file, MAXPATHLEN, "%s/rfcomm.conf", CONFIGDIR);
+       }
+
+       if (!(yyin = fopen(file, "r")))
+               return -1;
+
+       lineno = 1;
+       yyparse();
+
+       fclose(yyin);
+
+       return 0;
+}
diff --git a/tools/ppporc.c b/tools/ppporc.c
new file mode 100644 (file)
index 0000000..ca44b40
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+/* IO cancelation */
+static volatile sig_atomic_t __io_canceled;
+
+static inline void io_init(void)
+{
+       __io_canceled = 0;
+}
+
+static inline void io_cancel(void)
+{
+       __io_canceled = 1;
+}
+
+/* Signal functions */
+static void sig_hup(int sig)
+{
+       return;
+}
+
+static void sig_term(int sig)
+{
+       syslog(LOG_INFO, "Closing RFCOMM channel");
+       io_cancel();
+}
+
+/* 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;
+}
+
+/* Create the RFCOMM connection */
+static int create_connection(bdaddr_t *bdaddr, uint8_t channel)
+{
+       struct sockaddr_rc remote_addr, local_addr;
+       int fd, err;
+
+       if ((fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0)
+               return fd;
+
+       memset(&local_addr, 0, sizeof(local_addr));
+       local_addr.rc_family = AF_BLUETOOTH;
+       bacpy(&local_addr.rc_bdaddr, BDADDR_ANY);
+       if ((err = bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr))) < 0) {
+               close(fd);
+               return err;
+       }
+
+       memset(&remote_addr, 0, sizeof(remote_addr));
+       remote_addr.rc_family = AF_BLUETOOTH;
+       bacpy(&remote_addr.rc_bdaddr, bdaddr);
+       remote_addr.rc_channel = channel;
+       if ((err = connect(fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr))) < 0) {
+               close(fd);
+               return err;
+       }
+
+       syslog(LOG_INFO, "RFCOMM channel %d connected", channel);
+
+       return fd;
+}
+
+/* Process the data from socket and pseudo tty */
+static int process_data(int fd)
+{
+       struct pollfd p[2];
+       char buf[1024];
+       int err, r;
+
+       p[0].fd = 0;
+       p[0].events = POLLIN | POLLERR | POLLHUP | POLLNVAL;
+
+       p[1].fd = fd;
+       p[1].events = POLLIN | POLLERR | POLLHUP | POLLNVAL;
+
+       err = 0;
+
+       while (!__io_canceled) {
+               p[0].revents = 0;
+               p[1].revents = 0;
+
+               err = poll(p, 2, -1);
+               if (err < 0)
+                       break;
+
+               err = 0;
+
+               if (p[0].revents) {
+                       if (p[0].revents & (POLLERR | POLLHUP | POLLNVAL))
+                         break;
+                       r = read(0, buf, sizeof(buf));
+                       if (r < 0) {
+                               if (errno != EINTR && errno != EAGAIN) {
+                                       err = r;
+                                       break;
+                               }
+                       }
+
+                       err = write_n(fd, buf, r);
+                       if (err < 0)
+                               break;
+               }
+
+               if (p[1].revents) {
+                       if (p[1].revents & (POLLERR | POLLHUP | POLLNVAL))
+                               break;
+                       r = read(fd, buf, sizeof(buf));
+                       if (r < 0) {
+                               if (errno != EINTR && errno != EAGAIN) {
+                                       err = r;
+                                       break;
+                               }
+                       }
+
+                       err = write_n(1, buf, r);
+                       if (err < 0)
+                               break;
+               }
+       }
+
+       return err;
+}
+
+static void usage(void)
+{
+       printf("Usage:\tppporc <bdaddr> [channel]\n");
+}
+
+int main(int argc, char** argv)
+{
+       struct sigaction sa;
+       int fd, err, opt;
+
+       bdaddr_t bdaddr;
+       uint8_t channel;
+
+       /* Parse command line options */
+       while ((opt = getopt(argc, argv, "h")) != EOF) {
+               switch(opt) {
+               case 'h':
+                       usage();
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       switch (argc) {
+       case 1:
+               str2ba(argv[0], &bdaddr);
+               channel = 1;
+               break;
+       case 2:
+               str2ba(argv[0], &bdaddr);
+               channel = atoi(argv[1]);
+               break;
+       default:
+               usage();
+               exit(0);
+       }
+
+       /* Initialize syslog */
+       openlog("ppporc", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
+       syslog(LOG_INFO, "PPP over RFCOMM");
+
+       /* 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);
+
+       syslog(LOG_INFO, "Connecting to %s", argv[0]);
+
+       if ((fd = create_connection(&bdaddr, channel)) < 0) {
+               syslog(LOG_ERR, "Can't connect to remote device (%s)", strerror(errno));
+               return fd;
+       }
+
+       err = process_data(fd);
+
+       close(fd);
+
+       return err;
+}
diff --git a/tools/rfcomm.1 b/tools/rfcomm.1
new file mode 100644 (file)
index 0000000..06252e5
--- /dev/null
@@ -0,0 +1,137 @@
+.\"
+.\"    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., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.TH RFCOMM 1 "APRIL 28, 2002" "" ""
+
+.SH NAME
+rfcomm \- RFCOMM configuration utility
+.SH SYNOPSIS
+.BR "rfcomm
+[
+.I options
+] <
+.I command
+> <
+.I dev
+>
+.SH DESCRIPTION
+.B rfcomm
+is used to set up, maintain, and inspect the RFCOMM configuration
+of the Bluetooth subsystem in the Linux kernel. If no
+.B command
+is given, or if the option
+.B -a
+is used,
+.B rfcomm
+prints information about the configured RFCOMM devices.
+.SH OPTIONS
+.TP
+.BI -h
+Gives a list of possible commands.
+.TP
+.BI -a
+Prints information about all configured RFCOMM devices.
+.TP
+.BI -r
+Switch TTY into raw mode (doesn't work with "bind").
+.TP
+.BI -f " <file>"
+Specify alternate config file.
+.TP
+.BI -i " <hciX> | <bdaddr>"
+The command is applied to device
+.BI -A
+Enable authentication.
+.BI -E
+Enable encryption.
+.BI -S
+Secure connection.
+.BI -M
+Become the master of a piconet.
+.I
+hciX
+, which must be the name or the address of an installed Bluetooth
+device. If not specified, the command will be use the first
+available Bluetooth device.
+.TP
+.BI -A
+Enable authentification
+.TP
+.BI -E
+Enable encryption
+.TP
+.BI -S
+Secure connection
+.TP
+.BI -M
+Become the master of a piconet
+.TP
+.BI -L " <seconds>"
+Set linger timeout
+.SH COMMANDS
+.TP
+.BI show " <dev>"
+Display the information about the specified device.
+.TP
+.BI connect " <dev> [bdaddr] [channel]"
+Connect the RFCOMM device to the remote Bluetooth device on the
+specified channel. If no channel is specified, it will use the
+channel number 1. If also the Bluetooth address is left out, it
+tries to read the data from the config file. This command can
+be terminated with the key sequence CTRL-C.
+.TP
+.BI listen " <dev> [channel] [cmd]"
+Listen on a specified RFCOMM channel for incoming connections.
+If no channel is specified, it will use the channel number 1, but
+a channel must be specified before cmd. If cmd is given, it will be
+executed as soon as a client connects. When the child process
+terminates or the client disconnect, the command will terminate.
+Occurences of {} in cmd will be replaced by the name of the device
+used by the connection. This command can be terminated with the key
+sequence CTRL-C.
+.TP
+.BI watch " <dev> [channel] [cmd]"
+Watch is identical to
+.B listen
+except that when the child process terminates or the client
+disconnect, the command will restart listening with the same
+parameters.
+.TP
+.BI bind " <dev> [bdaddr] [channel]"
+This binds the RFCOMM device to a remote Bluetooth device. The
+command did not establish a connection to the remote device, it
+only creates the binding. The connection will be established right
+after an application tries to open the RFCOMM device. If no channel
+number is specified, it uses the channel number 1. If the Bluetooth
+address is also left out, it tries to read the data from the config
+file.
+
+If
+.B all
+is specified for the RFCOMM device, then all devices that have
+.B "bind yes"
+set in the config will be bound.
+.TP
+.BI release " <dev>"
+This command releases a defined RFCOMM binding.
+
+If
+.B all
+is specified for the RFCOMM device, then all bindings will be removed.
+This command didn't care about the settings in the config file.
+.SH AUTHOR
+Written by Marcel Holtmann <marcel@holtmann.org>.
+.br
diff --git a/tools/rfcomm.c b/tools/rfcomm.c
new file mode 100644 (file)
index 0000000..6800445
--- /dev/null
@@ -0,0 +1,849 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <signal.h>
+#include <termios.h>
+#include <sys/poll.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+
+#include "kword.h"
+
+#ifdef NEED_PPOLL
+#include "ppoll.h"
+#endif
+
+static char *rfcomm_config_file = NULL;
+static int rfcomm_raw_tty = 0;
+static int auth = 0;
+static int encryption = 0;
+static int secure = 0;
+static int master = 0;
+static int linger = 0;
+
+static char *rfcomm_state[] = {
+       "unknown",
+       "connected",
+       "clean",
+       "bound",
+       "listening",
+       "connecting",
+       "connecting",
+       "config",
+       "disconnecting",
+       "closed"
+};
+
+static volatile sig_atomic_t __io_canceled = 0;
+
+static void sig_hup(int sig)
+{
+       return;
+}
+
+static void sig_term(int sig)
+{
+       __io_canceled = 1;
+}
+
+static char *rfcomm_flagstostr(uint32_t flags)
+{
+       static char str[100];
+       str[0] = 0;
+
+       strcat(str, "[");
+
+       if (flags & (1 << RFCOMM_REUSE_DLC))
+               strcat(str, "reuse-dlc ");
+
+       if (flags & (1 << RFCOMM_RELEASE_ONHUP))
+               strcat(str, "release-on-hup ");
+
+       if (flags & (1 << RFCOMM_TTY_ATTACHED))
+               strcat(str, "tty-attached");
+
+       strcat(str, "]");
+       return str;
+}
+
+static void print_dev_info(struct rfcomm_dev_info *di)
+{
+       char src[18], dst[18], addr[40];
+
+       ba2str(&di->src, src); ba2str(&di->dst, dst);
+
+       if (bacmp(&di->src, BDADDR_ANY) == 0)
+               sprintf(addr, "%s", dst);
+       else
+               sprintf(addr, "%s -> %s", src, dst);
+
+       printf("rfcomm%d: %s channel %d %s %s\n",
+               di->id, addr, di->channel,
+               rfcomm_state[di->state],
+               di->flags ? rfcomm_flagstostr(di->flags) : "");
+}
+
+static void print_dev_list(int ctl, int flags)
+{
+       struct rfcomm_dev_list_req *dl;
+       struct rfcomm_dev_info *di;
+       int i;
+
+       dl = malloc(sizeof(*dl) + RFCOMM_MAX_DEV * sizeof(*di));
+       if (!dl) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       dl->dev_num = RFCOMM_MAX_DEV;
+       di = dl->dev_info;
+
+       if (ioctl(ctl, RFCOMMGETDEVLIST, (void *) dl) < 0) {
+               perror("Can't get device list");
+               free(dl);
+               exit(1);
+       }
+
+       for (i = 0; i < dl->dev_num; i++)
+               print_dev_info(di + i);
+       free(dl);
+}
+
+static int create_dev(int ctl, int dev, uint32_t flags, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       struct rfcomm_dev_req req;
+       int err;
+
+       memset(&req, 0, sizeof(req));
+       req.dev_id = dev;
+       req.flags = flags;
+       bacpy(&req.src, bdaddr);
+
+       if (argc < 2) {
+               err = rfcomm_read_config(rfcomm_config_file);
+               if (err < 0) {
+                       perror("Can't open RFCOMM config file");
+                       return err;
+               }
+
+               bacpy(&req.dst, &rfcomm_opts[dev].bdaddr);
+               req.channel = rfcomm_opts[dev].channel;
+
+               if (bacmp(&req.dst, BDADDR_ANY) == 0) {
+                       fprintf(stderr, "Can't find a config entry for rfcomm%d\n", dev);
+                       return -EFAULT;
+               }
+       } else {
+               str2ba(argv[1], &req.dst);
+
+               if (argc > 2)
+                       req.channel = atoi(argv[2]);
+               else
+                       req.channel = 1;
+       }
+
+       err = ioctl(ctl, RFCOMMCREATEDEV, &req);
+       if (err == EOPNOTSUPP)
+               fprintf(stderr, "RFCOMM TTY support not available\n");
+       else if (err < 0)
+               perror("Can't create device");
+
+       return err;
+}
+
+static int create_all(int ctl)
+{
+       struct rfcomm_dev_req req;
+       int i, err;
+
+       err = rfcomm_read_config(rfcomm_config_file);
+       if (err < 0) {
+               perror("Can't open RFCOMM config file");
+               return err;
+       }
+
+       for (i = 0; i < RFCOMM_MAX_DEV; i++) {
+               if (!rfcomm_opts[i].bind)
+                       continue;
+
+               memset(&req, 0, sizeof(req));
+               req.dev_id = i;
+               req.flags = 0;
+               bacpy(&req.src, BDADDR_ANY);
+               bacpy(&req.dst, &rfcomm_opts[i].bdaddr);
+               req.channel = rfcomm_opts[i].channel;
+
+               if (bacmp(&req.dst, BDADDR_ANY) != 0)
+                       ioctl(ctl, RFCOMMCREATEDEV, &req);
+       }
+
+       return 0;
+}
+
+static int release_dev(int ctl, int dev, uint32_t flags)
+{
+       struct rfcomm_dev_req req;
+       int err;
+
+       memset(&req, 0, sizeof(req));
+       req.dev_id = dev;
+
+       err = ioctl(ctl, RFCOMMRELEASEDEV, &req);
+       if (err < 0)
+               perror("Can't release device");
+
+       return err;
+}
+
+static int release_all(int ctl)
+{
+       struct rfcomm_dev_list_req *dl;
+       struct rfcomm_dev_info *di;
+       int i;
+
+       dl = malloc(sizeof(*dl) + RFCOMM_MAX_DEV * sizeof(*di));
+       if (!dl) {
+               perror("Can't allocate memory");
+               exit(1);
+       }
+
+       dl->dev_num = RFCOMM_MAX_DEV;
+       di = dl->dev_info;
+
+       if (ioctl(ctl, RFCOMMGETDEVLIST, (void *) dl) < 0) {
+               perror("Can't get device list");
+               free(dl);
+               exit(1);
+       }
+
+       for (i = 0; i < dl->dev_num; i++)
+               release_dev(ctl, (di + i)->id, 0);
+
+       free(dl);
+       return 0;
+}
+
+static void run_cmdline(struct pollfd *p, sigset_t* sigs, char *devname,
+                       int argc, char **argv)
+{
+       int i;
+       pid_t pid;
+       char **cmdargv;
+
+       cmdargv = malloc((argc + 1) * sizeof(char*));
+       if (!cmdargv)
+               return;
+
+       for (i = 0; i < argc; i++)
+               cmdargv[i] = (strcmp(argv[i], "{}") == 0) ? devname : argv[i];
+       cmdargv[i] = NULL;
+
+       pid = fork();
+
+       switch (pid) {
+       case 0:
+               i = execvp(cmdargv[0], cmdargv);
+               fprintf(stderr, "Couldn't execute command %s (errno=%d:%s)\n",
+                               cmdargv[0], errno, strerror(errno));
+               break;
+       case -1:
+               fprintf(stderr, "Couldn't fork to execute command %s\n",
+                               cmdargv[0]);
+               break;
+       default:
+               while (1) {
+                       int status;
+                       pid_t child;
+                       struct timespec ts;
+
+                       child = waitpid(-1, &status, WNOHANG);
+                       if (child == pid || (child < 0 && errno != EAGAIN))
+                               break;
+
+                       p->revents = 0;
+                       ts.tv_sec  = 0;
+                       ts.tv_nsec = 200;
+                       if (ppoll(p, 1, &ts, sigs) || __io_canceled) {
+                               kill(pid, SIGTERM);
+                               waitpid(pid, &status, 0);
+                               break;
+                       }
+               }
+               break;
+       }
+
+       free(cmdargv);
+}
+
+static void cmd_connect(int ctl, int dev, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       struct sockaddr_rc laddr, raddr;
+       struct rfcomm_dev_req req;
+       struct termios ti;
+       struct sigaction sa;
+       struct pollfd p;
+       sigset_t sigs;
+       socklen_t alen;
+       char dst[18], devname[MAXPATHLEN];
+       int sk, fd, try = 30;
+
+       laddr.rc_family = AF_BLUETOOTH;
+       bacpy(&laddr.rc_bdaddr, bdaddr);
+       laddr.rc_channel = 0;
+
+       if (argc < 2) {
+               if (rfcomm_read_config(rfcomm_config_file) < 0) {
+                       perror("Can't open RFCOMM config file");
+                       return;
+               }
+
+               raddr.rc_family = AF_BLUETOOTH;
+               bacpy(&raddr.rc_bdaddr, &rfcomm_opts[dev].bdaddr);
+               raddr.rc_channel = rfcomm_opts[dev].channel;
+
+               if (bacmp(&raddr.rc_bdaddr, BDADDR_ANY) == 0) {
+                       fprintf(stderr, "Can't find a config entry for rfcomm%d\n", dev);
+                       return;
+               }
+       } else {
+               raddr.rc_family = AF_BLUETOOTH;
+               str2ba(argv[1], &raddr.rc_bdaddr);
+
+               if (argc > 2)
+                       raddr.rc_channel = atoi(argv[2]);
+               else
+                       raddr.rc_channel = 1;
+       }
+
+       sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+       if (sk < 0) {
+               perror("Can't create RFCOMM socket");
+               return;
+       }
+
+       if (linger) {
+               struct linger l = { .l_onoff = 1, .l_linger = linger };
+
+               if (setsockopt(sk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) {
+                       perror("Can't set linger option");
+                       return;
+               }
+       }
+
+       if (bind(sk, (struct sockaddr *) &laddr, sizeof(laddr)) < 0) {
+               perror("Can't bind RFCOMM socket");
+               close(sk);
+               return;
+       }
+
+       if (connect(sk, (struct sockaddr *) &raddr, sizeof(raddr)) < 0) {
+               perror("Can't connect RFCOMM socket");
+               close(sk);
+               return;
+       }
+
+       alen = sizeof(laddr);
+       if (getsockname(sk, (struct sockaddr *)&laddr, &alen) < 0) {
+               perror("Can't get RFCOMM socket name");
+               close(sk);
+               return;
+       }
+
+       memset(&req, 0, sizeof(req));
+       req.dev_id = dev;
+       req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP);
+
+       bacpy(&req.src, &laddr.rc_bdaddr);
+       bacpy(&req.dst, &raddr.rc_bdaddr);
+       req.channel = raddr.rc_channel;
+
+       dev = ioctl(sk, RFCOMMCREATEDEV, &req);
+       if (dev < 0) {
+               perror("Can't create RFCOMM TTY");
+               close(sk);
+               return;
+       }
+
+       snprintf(devname, MAXPATHLEN - 1, "/dev/rfcomm%d", dev);
+       while ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) {
+               if (errno == EACCES) {
+                       perror("Can't open RFCOMM device");
+                       goto release;
+               }
+
+               snprintf(devname, MAXPATHLEN - 1, "/dev/bluetooth/rfcomm/%d", dev);
+               if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) {
+                       if (try--) {
+                               snprintf(devname, MAXPATHLEN - 1, "/dev/rfcomm%d", dev);
+                               usleep(100 * 1000);
+                               continue;
+                       }
+                       perror("Can't open RFCOMM device");
+                       goto release;
+               }
+       }
+
+       if (rfcomm_raw_tty) {
+               tcflush(fd, TCIOFLUSH);
+
+               cfmakeraw(&ti);
+               tcsetattr(fd, TCSANOW, &ti);
+       }
+
+       close(sk);
+
+       ba2str(&req.dst, dst);
+       printf("Connected %s to %s on channel %d\n", devname, dst, req.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 = fd;
+       p.events = POLLERR | POLLHUP;
+
+       while (!__io_canceled) {
+               p.revents = 0;
+               if (ppoll(&p, 1, NULL, &sigs) > 0)
+                       break;
+       }
+
+       printf("Disconnected\n");
+
+       close(fd);
+       return;
+
+release:
+       memset(&req, 0, sizeof(req));
+       req.dev_id = dev;
+       req.flags = (1 << RFCOMM_HANGUP_NOW);
+       ioctl(ctl, RFCOMMRELEASEDEV, &req);
+
+       close(sk);
+}
+
+static void cmd_listen(int ctl, int dev, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       struct sockaddr_rc laddr, raddr;
+       struct rfcomm_dev_req req;
+       struct termios ti;
+       struct sigaction sa;
+       struct pollfd p;
+       sigset_t sigs;
+       socklen_t alen;
+       char dst[18], devname[MAXPATHLEN];
+       int sk, nsk, fd, lm, try = 30;
+
+       laddr.rc_family = AF_BLUETOOTH;
+       bacpy(&laddr.rc_bdaddr, bdaddr);
+       laddr.rc_channel = (argc < 2) ? 1 : atoi(argv[1]);
+
+       sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+       if (sk < 0) {
+               perror("Can't create RFCOMM socket");
+               return;
+       }
+
+       lm = 0;
+       if (master)
+               lm |= RFCOMM_LM_MASTER;
+       if (auth)
+               lm |= RFCOMM_LM_AUTH;
+       if (encryption)
+               lm |= RFCOMM_LM_ENCRYPT;
+       if (secure)
+               lm |= RFCOMM_LM_SECURE;
+
+       if (lm && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) {
+               perror("Can't set RFCOMM link mode");
+               close(sk);
+               return;
+       }
+
+       if (bind(sk, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
+               perror("Can't bind RFCOMM socket");
+               close(sk);
+               return;
+       }
+
+       printf("Waiting for connection on channel %d\n", laddr.rc_channel);
+
+       listen(sk, 10);
+
+       alen = sizeof(raddr);
+       nsk = accept(sk, (struct sockaddr *) &raddr, &alen);
+
+       alen = sizeof(laddr);
+       if (getsockname(nsk, (struct sockaddr *)&laddr, &alen) < 0) {
+               perror("Can't get RFCOMM socket name");
+               close(nsk);
+               return;
+       }
+
+       if (linger) {
+               struct linger l = { .l_onoff = 1, .l_linger = linger };
+
+               if (setsockopt(nsk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) {
+                       perror("Can't set linger option");
+                       close(nsk);
+                       return;
+               }
+       }
+
+       memset(&req, 0, sizeof(req));
+       req.dev_id = dev;
+       req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP);
+
+       bacpy(&req.src, &laddr.rc_bdaddr);
+       bacpy(&req.dst, &raddr.rc_bdaddr);
+       req.channel = raddr.rc_channel;
+
+       dev = ioctl(nsk, RFCOMMCREATEDEV, &req);
+       if (dev < 0) {
+               perror("Can't create RFCOMM TTY");
+               close(sk);
+               return;
+       }
+
+       snprintf(devname, MAXPATHLEN - 1, "/dev/rfcomm%d", dev);
+       while ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) {
+               if (errno == EACCES) {
+                       perror("Can't open RFCOMM device");
+                       goto release;
+               }
+
+               snprintf(devname, MAXPATHLEN - 1, "/dev/bluetooth/rfcomm/%d", dev);
+               if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) {
+                       if (try--) {
+                               snprintf(devname, MAXPATHLEN - 1, "/dev/rfcomm%d", dev);
+                               usleep(100 * 1000);
+                               continue;
+                       }
+                       perror("Can't open RFCOMM device");
+                       goto release;
+               }
+       }
+
+       if (rfcomm_raw_tty) {
+               tcflush(fd, TCIOFLUSH);
+
+               cfmakeraw(&ti);
+               tcsetattr(fd, TCSANOW, &ti);
+       }
+
+       close(sk);
+       close(nsk);
+
+       ba2str(&req.dst, dst);
+       printf("Connection from %s to %s\n", dst, devname);
+       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 = fd;
+       p.events = POLLERR | POLLHUP;
+
+       if (argc <= 2) {
+               while (!__io_canceled) {
+                       p.revents = 0;
+                       if (ppoll(&p, 1, NULL, &sigs) > 0)
+                               break;
+               }
+       } else
+               run_cmdline(&p, &sigs, devname, argc - 2, argv + 2);
+
+       sa.sa_handler = NULL;
+       sigaction(SIGTERM, &sa, NULL);
+       sigaction(SIGINT,  &sa, NULL);
+
+       printf("Disconnected\n");
+
+       close(fd);
+       return;
+
+release:
+       memset(&req, 0, sizeof(req));
+       req.dev_id = dev;
+       req.flags = (1 << RFCOMM_HANGUP_NOW);
+       ioctl(ctl, RFCOMMRELEASEDEV, &req);
+
+       close(sk);
+}
+
+static void cmd_watch(int ctl, int dev, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       while (!__io_canceled) {
+               cmd_listen(ctl, dev, bdaddr, argc, argv);
+               usleep(10000);
+       }
+}
+
+static void cmd_create(int ctl, int dev, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       if (strcmp(argv[0], "all") == 0)
+               create_all(ctl);
+       else
+               create_dev(ctl, dev, 0, bdaddr, argc, argv);
+}
+
+static void cmd_release(int ctl, int dev, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       if (strcmp(argv[0], "all") == 0)
+               release_all(ctl);
+       else
+               release_dev(ctl, dev, 0);
+}
+
+static void cmd_show(int ctl, int dev, bdaddr_t *bdaddr, int argc, char **argv)
+{
+       if (strcmp(argv[0], "all") == 0)
+               print_dev_list(ctl, 0);
+       else {
+               struct rfcomm_dev_info di = { id: atoi(argv[0]) };
+               if (ioctl(ctl, RFCOMMGETDEVINFO, &di) < 0) {
+                       perror("Get info failed");
+                       exit(1);
+               }
+
+               print_dev_info(&di);
+       }
+}
+
+struct {
+       char *cmd;
+       char *alt;
+       void (*func)(int ctl, int dev, bdaddr_t *bdaddr, int argc, char **argv);
+       char *opt;
+       char *doc;
+} command[] = {
+       { "bind",    "create", cmd_create,  "<dev> <bdaddr> [channel]", "Bind device"    },
+       { "release", "unbind", cmd_release, "<dev>",                    "Release device" },
+       { "show",    "info",   cmd_show,    "<dev>",                    "Show device"    },
+       { "connect", "conn",   cmd_connect, "<dev> <bdaddr> [channel]", "Connect device" },
+       { "listen",  "server", cmd_listen,  "<dev> [channel [cmd]]",    "Listen"         },
+       { "watch",   "watch",  cmd_watch,   "<dev> [channel [cmd]]",    "Watch"          },
+       { NULL, NULL, NULL, 0, 0 }
+};
+
+static void usage(void)
+{
+       int i;
+
+       printf("RFCOMM configuration utility ver %s\n", VERSION);
+
+       printf("Usage:\n"
+               "\trfcomm [options] <command> <dev>\n"
+               "\n");
+
+       printf("Options:\n"
+               "\t-i [hciX|bdaddr]      Local HCI device or BD Address\n"
+               "\t-h, --help            Display help\n"
+               "\t-r, --raw             Switch TTY into raw mode\n"
+               "\t-A, --auth            Enable authentication\n"
+               "\t-E, --encrypt         Enable encryption\n"
+               "\t-S, --secure          Secure connection\n"
+               "\t-M, --master          Become the master of a piconet\n"
+               "\t-f, --config [file]   Specify alternate config file\n"
+               "\t-a                    Show all devices (default)\n"
+               "\n");
+
+       printf("Commands:\n");
+       for (i = 0; command[i].cmd; i++)
+               printf("\t%-8s %-24s\t%s\n",
+                       command[i].cmd,
+                       command[i].opt ? command[i].opt : " ",
+                       command[i].doc);
+       printf("\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "device",     1, 0, 'i' },
+       { "config",     1, 0, 'f' },
+       { "raw",        0, 0, 'r' },
+       { "auth",       0, 0, 'A' },
+       { "encrypt",    0, 0, 'E' },
+       { "secure",     0, 0, 'S' },
+       { "master",     0, 0, 'M' },
+       { "linger",     1, 0, 'L' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       bdaddr_t bdaddr;
+       int i, opt, ctl, dev_id, show_all = 0;
+
+       bacpy(&bdaddr, BDADDR_ANY);
+
+       while ((opt = getopt_long(argc, argv, "+i:f:rahAESML:", main_options, NULL)) != -1) {
+               switch(opt) {
+               case 'i':
+                       if (strncmp(optarg, "hci", 3) == 0)
+                               hci_devba(atoi(optarg + 3), &bdaddr);
+                       else
+                               str2ba(optarg, &bdaddr);
+                       break;
+
+               case 'f':
+                       rfcomm_config_file = strdup(optarg);
+                       break;
+
+               case 'r':
+                       rfcomm_raw_tty = 1;
+                       break;
+
+               case 'a':
+                       show_all = 1;
+                       break;
+
+               case 'h':
+                       usage();
+                       exit(0);
+
+               case 'A':
+                       auth = 1;
+                       break;
+
+               case 'E':
+                       encryption = 1;
+                       break;
+
+               case 'S':
+                       secure = 1;
+                       break;
+
+               case 'M':
+                       master = 1;
+                       break;
+
+               case 'L':
+                       linger = atoi(optarg);
+                       break;
+
+               default:
+                       exit(0);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 2) {
+               if (argc != 0) {
+                       usage();
+                       exit(1);
+               } else
+                       show_all = 1;
+       }
+
+       ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM);
+       if (ctl < 0) {
+               perror("Can't open RFCOMM control socket");
+               exit(1);
+       }
+
+       if (show_all) {
+               print_dev_list(ctl, 0);
+               close(ctl);
+               exit(0);
+       }
+
+       if (strncmp(argv[1], "/dev/rfcomm", 11) == 0)
+               dev_id = atoi(argv[1] + 11);
+       else if (strncmp(argv[1], "rfcomm", 6) == 0)
+               dev_id = atoi(argv[1] + 6);
+       else
+               dev_id = atoi(argv[1]);
+
+       for (i = 0; command[i].cmd; i++) {
+               if (strncmp(command[i].cmd, argv[0], 4) && strncmp(command[i].alt, argv[0], 4))
+                       continue;
+               argc--;
+               argv++;
+               command[i].func(ctl, dev_id, &bdaddr, argc, argv);
+               close(ctl);
+               exit(0);
+       }
+
+       usage();
+
+       close(ctl);
+
+       return 0;
+}
diff --git a/tools/rfcomm.conf b/tools/rfcomm.conf
new file mode 100644 (file)
index 0000000..6179ef7
--- /dev/null
@@ -0,0 +1,17 @@
+#
+# RFCOMM configuration file.
+#
+
+#rfcomm0 {
+#      # Automatically bind the device at startup
+#      bind no;
+#
+#      # Bluetooth address of the device
+#      device 11:22:33:44:55:66;
+#
+#      # RFCOMM channel for the connection
+#      channel 1;
+#
+#      # Description of the connection
+#      comment "Example Bluetooth device";
+#}
diff --git a/tools/sdptool.1 b/tools/sdptool.1
new file mode 100644 (file)
index 0000000..0f100e2
--- /dev/null
@@ -0,0 +1,130 @@
+.\" $Header$
+.\"
+.\"    transcript compatibility for postscript use.
+.\"
+.\"    synopsis:  .P! <file.ps>
+.\"
+.de P!
+.fl
+\!!1 setgray
+.fl
+\\&.\"
+.fl
+\!!0 setgray
+.fl                    \" force out current output buffer
+\!!save /psv exch def currentpoint translate 0 0 moveto
+\!!/showpage{}def
+.fl                    \" prolog
+.sy sed -e 's/^/!/' \\$1\" bring in postscript file
+\!!psv restore
+.
+.de pF
+.ie     \a\\*(f1\a\a .ds f1 \\n(.f
+.el .ie \a\\*(f2\a\a .ds f2 \\n(.f
+.el .ie \a\\*(f3\a\a .ds f3 \\n(.f
+.el .ie \a\\*(f4\a\a .ds f4 \\n(.f
+.el .tm ? font overflow
+.ft \\$1
+..
+.de fP
+.ie     !\a\\*(f4\a\a \{\
+.      ft \\*(f4
+.      ds f4\"
+'      br \}
+.el .ie !\a\\*(f3\a\a \{\
+.      ft \\*(f3
+.      ds f3\"
+'      br \}
+.el .ie !\a\\*(f2\a\a \{\
+.      ft \\*(f2
+.      ds f2\"
+'      br \}
+.el .ie !\a\\*(f1\a\a \{\
+.      ft \\*(f1
+.      ds f1\"
+'      br \}
+.el .tm ? font underflow
+..
+.ds f1\"
+.ds f2\"
+.ds f3\"
+.ds f4\"
+'\" t
+.ta 8n 16n 24n 32n 40n 48n 56n 64n 72n
+.TH "sdptool" "1"
+.SH "NAME"
+sdptool \(em control and interrogate SDP servers
+.SH "SYNOPSIS"
+.PP
+\fBsdptool\fR [\fIoptions\fR]  {\fIcommand\fR}  [\fIcommand parameters\fR \&...]
+.SH "DESCRIPTION"
+.PP
+\fBsdptool\fR provides the interface for
+performing SDP queries on Bluetooth devices, and administering a
+local \fBsdpd\fR.
+.SH "COMMANDS"
+.PP
+The following commands are available.  In all cases \fBbdaddr\fR
+specifies the device to search or browse.  If \fIlocal\fP is used
+for \fBbdaddr\fP, then the local \fBsdpd\fR is searched.
+.PP
+Services are identified and manipulated with a 4-byte \fBrecord_handle\fP
+(NOT the service name).  To find a service's \fBrecord_handle\fP, look for the
+"Service RecHandle" line in the \fBsearch\fP or \fBbrowse\fP results
+.IP "\fBsearch [--bdaddr bdaddr] [--tree] [--raw] [--xml] service_name\fP" 10
+Search for services..
+.IP "" 10
+Known service names are DID, SP, DUN, LAN, FAX, OPUSH,
+FTP, HS, HF, HFAG, SAP, NAP, GN, PANU, HCRP, HID, CIP,
+A2SRC, A2SNK, AVRCT, AVRTG, UDIUE, UDITE and SYNCML.
+.IP "\fBbrowse [--tree] [--raw] [--xml] [bdaddr]\fP" 10
+Browse all available services on the device
+specified by a Bluetooth address as a parameter.
+.IP "\fBrecords [--tree] [--raw] [--xml] bdaddr\fP" 10
+Retrieve all possible service records.
+.IP "\fBadd [ --handle=N --channel=N ]\fP" 10
+Add a service to the local
+\fBsdpd\fR.
+.IP "" 10
+You can specify a handle for this record using
+the \fB--handle\fP option.
+.IP "" 10
+You can specify a channel to add the service on
+using the \fB--channel\fP option.
+.IP "\fBdel record_handle\fP" 10
+Remove a service from the local
+\fBsdpd\fR.
+.IP "\fBget [--tree] [--raw] [--xml] [--bdaddr bdaddr] record_handle\fP" 10
+Retrieve a service from the local
+\fBsdpd\fR.
+.IP "\fBsetattr record_handle attrib_id attrib_value\fP" 10
+Set or add an attribute to an SDP record.
+
+.IP "\fBsetseq record_handle attrib_id attrib_values\fP" 10
+Set or add an attribute sequence to an
+SDP record.
+.SH "OPTIONS"
+.IP "\fB--help\fP" 10
+Displays help on using sdptool.
+
+.SH "EXAMPLES"
+.PP
+sdptool browse 00:80:98:24:15:6D
+.PP
+sdptool browse local
+.PP
+sdptool add DUN
+.PP
+sdptool del 0x10000
+.SH "BUGS"
+.PP
+Documentation needs improving.
+.SH "AUTHOR"
+.PP
+Maxim Krasnyansky <maxk@qualcomm.com>. Man page written
+by Edd Dumbill <ejad@debian.org>.
+
+.SH "SEE ALSO"
+.PP
+sdpd(8)
+.\" created by instant / docbook-to-man, Thu 15 Jan 2004, 21:01
diff --git a/tools/sdptool.c b/tools/sdptool.c
new file mode 100644 (file)
index 0000000..140a46a
--- /dev/null
@@ -0,0 +1,4274 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
+ *  Copyright (C) 2002-2003  Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <netinet/in.h>
+
+#include "sdp-xml.h"
+
+#ifndef APPLE_AGENT_SVCLASS_ID
+#define APPLE_AGENT_SVCLASS_ID 0x2112
+#endif
+
+#define for_each_opt(opt, long, short) while ((opt=getopt_long(argc, argv, short ? short:"+", long, 0)) != -1)
+
+/*
+ * Convert a string to a BDADDR, with a few "enhancements" - Jean II
+ */
+static int estr2ba(char *str, bdaddr_t *ba)
+{
+       /* Only trap "local", "any" is already dealt with */
+       if(!strcmp(str, "local")) {
+               bacpy(ba, BDADDR_LOCAL);
+               return 0;
+       }
+       return str2ba(str, ba);
+}
+
+#define DEFAULT_VIEW   0       /* Display only known attribute */
+#define TREE_VIEW      1       /* Display full attribute tree */
+#define RAW_VIEW       2       /* Display raw tree */
+#define XML_VIEW       3       /* Display xml tree */
+
+/* Pass args to the inquiry/search handler */
+struct search_context {
+       char            *svc;           /* Service */
+       uuid_t          group;          /* Browse group */
+       int             view;           /* View mode */
+       uint32_t        handle;         /* Service record handle */
+};
+
+typedef int (*handler_t)(bdaddr_t *bdaddr, struct search_context *arg);
+
+static char UUID_str[MAX_LEN_UUID_STR];
+static bdaddr_t interface;
+
+/* Definition of attribute members */
+struct member_def {
+       char *name;
+};
+
+/* Definition of an attribute */
+struct attrib_def {
+       int                     num;            /* Numeric ID - 16 bits */
+       char                    *name;          /* User readable name */
+       struct member_def       *members;       /* Definition of attribute args */
+       int                     member_max;     /* Max of attribute arg definitions */
+};
+
+/* Definition of a service or protocol */
+struct uuid_def {
+       int                     num;            /* Numeric ID - 16 bits */
+       char                    *name;          /* User readable name */
+       struct attrib_def       *attribs;       /* Specific attribute definitions */
+       int                     attrib_max;     /* Max of attribute definitions */
+};
+
+/* Context information about current attribute */
+struct attrib_context {
+       struct uuid_def         *service;       /* Service UUID, if known */
+       struct attrib_def       *attrib;        /* Description of the attribute */
+       int                     member_index;   /* Index of current attribute member */
+};
+
+/* Context information about the whole service */
+struct service_context {
+       struct uuid_def         *service;       /* Service UUID, if known */
+};
+
+/* Allow us to do nice formatting of the lists */
+static char *indent_spaces = "                                         ";
+
+/* ID of the service attribute.
+ * Most attributes after 0x200 are defined based on the service, so
+ * we need to find what is the service (which is messy) - Jean II */
+#define SERVICE_ATTR   0x1
+
+/* Definition of the optional arguments in protocol list */
+static struct member_def protocol_members[] = {
+       { "Protocol"            },
+       { "Channel/Port"        },
+       { "Version"             },
+};
+
+/* Definition of the optional arguments in profile list */
+static struct member_def profile_members[] = {
+       { "Profile"     },
+       { "Version"     },
+};
+
+/* Definition of the optional arguments in Language list */
+static struct member_def language_members[] = {
+       { "Code ISO639"         },
+       { "Encoding"            },
+       { "Base Offset"         },
+};
+
+/* Name of the various common attributes. See BT assigned numbers */
+static struct attrib_def attrib_names[] = {
+       { 0x0, "ServiceRecordHandle", NULL, 0 },
+       { 0x1, "ServiceClassIDList", NULL, 0 },
+       { 0x2, "ServiceRecordState", NULL, 0 },
+       { 0x3, "ServiceID", NULL, 0 },
+       { 0x4, "ProtocolDescriptorList",
+               protocol_members, sizeof(protocol_members)/sizeof(struct member_def) },
+       { 0x5, "BrowseGroupList", NULL, 0 },
+       { 0x6, "LanguageBaseAttributeIDList",
+               language_members, sizeof(language_members)/sizeof(struct member_def) },
+       { 0x7, "ServiceInfoTimeToLive", NULL, 0 },
+       { 0x8, "ServiceAvailability", NULL, 0 },
+       { 0x9, "BluetoothProfileDescriptorList",
+               profile_members, sizeof(profile_members)/sizeof(struct member_def) },
+       { 0xA, "DocumentationURL", NULL, 0 },
+       { 0xB, "ClientExecutableURL", NULL, 0 },
+       { 0xC, "IconURL", NULL, 0 },
+       { 0xD, "AdditionalProtocolDescriptorLists", NULL, 0 },
+       /* Definitions after that are tricky (per profile or offset) */
+};
+
+const int attrib_max = sizeof(attrib_names)/sizeof(struct attrib_def);
+
+/* Name of the various SPD attributes. See BT assigned numbers */
+static struct attrib_def sdp_attrib_names[] = {
+       { 0x200, "VersionNumberList", NULL, 0 },
+       { 0x201, "ServiceDatabaseState", NULL, 0 },
+};
+
+/* Name of the various SPD attributes. See BT assigned numbers */
+static struct attrib_def browse_attrib_names[] = {
+       { 0x200, "GroupID", NULL, 0 },
+};
+
+/* Name of the various Device ID attributes. See Device Id spec. */
+static struct attrib_def did_attrib_names[] = {
+       { 0x200, "SpecificationID", NULL, 0 },
+       { 0x201, "VendorID", NULL, 0 },
+       { 0x202, "ProductID", NULL, 0 },
+       { 0x203, "Version", NULL, 0 },
+       { 0x204, "PrimaryRecord", NULL, 0 },
+       { 0x205, "VendorIDSource", NULL, 0 },
+};
+
+/* Name of the various HID attributes. See HID spec. */
+static struct attrib_def hid_attrib_names[] = {
+       { 0x200, "DeviceReleaseNum", NULL, 0 },
+       { 0x201, "ParserVersion", NULL, 0 },
+       { 0x202, "DeviceSubclass", NULL, 0 },
+       { 0x203, "CountryCode", NULL, 0 },
+       { 0x204, "VirtualCable", NULL, 0 },
+       { 0x205, "ReconnectInitiate", NULL, 0 },
+       { 0x206, "DescriptorList", NULL, 0 },
+       { 0x207, "LangIDBaseList", NULL, 0 },
+       { 0x208, "SDPDisable", NULL, 0 },
+       { 0x209, "BatteryPower", NULL, 0 },
+       { 0x20a, "RemoteWakeup", NULL, 0 },
+       { 0x20b, "ProfileVersion", NULL, 0 },
+       { 0x20c, "SupervisionTimeout", NULL, 0 },
+       { 0x20d, "NormallyConnectable", NULL, 0 },
+       { 0x20e, "BootDevice", NULL, 0 },
+};
+
+/* Name of the various PAN attributes. See BT assigned numbers */
+/* Note : those need to be double checked - Jean II */
+static struct attrib_def pan_attrib_names[] = {
+       { 0x200, "IpSubnet", NULL, 0 },         /* Obsolete ??? */
+       { 0x30A, "SecurityDescription", NULL, 0 },
+       { 0x30B, "NetAccessType", NULL, 0 },
+       { 0x30C, "MaxNetAccessrate", NULL, 0 },
+       { 0x30D, "IPv4Subnet", NULL, 0 },
+       { 0x30E, "IPv6Subnet", NULL, 0 },
+};
+
+/* Name of the various Generic-Audio attributes. See BT assigned numbers */
+/* Note : totally untested - Jean II */
+static struct attrib_def audio_attrib_names[] = {
+       { 0x302, "Remote audio volume control", NULL, 0 },
+};
+
+/* Same for the UUIDs. See BT assigned numbers */
+static struct uuid_def uuid16_names[] = {
+       /* -- Protocols -- */
+       { 0x0001, "SDP", NULL, 0 },
+       { 0x0002, "UDP", NULL, 0 },
+       { 0x0003, "RFCOMM", NULL, 0 },
+       { 0x0004, "TCP", NULL, 0 },
+       { 0x0005, "TCS-BIN", NULL, 0 },
+       { 0x0006, "TCS-AT", NULL, 0 },
+       { 0x0008, "OBEX", NULL, 0 },
+       { 0x0009, "IP", NULL, 0 },
+       { 0x000a, "FTP", NULL, 0 },
+       { 0x000c, "HTTP", NULL, 0 },
+       { 0x000e, "WSP", NULL, 0 },
+       { 0x000f, "BNEP", NULL, 0 },
+       { 0x0010, "UPnP/ESDP", NULL, 0 },
+       { 0x0011, "HIDP", NULL, 0 },
+       { 0x0012, "HardcopyControlChannel", NULL, 0 },
+       { 0x0014, "HardcopyDataChannel", NULL, 0 },
+       { 0x0016, "HardcopyNotification", NULL, 0 },
+       { 0x0017, "AVCTP", NULL, 0 },
+       { 0x0019, "AVDTP", NULL, 0 },
+       { 0x001b, "CMTP", NULL, 0 },
+       { 0x001d, "UDI_C-Plane", NULL, 0 },
+       { 0x0100, "L2CAP", NULL, 0 },
+       /* -- Services -- */
+       { 0x1000, "ServiceDiscoveryServerServiceClassID",
+               sdp_attrib_names, sizeof(sdp_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1001, "BrowseGroupDescriptorServiceClassID",
+               browse_attrib_names, sizeof(browse_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1002, "PublicBrowseGroup", NULL, 0 },
+       { 0x1101, "SerialPort", NULL, 0 },
+       { 0x1102, "LANAccessUsingPPP", NULL, 0 },
+       { 0x1103, "DialupNetworking (DUN)", NULL, 0 },
+       { 0x1104, "IrMCSync", NULL, 0 },
+       { 0x1105, "OBEXObjectPush", NULL, 0 },
+       { 0x1106, "OBEXFileTransfer", NULL, 0 },
+       { 0x1107, "IrMCSyncCommand", NULL, 0 },
+       { 0x1108, "Headset",
+               audio_attrib_names, sizeof(audio_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1109, "CordlessTelephony", NULL, 0 },
+       { 0x110a, "AudioSource", NULL, 0 },
+       { 0x110b, "AudioSink", NULL, 0 },
+       { 0x110c, "RemoteControlTarget", NULL, 0 },
+       { 0x110d, "AdvancedAudio", NULL, 0 },
+       { 0x110e, "RemoteControl", NULL, 0 },
+       { 0x110f, "VideoConferencing", NULL, 0 },
+       { 0x1110, "Intercom", NULL, 0 },
+       { 0x1111, "Fax", NULL, 0 },
+       { 0x1112, "HeadsetAudioGateway", NULL, 0 },
+       { 0x1113, "WAP", NULL, 0 },
+       { 0x1114, "WAP Client", NULL, 0 },
+       { 0x1115, "PANU (PAN/BNEP)",
+               pan_attrib_names, sizeof(pan_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1116, "NAP (PAN/BNEP)",
+               pan_attrib_names, sizeof(pan_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1117, "GN (PAN/BNEP)",
+               pan_attrib_names, sizeof(pan_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1118, "DirectPrinting (BPP)", NULL, 0 },
+       { 0x1119, "ReferencePrinting (BPP)", NULL, 0 },
+       { 0x111a, "Imaging (BIP)", NULL, 0 },
+       { 0x111b, "ImagingResponder (BIP)", NULL, 0 },
+       { 0x111c, "ImagingAutomaticArchive (BIP)", NULL, 0 },
+       { 0x111d, "ImagingReferencedObjects (BIP)", NULL, 0 },
+       { 0x111e, "Handsfree", NULL, 0 },
+       { 0x111f, "HandsfreeAudioGateway", NULL, 0 },
+       { 0x1120, "DirectPrintingReferenceObjectsService (BPP)", NULL, 0 },
+       { 0x1121, "ReflectedUI (BPP)", NULL, 0 },
+       { 0x1122, "BasicPrinting (BPP)", NULL, 0 },
+       { 0x1123, "PrintingStatus (BPP)", NULL, 0 },
+       { 0x1124, "HumanInterfaceDeviceService (HID)",
+               hid_attrib_names, sizeof(hid_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1125, "HardcopyCableReplacement (HCR)", NULL, 0 },
+       { 0x1126, "HCR_Print (HCR)", NULL, 0 },
+       { 0x1127, "HCR_Scan (HCR)", NULL, 0 },
+       { 0x1128, "Common ISDN Access (CIP)", NULL, 0 },
+       { 0x1129, "VideoConferencingGW (VCP)", NULL, 0 },
+       { 0x112a, "UDI-MT", NULL, 0 },
+       { 0x112b, "UDI-TA", NULL, 0 },
+       { 0x112c, "Audio/Video", NULL, 0 },
+       { 0x112d, "SIM Access (SAP)", NULL, 0 },
+       { 0x112e, "Phonebook Access (PBAP) - PCE", NULL, 0 },
+       { 0x112f, "Phonebook Access (PBAP) - PSE", NULL, 0 },
+       { 0x1130, "Phonebook Access (PBAP)", NULL, 0 },
+       /* ... */
+       { 0x1200, "PnPInformation",
+               did_attrib_names, sizeof(did_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1201, "GenericNetworking", NULL, 0 },
+       { 0x1202, "GenericFileTransfer", NULL, 0 },
+       { 0x1203, "GenericAudio",
+               audio_attrib_names, sizeof(audio_attrib_names)/sizeof(struct attrib_def) },
+       { 0x1204, "GenericTelephony", NULL, 0 },
+       /* ... */
+       { 0x1303, "VideoSource", NULL, 0 },
+       { 0x1304, "VideoSink", NULL, 0 },
+       { 0x1305, "VideoDistribution", NULL, 0 },
+       { 0x1400, "HDP", NULL, 0 },
+       { 0x1401, "HDPSource", NULL, 0 },
+       { 0x1402, "HDPSink", NULL, 0 },
+       { 0x2112, "AppleAgent", NULL, 0 },
+};
+
+static const int uuid16_max = sizeof(uuid16_names)/sizeof(struct uuid_def);
+
+static void sdp_data_printf(sdp_data_t *, struct attrib_context *, int);
+
+/*
+ * Parse a UUID.
+ * The BT assigned numbers only list UUID16, so I'm not sure the
+ * other types will ever get used...
+ */
+static void sdp_uuid_printf(uuid_t *uuid, struct attrib_context *context, int indent)
+{
+       if (uuid) {
+               if (uuid->type == SDP_UUID16) {
+                       uint16_t uuidNum = uuid->value.uuid16;
+                       struct uuid_def *uuidDef = NULL;
+                       int i;
+
+                       for (i = 0; i < uuid16_max; i++)
+                               if (uuid16_names[i].num == uuidNum) {
+                                       uuidDef = &uuid16_names[i];
+                                       break;
+                               }
+
+                       /* Check if it's the service attribute */
+                       if (context->attrib && context->attrib->num == SERVICE_ATTR) {
+                               /* We got the service ID !!! */
+                               context->service = uuidDef;
+                       }
+
+                       if (uuidDef)
+                               printf("%.*sUUID16 : 0x%.4x - %s\n",
+                                       indent, indent_spaces, uuidNum, uuidDef->name);
+                       else
+                               printf("%.*sUUID16 : 0x%.4x\n",
+                                       indent, indent_spaces, uuidNum);
+               } else if (uuid->type == SDP_UUID32) {
+                       struct uuid_def *uuidDef = NULL;
+                       int i;
+
+                       if (!(uuid->value.uuid32 & 0xffff0000)) {
+                               uint16_t uuidNum = uuid->value.uuid32;
+                               for (i = 0; i < uuid16_max; i++)
+                                       if (uuid16_names[i].num == uuidNum) {
+                                               uuidDef = &uuid16_names[i];
+                                               break;
+                                       }
+                       }
+
+                       if (uuidDef)
+                               printf("%.*sUUID32 : 0x%.8x - %s\n",
+                                       indent, indent_spaces, uuid->value.uuid32, uuidDef->name);
+                       else
+                               printf("%.*sUUID32 : 0x%.8x\n",
+                                       indent, indent_spaces, uuid->value.uuid32);
+               } else if (uuid->type == SDP_UUID128) {
+                       unsigned int data0;
+                       unsigned short data1;
+                       unsigned short data2;
+                       unsigned short data3;
+                       unsigned int data4;
+                       unsigned short data5;
+
+                       memcpy(&data0, &uuid->value.uuid128.data[0], 4);
+                       memcpy(&data1, &uuid->value.uuid128.data[4], 2);
+                       memcpy(&data2, &uuid->value.uuid128.data[6], 2);
+                       memcpy(&data3, &uuid->value.uuid128.data[8], 2);
+                       memcpy(&data4, &uuid->value.uuid128.data[10], 4);
+                       memcpy(&data5, &uuid->value.uuid128.data[14], 2);
+
+                       printf("%.*sUUID128 : 0x%.8x-%.4x-%.4x-%.4x-%.8x-%.4x\n",
+                               indent, indent_spaces,
+                               ntohl(data0), ntohs(data1), ntohs(data2),
+                               ntohs(data3), ntohl(data4), ntohs(data5));
+               } else
+                       printf("%.*sEnum type of UUID not set\n",
+                               indent, indent_spaces);
+       } else
+               printf("%.*sNull passed to print UUID\n",
+                               indent, indent_spaces);
+}
+
+/*
+ * Parse a sequence of data elements (i.e. a list)
+ */
+static void printf_dataseq(sdp_data_t * pData, struct attrib_context *context, int indent)
+{
+       sdp_data_t *sdpdata = NULL;
+
+       sdpdata = pData;
+       if (sdpdata) {
+               context->member_index = 0;
+               do {
+                       sdp_data_printf(sdpdata, context, indent + 2);
+                       sdpdata = sdpdata->next;
+                       context->member_index++;
+               } while (sdpdata);
+       } else {
+               printf("%.*sBroken dataseq link\n", indent, indent_spaces);
+       }
+}
+
+/*
+ * Parse a single data element (either in the attribute or in a data
+ * sequence).
+ */
+static void sdp_data_printf(sdp_data_t *sdpdata, struct attrib_context *context, int indent)
+{
+       char *member_name = NULL;
+
+       /* Find member name. Almost black magic ;-) */
+       if (context && context->attrib && context->attrib->members &&
+                       context->member_index < context->attrib->member_max) {
+               member_name = context->attrib->members[context->member_index].name;
+       }
+
+       switch (sdpdata->dtd) {
+       case SDP_DATA_NIL:
+               printf("%.*sNil\n", indent, indent_spaces);
+               break;
+       case SDP_BOOL:
+       case SDP_UINT8:
+       case SDP_UINT16:
+       case SDP_UINT32:
+       case SDP_UINT64:
+       case SDP_UINT128:
+       case SDP_INT8:
+       case SDP_INT16:
+       case SDP_INT32:
+       case SDP_INT64:
+       case SDP_INT128:
+               if (member_name) {
+                       printf("%.*s%s (Integer) : 0x%x\n",
+                               indent, indent_spaces, member_name, sdpdata->val.uint32);
+               } else {
+                       printf("%.*sInteger : 0x%x\n", indent, indent_spaces,
+                               sdpdata->val.uint32);
+               }
+               break;
+
+       case SDP_UUID16:
+       case SDP_UUID32:
+       case SDP_UUID128:
+               //printf("%.*sUUID\n", indent, indent_spaces);
+               sdp_uuid_printf(&sdpdata->val.uuid, context, indent);
+               break;
+
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_TEXT_STR32:
+               if (sdpdata->unitSize > (int) strlen(sdpdata->val.str)) {
+                       int i;
+                       printf("%.*sData :", indent, indent_spaces);
+                       for (i = 0; i < sdpdata->unitSize; i++)
+                               printf(" %02x", (unsigned char) sdpdata->val.str[i]);
+                       printf("\n");
+               } else
+                       printf("%.*sText : \"%s\"\n", indent, indent_spaces, sdpdata->val.str);
+               break;
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_URL_STR32:
+               printf("%.*sURL : %s\n", indent, indent_spaces, sdpdata->val.str);
+               break;
+
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+               printf("%.*sData Sequence\n", indent, indent_spaces);
+               printf_dataseq(sdpdata->val.dataseq, context, indent);
+               break;
+
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+               printf("%.*sData Sequence Alternates\n", indent, indent_spaces);
+               printf_dataseq(sdpdata->val.dataseq, context, indent);
+               break;
+       }
+}
+
+/*
+ * Parse a single attribute.
+ */
+static void print_tree_attr_func(void *value, void *userData)
+{
+       sdp_data_t *sdpdata = value;
+       uint16_t attrId;
+       struct service_context *service = (struct service_context *) userData;
+       struct attrib_context context;
+       struct attrib_def *attrDef = NULL;
+       int i;
+
+       if (!sdpdata)
+               return;
+
+       attrId = sdpdata->attrId;
+       /* Search amongst the generic attributes */
+       for (i = 0; i < attrib_max; i++)
+               if (attrib_names[i].num == attrId) {
+                       attrDef = &attrib_names[i];
+                       break;
+               }
+       /* Search amongst the specific attributes of this service */
+       if ((attrDef == NULL) && (service->service != NULL) &&
+                               (service->service->attribs != NULL)) {
+               struct attrib_def *svc_attribs = service->service->attribs;
+               int             svc_attrib_max = service->service->attrib_max;
+               for (i = 0; i < svc_attrib_max; i++)
+                       if (svc_attribs[i].num == attrId) {
+                               attrDef = &svc_attribs[i];
+                               break;
+                       }
+       }
+
+       if (attrDef)
+               printf("Attribute Identifier : 0x%x - %s\n", attrId, attrDef->name);
+       else
+               printf("Attribute Identifier : 0x%x\n", attrId);
+       /* Build context */
+       context.service = service->service;
+       context.attrib = attrDef;
+       context.member_index = 0;
+       /* Parse attribute members */
+       sdp_data_printf(sdpdata, &context, 2);
+       /* Update service */
+       service->service = context.service;
+}
+
+/*
+ * Main entry point of this library. Parse a SDP record.
+ * We assume the record has already been read, parsed and cached
+ * locally. Jean II
+ */
+static void print_tree_attr(sdp_record_t *rec)
+{
+       if (rec && rec->attrlist) {
+               struct service_context service = { NULL };
+               sdp_list_foreach(rec->attrlist, print_tree_attr_func, &service);
+       }
+}
+
+static void print_raw_data(sdp_data_t *data, int indent)
+{
+       struct uuid_def *def;
+       int i, hex;
+
+       if (!data)
+               return;
+
+       for (i = 0; i < indent; i++)
+               printf("\t");
+
+       switch (data->dtd) {
+       case SDP_DATA_NIL:
+               printf("NIL\n");
+               break;
+       case SDP_BOOL:
+               printf("Bool %s\n", data->val.uint8 ? "True" : "False");
+               break;
+       case SDP_UINT8:
+               printf("UINT8 0x%02x\n", data->val.uint8);
+               break;
+       case SDP_UINT16:
+               printf("UINT16 0x%04x\n", data->val.uint16);
+               break;
+       case SDP_UINT32:
+               printf("UINT32 0x%08x\n", data->val.uint32);
+               break;
+       case SDP_UINT64:
+               printf("UINT64 0x%016jx\n", data->val.uint64);
+               break;
+       case SDP_UINT128:
+               printf("UINT128 ...\n");
+               break;
+       case SDP_INT8:
+               printf("INT8 %d\n", data->val.int8);
+               break;
+       case SDP_INT16:
+               printf("INT16 %d\n", data->val.int16);
+               break;
+       case SDP_INT32:
+               printf("INT32 %d\n", data->val.int32);
+               break;
+       case SDP_INT64:
+               printf("INT64 %jd\n", data->val.int64);
+               break;
+       case SDP_INT128:
+               printf("INT128 ...\n");
+               break;
+       case SDP_UUID16:
+       case SDP_UUID32:
+       case SDP_UUID128:
+               switch (data->val.uuid.type) {
+               case SDP_UUID16:
+                       def = NULL;
+                       for (i = 0; i < uuid16_max; i++)
+                               if (uuid16_names[i].num == data->val.uuid.value.uuid16) {
+                                       def = &uuid16_names[i];
+                                       break;
+                               }
+                       if (def)
+                               printf("UUID16 0x%04x - %s\n", data->val.uuid.value.uuid16, def->name);
+                       else
+                               printf("UUID16 0x%04x\n", data->val.uuid.value.uuid16);
+                       break;
+               case SDP_UUID32:
+                       def = NULL;
+                       if (!(data->val.uuid.value.uuid32 & 0xffff0000)) {
+                               uint16_t value = data->val.uuid.value.uuid32;
+                               for (i = 0; i < uuid16_max; i++)
+                                       if (uuid16_names[i].num == value) {
+                                               def = &uuid16_names[i];
+                                               break;
+                                       }
+                       }
+                       if (def)
+                               printf("UUID32 0x%08x - %s\n", data->val.uuid.value.uuid32, def->name);
+                       else
+                               printf("UUID32 0x%08x\n", data->val.uuid.value.uuid32);
+                       break;
+               case SDP_UUID128:
+                       printf("UUID128 ");
+                       for (i = 0; i < 16; i++) {
+                               switch (i) {
+                               case 4:
+                               case 6:
+                               case 8:
+                               case 10:
+                                       printf("-");
+                                       break;
+                               }
+                               printf("%02x", (unsigned char ) data->val.uuid.value.uuid128.data[i]);
+                       }
+                       printf("\n");
+                       break;
+               default:
+                       printf("UUID type 0x%02x\n", data->val.uuid.type);
+                       break;
+               }
+               break;
+       case SDP_TEXT_STR8:
+       case SDP_TEXT_STR16:
+       case SDP_TEXT_STR32:
+               hex = 0;
+               for (i = 0; i < data->unitSize; i++) {
+                       if (i == (data->unitSize - 1) && data->val.str[i] == '\0')
+                               break;
+                       if (!isprint(data->val.str[i])) {
+                               hex = 1;
+                               break;
+                       }
+               }
+               if (hex) {
+                       printf("Data");
+                       for (i = 0; i < data->unitSize; i++)
+                               printf(" %02x", (unsigned char) data->val.str[i]);
+               } else {
+                       printf("String ");
+                       for (i = 0; i < data->unitSize; i++)
+                               printf("%c", data->val.str[i]);
+               }
+               printf("\n");
+               break;
+       case SDP_URL_STR8:
+       case SDP_URL_STR16:
+       case SDP_URL_STR32:
+               printf("URL %s\n", data->val.str);
+               break;
+       case SDP_SEQ8:
+       case SDP_SEQ16:
+       case SDP_SEQ32:
+               printf("Sequence\n");
+               print_raw_data(data->val.dataseq, indent + 1);
+               break;
+       case SDP_ALT8:
+       case SDP_ALT16:
+       case SDP_ALT32:
+               printf("Alternate\n");
+               print_raw_data(data->val.dataseq, indent + 1);
+               break;
+       default:
+               printf("Unknown type 0x%02x\n", data->dtd);
+               break;
+       }
+
+       print_raw_data(data->next, indent);
+}
+
+static void print_raw_attr_func(void *value, void *userData)
+{
+       sdp_data_t *data = (sdp_data_t *) value;
+       struct attrib_def *def = NULL;
+       int i;
+
+       if (!data)
+               return;
+
+       /* Search amongst the generic attributes */
+       for (i = 0; i < attrib_max; i++)
+               if (attrib_names[i].num == data->attrId) {
+                       def = &attrib_names[i];
+                       break;
+               }
+
+       if (def)
+               printf("\tAttribute 0x%04x - %s\n", data->attrId, def->name);
+       else
+               printf("\tAttribute 0x%04x\n", data->attrId);
+
+       print_raw_data(data, 2);
+}
+
+static void print_raw_attr(sdp_record_t *rec)
+{
+       if (rec && rec->attrlist) {
+               printf("Sequence\n");
+               sdp_list_foreach(rec->attrlist, print_raw_attr_func, 0);
+       }
+}
+
+/*
+ * Set attributes with single values in SDP record
+ * Jean II
+ */
+static int set_attrib(sdp_session_t *sess, uint32_t handle, uint16_t attrib, char *value)
+{
+       sdp_list_t *attrid_list;
+       uint32_t range = 0x0000ffff;
+       sdp_record_t *rec;
+       int ret;
+
+       /* Get the old SDP record */
+       attrid_list = sdp_list_append(NULL, &range);
+       rec = sdp_service_attr_req(sess, handle, SDP_ATTR_REQ_RANGE, attrid_list);
+       sdp_list_free(attrid_list, NULL);
+
+       if (!rec) {
+               printf("Service get request failed.\n");
+               return -1;
+       }
+
+       /* Check the type of attribute */
+       if (!strncasecmp(value, "u0x", 3)) {
+               /* UUID16 */
+               uint16_t value_int = 0;
+               uuid_t value_uuid;
+               value_int = strtoul(value + 3, NULL, 16);
+               sdp_uuid16_create(&value_uuid, value_int);
+               printf("Adding attrib 0x%X uuid16 0x%X to record 0x%X\n",
+                       attrib, value_int, handle);
+
+               sdp_attr_add_new(rec, attrib, SDP_UUID16, &value_uuid.value.uuid16);
+       } else if (!strncasecmp(value, "0x", 2)) {
+               /* Int */
+               uint32_t value_int;
+               value_int = strtoul(value + 2, NULL, 16);
+               printf("Adding attrib 0x%X int 0x%X to record 0x%X\n",
+                       attrib, value_int, handle);
+
+               sdp_attr_add_new(rec, attrib, SDP_UINT32, &value_int);
+       } else {
+               /* String */
+               printf("Adding attrib 0x%X string \"%s\" to record 0x%X\n",
+                       attrib, value, handle);
+
+               /* Add/Update our attribute to the record */
+               sdp_attr_add_new(rec, attrib, SDP_TEXT_STR8, value);
+       }
+
+       /* Update on the server */
+       ret = sdp_device_record_update(sess, &interface, rec);
+       if (ret < 0)
+               printf("Service Record update failed (%d).\n", errno);
+       sdp_record_free(rec);
+       return ret;
+}
+
+static struct option set_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *set_help =
+       "Usage:\n"
+       "\tget record_handle attrib_id attrib_value\n";
+
+/*
+ * Add an attribute to an existing SDP record on the local SDP server
+ */
+static int cmd_setattr(int argc, char **argv)
+{
+       int opt, status;
+       uint32_t handle;
+       uint16_t attrib;
+       sdp_session_t *sess;
+
+       for_each_opt(opt, set_options, NULL) {
+               switch(opt) {
+               default:
+                       printf("%s", set_help);
+                       return -1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 3) {
+               printf("%s", set_help);
+               return -1;
+       }
+
+       /* Convert command line args */
+       handle = strtoul(argv[0], NULL, 16);
+       attrib = strtoul(argv[1], NULL, 16);
+
+       /* Do it */
+       sess = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0);
+       if (!sess)
+               return -1;
+
+       status = set_attrib(sess, handle, attrib, argv[2]);
+       sdp_close(sess);
+
+       return status;
+}
+
+/*
+ * We do only simple data sequences. Sequence of sequences is a pain ;-)
+ * Jean II
+ */
+static int set_attribseq(sdp_session_t *session, uint32_t handle, uint16_t attrib, int argc, char **argv)
+{
+       sdp_list_t *attrid_list;
+       uint32_t range = 0x0000ffff;
+       sdp_record_t *rec;
+       sdp_data_t *pSequenceHolder = NULL;
+       void **dtdArray;
+       void **valueArray;
+       void **allocArray;
+       uint8_t uuid16 = SDP_UUID16;
+       uint8_t uint32 = SDP_UINT32;
+       uint8_t str8 = SDP_TEXT_STR8;
+       int i, ret = 0;
+
+       /* Get the old SDP record */
+       attrid_list = sdp_list_append(NULL, &range);
+       rec = sdp_service_attr_req(session, handle, SDP_ATTR_REQ_RANGE, attrid_list);
+       sdp_list_free(attrid_list, NULL);
+
+       if (!rec) {
+               printf("Service get request failed.\n");
+               return -1;
+       }
+
+       /* Create arrays */
+       dtdArray = (void **)malloc(argc * sizeof(void *));
+       valueArray = (void **)malloc(argc * sizeof(void *));
+       allocArray = (void **)malloc(argc * sizeof(void *));
+
+       /* Loop on all args, add them in arrays */
+       for (i = 0; i < argc; i++) {
+               /* Check the type of attribute */
+               if (!strncasecmp(argv[i], "u0x", 3)) {
+                       /* UUID16 */
+                       uint16_t value_int = strtoul((argv[i]) + 3, NULL, 16);
+                       uuid_t *value_uuid = (uuid_t *) malloc(sizeof(uuid_t));
+                       allocArray[i] = value_uuid;
+                       sdp_uuid16_create(value_uuid, value_int);
+
+                       printf("Adding uuid16 0x%X to record 0x%X\n", value_int, handle);
+                       dtdArray[i] = &uuid16;
+                       valueArray[i] = &value_uuid->value.uuid16;
+               } else if (!strncasecmp(argv[i], "0x", 2)) {
+                       /* Int */
+                       uint32_t *value_int = (uint32_t *) malloc(sizeof(int));
+                       allocArray[i] = value_int;
+                       *value_int = strtoul((argv[i]) + 2, NULL, 16);
+
+                       printf("Adding int 0x%X to record 0x%X\n", *value_int, handle);
+                       dtdArray[i] = &uint32;
+                       valueArray[i] = value_int;
+               } else {
+                       /* String */
+                       printf("Adding string \"%s\" to record 0x%X\n", argv[i], handle);
+                       dtdArray[i] = &str8;
+                       valueArray[i] = argv[i];
+               }
+       }
+
+       /* Add this sequence to the attrib list */
+       pSequenceHolder = sdp_seq_alloc(dtdArray, valueArray, argc);
+       if (pSequenceHolder) {
+               sdp_attr_replace(rec, attrib, pSequenceHolder);
+
+               /* Update on the server */
+               ret = sdp_device_record_update(session, &interface, rec);
+               if (ret < 0)
+                       printf("Service Record update failed (%d).\n", errno);
+       } else
+               printf("Failed to create pSequenceHolder\n");
+
+       /* Cleanup */
+       for (i = 0; i < argc; i++)
+               free(allocArray[i]);
+
+       free(dtdArray);
+       free(valueArray);
+       free(allocArray);
+
+       sdp_record_free(rec);
+
+       return ret;
+}
+
+static struct option seq_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *seq_help =
+       "Usage:\n"
+       "\tget record_handle attrib_id attrib_values\n";
+
+/*
+ * Add an attribute sequence to an existing SDP record
+ * on the local SDP server
+ */
+static int cmd_setseq(int argc, char **argv)
+{
+       int opt, status;
+       uint32_t handle;
+       uint16_t attrib;
+       sdp_session_t *sess;
+
+       for_each_opt(opt, seq_options, NULL) {
+               switch(opt) {
+               default:
+                       printf("%s", seq_help);
+                       return -1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 3) {
+               printf("%s", seq_help);
+               return -1;
+       }
+
+       /* Convert command line args */
+       handle = strtoul(argv[0], NULL, 16);
+       attrib = strtoul(argv[1], NULL, 16);
+
+       argc -= 2;
+       argv += 2;
+
+       /* Do it */
+       sess = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0);
+       if (!sess)
+               return -1;
+
+       status = set_attribseq(sess, handle, attrib, argc, argv);
+       sdp_close(sess);
+
+       return status;
+}
+
+static void print_service_class(void *value, void *userData)
+{
+       char ServiceClassUUID_str[MAX_LEN_SERVICECLASS_UUID_STR];
+       uuid_t *uuid = (uuid_t *)value;
+
+       sdp_uuid2strn(uuid, UUID_str, MAX_LEN_UUID_STR);
+       sdp_svclass_uuid2strn(uuid, ServiceClassUUID_str, MAX_LEN_SERVICECLASS_UUID_STR);
+       if (uuid->type != SDP_UUID128)
+               printf("  \"%s\" (0x%s)\n", ServiceClassUUID_str, UUID_str);
+       else
+               printf("  UUID 128: %s\n", UUID_str);
+}
+
+static void print_service_desc(void *value, void *user)
+{
+       char str[MAX_LEN_PROTOCOL_UUID_STR];
+       sdp_data_t *p = (sdp_data_t *)value, *s;
+       int i = 0, proto = 0;
+
+       for (; p; p = p->next, i++) {
+               switch (p->dtd) {
+               case SDP_UUID16:
+               case SDP_UUID32:
+               case SDP_UUID128:
+                       sdp_uuid2strn(&p->val.uuid, UUID_str, MAX_LEN_UUID_STR);
+                       sdp_proto_uuid2strn(&p->val.uuid, str, sizeof(str));
+                       proto = sdp_uuid_to_proto(&p->val.uuid);
+                       printf("  \"%s\" (0x%s)\n", str, UUID_str);
+                       break;
+               case SDP_UINT8:
+                       if (proto == RFCOMM_UUID)
+                               printf("    Channel: %d\n", p->val.uint8);
+                       else
+                               printf("    uint8: 0x%x\n", p->val.uint8);
+                       break;
+               case SDP_UINT16:
+                       if (proto == L2CAP_UUID) {
+                               if (i == 1)
+                                       printf("    PSM: %d\n", p->val.uint16);
+                               else
+                                       printf("    Version: 0x%04x\n", p->val.uint16);
+                       } else if (proto == BNEP_UUID)
+                               if (i == 1)
+                                       printf("    Version: 0x%04x\n", p->val.uint16);
+                               else
+                                       printf("    uint16: 0x%x\n", p->val.uint16);
+                       else
+                               printf("    uint16: 0x%x\n", p->val.uint16);
+                       break;
+               case SDP_SEQ16:
+                       printf("    SEQ16:");
+                       for (s = p->val.dataseq; s; s = s->next)
+                               printf(" %x", s->val.uint16);
+                       printf("\n");
+                       break;
+               case SDP_SEQ8:
+                       printf("    SEQ8:");
+                       for (s = p->val.dataseq; s; s = s->next)
+                               printf(" %x", s->val.uint8);
+                       printf("\n");
+                       break;
+               default:
+                       printf("    FIXME: dtd=0%x\n", p->dtd);
+                       break;
+               }
+       }
+}
+
+static void print_lang_attr(void *value, void *user)
+{
+       sdp_lang_attr_t *lang = (sdp_lang_attr_t *)value;
+       printf("  code_ISO639: 0x%02x\n", lang->code_ISO639);
+       printf("  encoding:    0x%02x\n", lang->encoding);
+       printf("  base_offset: 0x%02x\n", lang->base_offset);
+}
+
+static void print_access_protos(void *value, void *userData)
+{
+       sdp_list_t *protDescSeq = (sdp_list_t *)value;
+       sdp_list_foreach(protDescSeq, print_service_desc, 0);
+}
+
+static void print_profile_desc(void *value, void *userData)
+{
+       sdp_profile_desc_t *desc = (sdp_profile_desc_t *)value;
+       char str[MAX_LEN_PROFILEDESCRIPTOR_UUID_STR];
+
+       sdp_uuid2strn(&desc->uuid, UUID_str, MAX_LEN_UUID_STR);
+       sdp_profile_uuid2strn(&desc->uuid, str, MAX_LEN_PROFILEDESCRIPTOR_UUID_STR);
+
+       printf("  \"%s\" (0x%s)\n", str, UUID_str);
+       if (desc->version)
+               printf("    Version: 0x%04x\n", desc->version);
+}
+
+/*
+ * Parse a SDP record in user friendly form.
+ */
+static void print_service_attr(sdp_record_t *rec)
+{
+       sdp_list_t *list = 0, *proto = 0;
+
+       sdp_record_print(rec);
+
+       printf("Service RecHandle: 0x%x\n", rec->handle);
+
+       if (sdp_get_service_classes(rec, &list) == 0) {
+               printf("Service Class ID List:\n");
+               sdp_list_foreach(list, print_service_class, 0);
+               sdp_list_free(list, free);
+       }
+       if (sdp_get_access_protos(rec, &proto) == 0) {
+               printf("Protocol Descriptor List:\n");
+               sdp_list_foreach(proto, print_access_protos, 0);
+               sdp_list_foreach(proto, (sdp_list_func_t)sdp_list_free, 0);
+               sdp_list_free(proto, 0);
+       }
+       if (sdp_get_lang_attr(rec, &list) == 0) {
+               printf("Language Base Attr List:\n");
+               sdp_list_foreach(list, print_lang_attr, 0);
+               sdp_list_free(list, free);
+       }
+       if (sdp_get_profile_descs(rec, &list) == 0) {
+               printf("Profile Descriptor List:\n");
+               sdp_list_foreach(list, print_profile_desc, 0);
+               sdp_list_free(list, free);
+       }
+}
+
+/*
+ * Support for Service (de)registration
+ */
+typedef struct {
+       uint32_t handle;
+       char *name;
+       char *provider;
+       char *desc;
+       unsigned int class;
+       unsigned int profile;
+       uint16_t psm;
+       uint8_t channel;
+       uint8_t network;
+} svc_info_t;
+
+static void add_lang_attr(sdp_record_t *r)
+{
+       sdp_lang_attr_t base_lang;
+       sdp_list_t *langs = 0;
+
+       /* UTF-8 MIBenum (http://www.iana.org/assignments/character-sets) */
+       base_lang.code_ISO639 = (0x65 << 8) | 0x6e;
+       base_lang.encoding = 106;
+       base_lang.base_offset = SDP_PRIMARY_LANG_BASE;
+       langs = sdp_list_append(0, &base_lang);
+       sdp_set_lang_attr(r, langs);
+       sdp_list_free(langs, 0);
+}
+
+static int add_sp(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *apseq, *proto[2], *profiles, *root, *aproto;
+       uuid_t root_uuid, sp_uuid, l2cap, rfcomm;
+       sdp_profile_desc_t profile;
+       sdp_record_t record;
+       uint8_t u8 = si->channel ? si->channel : 1;
+       sdp_data_t *channel;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+       sdp_list_free(root, 0);
+
+       sdp_uuid16_create(&sp_uuid, SERIAL_PORT_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &sp_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+       sdp_list_free(svclass_id, 0);
+
+       sdp_uuid16_create(&profile.uuid, SERIAL_PORT_PROFILE_ID);
+       profile.version = 0x0100;
+       profiles = sdp_list_append(0, &profile);
+       sdp_set_profile_descs(&record, profiles);
+       sdp_list_free(profiles, 0);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(0, &l2cap);
+       apseq = sdp_list_append(0, proto[0]);
+
+       sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
+       proto[1] = sdp_list_append(0, &rfcomm);
+       channel = sdp_data_alloc(SDP_UINT8, &u8);
+       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);
+
+       add_lang_attr(&record);
+
+       sdp_set_info_attr(&record, "Serial Port", "BlueZ", "COM Port");
+
+       sdp_set_url_attr(&record, "http://www.bluez.org/",
+                       "http://www.bluez.org/", "http://www.bluez.org/");
+
+       sdp_set_service_id(&record, sp_uuid);
+       sdp_set_service_ttl(&record, 0xffff);
+       sdp_set_service_avail(&record, 0xff);
+       sdp_set_record_state(&record, 0x00001234);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("Serial Port service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_dun(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root, *aproto;
+       uuid_t rootu, dun, gn, l2cap, rfcomm;
+       sdp_profile_desc_t profile;
+       sdp_list_t *proto[2];
+       sdp_record_t record;
+       uint8_t u8 = si->channel ? si->channel : 2;
+       sdp_data_t *channel;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&rootu, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &rootu);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&dun, DIALUP_NET_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &dun);
+       sdp_uuid16_create(&gn,  GENERIC_NETWORKING_SVCLASS_ID);
+       svclass_id = sdp_list_append(svclass_id, &gn);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile.uuid, DIALUP_NET_PROFILE_ID);
+       profile.version = 0x0100;
+       pfseq = sdp_list_append(0, &profile);
+       sdp_set_profile_descs(&record, pfseq);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(0, &l2cap);
+       apseq = sdp_list_append(0, proto[0]);
+
+       sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
+       proto[1] = sdp_list_append(0, &rfcomm);
+       channel = sdp_data_alloc(SDP_UINT8, &u8);
+       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, "Dial-Up Networking", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("Dial-Up Networking service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_fax(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, fax_uuid, tel_uuid, l2cap_uuid, rfcomm_uuid;
+       sdp_profile_desc_t profile;
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint8_t u8 = si->channel? si->channel : 3;
+       sdp_data_t *channel;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&fax_uuid, FAX_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &fax_uuid);
+       sdp_uuid16_create(&tel_uuid, GENERIC_TELEPHONY_SVCLASS_ID);
+       svclass_id = sdp_list_append(svclass_id, &tel_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile.uuid, FAX_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, &u8);
+       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, "Fax", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+       printf("Fax service registered\n");
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+       return ret;
+}
+
+static int add_lan(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       sdp_profile_desc_t profile;
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint8_t u8 = si->channel ? si->channel : 4;
+       sdp_data_t *channel;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       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, LAN_ACCESS_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile.uuid, LAN_ACCESS_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, &u8);
+       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, "LAN Access over PPP", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("LAN Access service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_headset(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       sdp_profile_desc_t profile;
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint8_t u8 = si->channel ? si->channel : 5;
+       sdp_data_t *channel;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       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_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 = 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, &u8);
+       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", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("Headset service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_headset_ag(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       sdp_profile_desc_t profile;
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint8_t u8 = si->channel ? si->channel : 7;
+       sdp_data_t *channel;
+       uint8_t netid = si->network ? si->network : 0x01; // ???? profile document
+       sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       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 = 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, &u8);
+       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, "Voice Gateway", 0, 0);
+
+       sdp_attr_add(&record, SDP_ATTR_EXTERNAL_NETWORK, network);
+
+       if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("Headset AG service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_handsfree(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       sdp_profile_desc_t profile;
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint8_t u8 = si->channel ? si->channel : 6;
+       uint16_t u16 = 0x31;
+       sdp_data_t *channel, *features;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       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 = 0x0101;
+       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, &u8);
+       proto[1] = sdp_list_append(proto[1], channel);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       features = sdp_data_alloc(SDP_UINT16, &u16);
+       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, "Handsfree", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("Handsfree service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_handsfree_ag(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       sdp_profile_desc_t profile;
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint8_t u8 = si->channel ? si->channel : 7;
+       uint16_t u16 = 0x17;
+       sdp_data_t *channel, *features;
+       uint8_t netid = si->network ? si->network : 0x01; // ???? profile document
+       sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       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, &u8);
+       proto[1] = sdp_list_append(proto[1], channel);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       features = sdp_data_alloc(SDP_UINT16, &u16);
+       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, "Voice Gateway", 0, 0);
+
+       sdp_attr_add(&record, SDP_ATTR_EXTERNAL_NETWORK, network);
+
+       if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("Handsfree AG service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_simaccess(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       sdp_profile_desc_t profile;
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint8_t u8 = si->channel? si->channel : 8;
+       uint16_t u16 = 0x31;
+       sdp_data_t *channel, *features;
+       int ret = 0;
+
+       memset((void *)&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       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, SAP_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &svclass_uuid);
+       sdp_uuid16_create(&ga_svclass_uuid, GENERIC_TELEPHONY_SVCLASS_ID);
+       svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile.uuid, SAP_PROFILE_ID);
+       profile.version = 0x0101;
+       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, &u8);
+       proto[1] = sdp_list_append(proto[1], channel);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       features = sdp_data_alloc(SDP_UINT16, &u16);
+       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, "SIM Access", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("SIM Access service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_opush(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, opush_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[3];
+       sdp_record_t record;
+       uint8_t chan = si->channel ? si->channel : 9;
+       sdp_data_t *channel;
+       uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xff };
+       void *dtds[sizeof(formats)], *values[sizeof(formats)];
+       unsigned int i;
+       uint8_t dtd = SDP_UINT8;
+       sdp_data_t *sflist;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&opush_uuid, OBEX_OBJPUSH_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &opush_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID);
+       profile[0].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, &chan);
+       proto[1] = sdp_list_append(proto[1], channel);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       sdp_uuid16_create(&obex_uuid, OBEX_UUID);
+       proto[2] = sdp_list_append(0, &obex_uuid);
+       apseq = sdp_list_append(apseq, proto[2]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       for (i = 0; i < sizeof(formats); i++) {
+               dtds[i] = &dtd;
+               values[i] = &formats[i];
+       }
+       sflist = sdp_seq_alloc(dtds, values, sizeof(formats));
+       sdp_attr_add(&record, SDP_ATTR_SUPPORTED_FORMATS_LIST, sflist);
+
+       sdp_set_info_attr(&record, "OBEX Object Push", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("OBEX Object Push service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(proto[2], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_pbap(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, pbap_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[3];
+       sdp_record_t record;
+       uint8_t chan = si->channel ? si->channel : 19;
+       sdp_data_t *channel;
+       uint8_t formats[] = {0x01};
+       uint8_t dtd = SDP_UINT8;
+       sdp_data_t *sflist;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&pbap_uuid, PBAP_PSE_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &pbap_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, PBAP_PROFILE_ID);
+       profile[0].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, &chan);
+       proto[1] = sdp_list_append(proto[1], channel);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       sdp_uuid16_create(&obex_uuid, OBEX_UUID);
+       proto[2] = sdp_list_append(0, &obex_uuid);
+       apseq = sdp_list_append(apseq, proto[2]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       sflist = sdp_data_alloc(dtd,formats);
+       sdp_attr_add(&record, SDP_ATTR_SUPPORTED_REPOSITORIES, sflist);
+
+       sdp_set_info_attr(&record, "OBEX Phonebook Access Server", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record,
+                       SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("PBAP service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(proto[2], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_ftp(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, ftrn_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[3];
+       sdp_record_t record;
+       uint8_t u8 = si->channel ? si->channel: 10;
+       sdp_data_t *channel;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&ftrn_uuid, OBEX_FILETRANS_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &ftrn_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, OBEX_FILETRANS_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);
+       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, &u8);
+       proto[1] = sdp_list_append(proto[1], channel);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       sdp_uuid16_create(&obex_uuid, OBEX_UUID);
+       proto[2] = sdp_list_append(0, &obex_uuid);
+       apseq = sdp_list_append(apseq, proto[2]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       sdp_set_info_attr(&record, "OBEX File Transfer", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("OBEX File Transfer service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(proto[2], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_directprint(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, opush_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[3];
+       sdp_record_t record;
+       uint8_t chan = si->channel ? si->channel : 12;
+       sdp_data_t *channel;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&opush_uuid, DIRECT_PRINTING_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &opush_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, BASIC_PRINTING_PROFILE_ID);
+       profile[0].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, &chan);
+       proto[1] = sdp_list_append(proto[1], channel);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       sdp_uuid16_create(&obex_uuid, OBEX_UUID);
+       proto[2] = sdp_list_append(0, &obex_uuid);
+       apseq = sdp_list_append(apseq, proto[2]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       sdp_set_info_attr(&record, "Direct Printing", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("Direct Printing service registered\n");
+
+end:
+       sdp_data_free(channel);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(proto[2], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_nap(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, ftrn_uuid, l2cap_uuid, bnep_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint16_t lp = 0x000f, ver = 0x0100;
+       sdp_data_t *psm, *version;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&ftrn_uuid, NAP_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &ftrn_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, NAP_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(&bnep_uuid, BNEP_UUID);
+       proto[1] = sdp_list_append(0, &bnep_uuid);
+       version  = sdp_data_alloc(SDP_UINT16, &ver);
+       proto[1] = sdp_list_append(proto[1], version);
+
+       {
+               uint16_t ptype[4] = { 0x0010, 0x0020, 0x0030, 0x0040 };
+               sdp_data_t *head, *pseq;
+               int p;
+
+               for (p = 0, head = NULL; p < 4; p++) {
+                       sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]);
+                       head = sdp_seq_append(head, data);
+               }
+               pseq = sdp_data_alloc(SDP_SEQ16, head);
+               proto[1] = sdp_list_append(proto[1], pseq);
+       }
+
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       sdp_set_info_attr(&record, "Network Access Point Service", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("NAP service registered\n");
+
+end:
+       sdp_data_free(version);
+       sdp_data_free(psm);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_gn(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, ftrn_uuid, l2cap_uuid, bnep_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint16_t lp = 0x000f, ver = 0x0100;
+       sdp_data_t *psm, *version;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&ftrn_uuid, GN_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &ftrn_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, GN_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(&bnep_uuid, BNEP_UUID);
+       proto[1] = sdp_list_append(0, &bnep_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);
+
+       sdp_set_info_attr(&record, "Group Network Service", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("GN service registered\n");
+
+end:
+       sdp_data_free(version);
+       sdp_data_free(psm);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_panu(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, ftrn_uuid, l2cap_uuid, bnep_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint16_t lp = 0x000f, ver = 0x0100;
+       sdp_data_t *psm, *version;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+       sdp_list_free(root, NULL);
+
+       sdp_uuid16_create(&ftrn_uuid, PANU_SVCLASS_ID);
+       svclass_id = sdp_list_append(NULL, &ftrn_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+       sdp_list_free(svclass_id, NULL);
+
+       sdp_uuid16_create(&profile[0].uuid, PANU_PROFILE_ID);
+       profile[0].version = 0x0100;
+       pfseq = sdp_list_append(NULL, &profile[0]);
+       sdp_set_profile_descs(&record, pfseq);
+       sdp_list_free(pfseq, NULL);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap_uuid);
+       psm = sdp_data_alloc(SDP_UINT16, &lp);
+       proto[0] = sdp_list_append(proto[0], psm);
+       apseq = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&bnep_uuid, BNEP_UUID);
+       proto[1] = sdp_list_append(NULL, &bnep_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(NULL, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       sdp_set_info_attr(&record, "PAN User", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("PANU service registered\n");
+
+end:
+       sdp_data_free(version);
+       sdp_data_free(psm);
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_hid_keyb(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, hidkb_uuid, l2cap_uuid, hidp_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[3];
+       sdp_data_t *psm, *lang_lst, *lang_lst2, *hid_spec_lst, *hid_spec_lst2;
+       unsigned int i;
+       uint8_t dtd = SDP_UINT16;
+       uint8_t dtd2 = SDP_UINT8;
+       uint8_t dtd_data = SDP_TEXT_STR8;
+       void *dtds[2];
+       void *values[2];
+       void *dtds2[2];
+       void *values2[2];
+       int leng[2];
+       uint8_t hid_spec_type = 0x22;
+       uint16_t hid_attr_lang[] = { 0x409, 0x100 };
+       static const uint16_t ctrl = 0x11;
+       static const uint16_t intr = 0x13;
+       static const uint16_t hid_attr[] = { 0x100, 0x111, 0x40, 0x0d, 0x01, 0x01 };
+       static const uint16_t hid_attr2[] = { 0x0, 0x01, 0x100, 0x1f40, 0x01, 0x01 };
+       const uint8_t hid_spec[] = {
+               0x05, 0x01, // usage page
+               0x09, 0x06, // keyboard
+               0xa1, 0x01, // key codes
+               0x85, 0x01, // minimum
+               0x05, 0x07, // max
+               0x19, 0xe0, // logical min
+               0x29, 0xe7, // logical max
+               0x15, 0x00, // report size
+               0x25, 0x01, // report count
+               0x75, 0x01, // input data variable absolute
+               0x95, 0x08, // report count
+               0x81, 0x02, // report size
+               0x75, 0x08,
+               0x95, 0x01,
+               0x81, 0x01,
+               0x75, 0x01,
+               0x95, 0x05,
+               0x05, 0x08,
+               0x19, 0x01,
+               0x29, 0x05,
+               0x91, 0x02,
+               0x75, 0x03,
+               0x95, 0x01,
+               0x91, 0x01,
+               0x75, 0x08,
+               0x95, 0x06,
+               0x15, 0x00,
+               0x26, 0xff,
+               0x00, 0x05,
+               0x07, 0x19,
+               0x00, 0x2a,
+               0xff, 0x00,
+               0x81, 0x00,
+               0x75, 0x01,
+               0x95, 0x01,
+               0x15, 0x00,
+               0x25, 0x01,
+               0x05, 0x0c,
+               0x09, 0xb8,
+               0x81, 0x06,
+               0x09, 0xe2,
+               0x81, 0x06,
+               0x09, 0xe9,
+               0x81, 0x02,
+               0x09, 0xea,
+               0x81, 0x02,
+               0x75, 0x01,
+               0x95, 0x04,
+               0x81, 0x01,
+               0xc0         // end tag
+       };
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       add_lang_attr(&record);
+
+       sdp_uuid16_create(&hidkb_uuid, HID_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &hidkb_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, HID_PROFILE_ID);
+       profile[0].version = 0x0100;
+       pfseq = sdp_list_append(0, profile);
+       sdp_set_profile_descs(&record, pfseq);
+
+       /* protocols */
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto[1] = sdp_list_append(0, &l2cap_uuid);
+       psm = sdp_data_alloc(SDP_UINT16, &ctrl);
+       proto[1] = sdp_list_append(proto[1], psm);
+       apseq = sdp_list_append(0, proto[1]);
+
+       sdp_uuid16_create(&hidp_uuid, HIDP_UUID);
+       proto[2] = sdp_list_append(0, &hidp_uuid);
+       apseq = sdp_list_append(apseq, proto[2]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       /* additional protocols */
+       proto[1] = sdp_list_append(0, &l2cap_uuid);
+       psm = sdp_data_alloc(SDP_UINT16, &intr);
+       proto[1] = sdp_list_append(proto[1], psm);
+       apseq = sdp_list_append(0, proto[1]);
+
+       sdp_uuid16_create(&hidp_uuid, HIDP_UUID);
+       proto[2] = sdp_list_append(0, &hidp_uuid);
+       apseq = sdp_list_append(apseq, proto[2]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_add_access_protos(&record, aproto);
+
+       sdp_set_info_attr(&record, "HID Keyboard", NULL, NULL);
+
+       for (i = 0; i < sizeof(hid_attr) / 2; i++)
+               sdp_attr_add_new(&record,
+                                       SDP_ATTR_HID_DEVICE_RELEASE_NUMBER + i,
+                                       SDP_UINT16, &hid_attr[i]);
+
+       dtds[0] = &dtd2;
+       values[0] = &hid_spec_type;
+       dtds[1] = &dtd_data;
+       values[1] = (uint8_t *) hid_spec;
+       leng[0] = 0;
+       leng[1] = sizeof(hid_spec);
+       hid_spec_lst = sdp_seq_alloc_with_length(dtds, values, leng, 2);
+       hid_spec_lst2 = sdp_data_alloc(SDP_SEQ8, hid_spec_lst);
+       sdp_attr_add(&record, SDP_ATTR_HID_DESCRIPTOR_LIST, hid_spec_lst2);
+
+       for (i = 0; i < sizeof(hid_attr_lang) / 2; i++) {
+               dtds2[i] = &dtd;
+               values2[i] = &hid_attr_lang[i];
+       }
+
+       lang_lst = sdp_seq_alloc(dtds2, values2, sizeof(hid_attr_lang) / 2);
+       lang_lst2 = sdp_data_alloc(SDP_SEQ8, lang_lst);
+       sdp_attr_add(&record, SDP_ATTR_HID_LANG_ID_BASE_LIST, lang_lst2);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_SDP_DISABLE, SDP_UINT16, &hid_attr2[0]);
+
+       for (i = 0; i < sizeof(hid_attr2) / 2 - 1; i++)
+               sdp_attr_add_new(&record, SDP_ATTR_HID_REMOTE_WAKEUP + i,
+                                               SDP_UINT16, &hid_attr2[i + 1]);
+
+       if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("HID keyboard service registered\n");
+
+       return 0;
+}
+
+static int add_hid_wiimote(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, hid_uuid, l2cap_uuid, hidp_uuid;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[3];
+       sdp_data_t *psm, *lang_lst, *lang_lst2, *hid_spec_lst, *hid_spec_lst2;
+       unsigned int i;
+       uint8_t dtd = SDP_UINT16;
+       uint8_t dtd2 = SDP_UINT8;
+       uint8_t dtd_data = SDP_TEXT_STR8;
+       void *dtds[2];
+       void *values[2];
+       void *dtds2[2];
+       void *values2[2];
+       int leng[2];
+       uint8_t hid_spec_type = 0x22;
+       uint16_t hid_attr_lang[] = { 0x409, 0x100 };
+       uint16_t ctrl = 0x11, intr = 0x13;
+       uint16_t hid_release = 0x0100, parser_version = 0x0111;
+       uint8_t subclass = 0x04, country = 0x33;
+       uint8_t virtual_cable = 0, reconnect = 1, sdp_disable = 0;
+       uint8_t battery = 1, remote_wakeup = 1;
+       uint16_t profile_version = 0x0100, superv_timeout = 0x0c80;
+       uint8_t norm_connect = 0, boot_device = 0;
+       const uint8_t hid_spec[] = {
+               0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x10,
+               0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
+               0x01, 0x06, 0x00, 0xff, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x11, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x12, 0x95, 0x02, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x13, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x14, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x15, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x16, 0x95, 0x15, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x17, 0x95, 0x06, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x18, 0x95, 0x15, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x19, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x1a, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
+               0x85, 0x20, 0x95, 0x06, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x21, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x22, 0x95, 0x04, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x30, 0x95, 0x02, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x31, 0x95, 0x05, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x32, 0x95, 0x0a, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x33, 0x95, 0x11, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x34, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x35, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x36, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x37, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x3d, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x3e, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
+               0x85, 0x3f, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
+               0xc0, 0x00
+       };
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&hid_uuid, HID_SVCLASS_ID);
+       svclass_id = sdp_list_append(NULL, &hid_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, HID_PROFILE_ID);
+       profile[0].version = 0x0100;
+       pfseq = sdp_list_append(NULL, profile);
+       sdp_set_profile_descs(&record, pfseq);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto[1] = sdp_list_append(0, &l2cap_uuid);
+       psm = sdp_data_alloc(SDP_UINT16, &ctrl);
+       proto[1] = sdp_list_append(proto[1], psm);
+       apseq = sdp_list_append(0, proto[1]);
+
+       sdp_uuid16_create(&hidp_uuid, HIDP_UUID);
+       proto[2] = sdp_list_append(0, &hidp_uuid);
+       apseq = sdp_list_append(apseq, proto[2]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       proto[1] = sdp_list_append(0, &l2cap_uuid);
+       psm = sdp_data_alloc(SDP_UINT16, &intr);
+       proto[1] = sdp_list_append(proto[1], psm);
+       apseq = sdp_list_append(0, proto[1]);
+
+       sdp_uuid16_create(&hidp_uuid, HIDP_UUID);
+       proto[2] = sdp_list_append(0, &hidp_uuid);
+       apseq = sdp_list_append(apseq, proto[2]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_add_access_protos(&record, aproto);
+
+       add_lang_attr(&record);
+
+       sdp_set_info_attr(&record, "Nintendo RVL-CNT-01",
+                                       "Nintendo", "Nintendo RVL-CNT-01");
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_DEVICE_RELEASE_NUMBER,
+                                               SDP_UINT16, &hid_release);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_PARSER_VERSION,
+                                               SDP_UINT16, &parser_version);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_DEVICE_SUBCLASS,
+                                               SDP_UINT8, &subclass);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_COUNTRY_CODE,
+                                               SDP_UINT8, &country);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_VIRTUAL_CABLE,
+                                               SDP_BOOL, &virtual_cable);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_RECONNECT_INITIATE,
+                                               SDP_BOOL, &reconnect);
+
+       dtds[0] = &dtd2;
+       values[0] = &hid_spec_type;
+       dtds[1] = &dtd_data;
+       values[1] = (uint8_t *) hid_spec;
+       leng[0] = 0;
+       leng[1] = sizeof(hid_spec);
+       hid_spec_lst = sdp_seq_alloc_with_length(dtds, values, leng, 2);
+       hid_spec_lst2 = sdp_data_alloc(SDP_SEQ8, hid_spec_lst);
+       sdp_attr_add(&record, SDP_ATTR_HID_DESCRIPTOR_LIST, hid_spec_lst2);
+
+       for (i = 0; i < sizeof(hid_attr_lang) / 2; i++) {
+               dtds2[i] = &dtd;
+               values2[i] = &hid_attr_lang[i];
+       }
+
+       lang_lst = sdp_seq_alloc(dtds2, values2, sizeof(hid_attr_lang) / 2);
+       lang_lst2 = sdp_data_alloc(SDP_SEQ8, lang_lst);
+       sdp_attr_add(&record, SDP_ATTR_HID_LANG_ID_BASE_LIST, lang_lst2);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_SDP_DISABLE,
+                                               SDP_BOOL, &sdp_disable);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_BATTERY_POWER,
+                                               SDP_BOOL, &battery);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_REMOTE_WAKEUP,
+                                               SDP_BOOL, &remote_wakeup);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_PROFILE_VERSION,
+                                               SDP_UINT16, &profile_version);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_SUPERVISION_TIMEOUT,
+                                               SDP_UINT16, &superv_timeout);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_NORMALLY_CONNECTABLE,
+                                               SDP_BOOL, &norm_connect);
+
+       sdp_attr_add_new(&record, SDP_ATTR_HID_BOOT_DEVICE,
+                                               SDP_BOOL, &boot_device);
+
+       if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("Wii-Mote service registered\n");
+
+       return 0;
+}
+
+static int add_cip(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, l2cap, cmtp, cip;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint16_t psm = si->psm ? si->psm : 0x1001;
+       uint8_t netid = si->network ? si->network : 0x02; // 0x02 = ISDN, 0x03 = GSM
+       sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&cip, CIP_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &cip);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, CIP_PROFILE_ID);
+       profile[0].version = 0x0100;
+       pfseq = sdp_list_append(0, &profile[0]);
+       sdp_set_profile_descs(&record, pfseq);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(0, &l2cap);
+       apseq = sdp_list_append(0, proto[0]);
+       proto[0] = sdp_list_append(proto[0], sdp_data_alloc(SDP_UINT16, &psm));
+       apseq = sdp_list_append(0, proto[0]);
+
+       sdp_uuid16_create(&cmtp, CMTP_UUID);
+       proto[1] = sdp_list_append(0, &cmtp);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       sdp_attr_add(&record, SDP_ATTR_EXTERNAL_NETWORK, network);
+
+       sdp_set_info_attr(&record, "Common ISDN Access", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("CIP service registered\n");
+
+end:
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+       sdp_data_free(network);
+
+       return ret;
+}
+
+static int add_ctp(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, l2cap, tcsbin, ctp;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       uint8_t netid = si->network ? si->network : 0x02; // 0x01-0x07 cf. p120 profile document
+       sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&ctp, CORDLESS_TELEPHONY_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &ctp);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, CORDLESS_TELEPHONY_PROFILE_ID);
+       profile[0].version = 0x0100;
+       pfseq = sdp_list_append(0, &profile[0]);
+       sdp_set_profile_descs(&record, pfseq);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(0, &l2cap);
+       apseq = sdp_list_append(0, proto[0]);
+
+       sdp_uuid16_create(&tcsbin, TCS_BIN_UUID);
+       proto[1] = sdp_list_append(0, &tcsbin);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(0, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       sdp_attr_add(&record, SDP_ATTR_EXTERNAL_NETWORK, network);
+
+       sdp_set_info_attr(&record, "Cordless Telephony", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto end;
+       }
+
+       printf("CTP service registered\n");
+
+end:
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+       sdp_data_free(network);
+
+       return ret;
+}
+
+static int add_a2source(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, l2cap, avdtp, a2src;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       sdp_data_t *psm, *version;
+       uint16_t lp = 0x0019, ver = 0x0100;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&a2src, AUDIO_SOURCE_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &a2src);
+       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, 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(&avdtp, AVDTP_UUID);
+       proto[1] = sdp_list_append(0, &avdtp);
+       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);
+
+       sdp_set_info_attr(&record, "Audio Source", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto done;
+       }
+
+       printf("Audio source service registered\n");
+
+done:
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_a2sink(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+       uuid_t root_uuid, l2cap, avdtp, a2snk;
+       sdp_profile_desc_t profile[1];
+       sdp_list_t *aproto, *proto[2];
+       sdp_record_t record;
+       sdp_data_t *psm, *version;
+       uint16_t lp = 0x0019, ver = 0x0100;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&a2snk, AUDIO_SINK_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &a2snk);
+       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, 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(&avdtp, AVDTP_UUID);
+       proto[1] = sdp_list_append(0, &avdtp);
+       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);
+
+       sdp_set_info_attr(&record, "Audio Sink", 0, 0);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto done;
+       }
+
+       printf("Audio sink service registered\n");
+
+done:
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_avrct(sdp_session_t *session, svc_info_t *si)
+{
+       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 = 0x0017, ver = 0x0100, feat = 0x000f;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &avrct);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+       profile[0].version = 0x0100;
+       pfseq = sdp_list_append(0, &profile[0]);
+       sdp_set_profile_descs(&record, pfseq);
+
+       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);
+
+       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);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto done;
+       }
+
+       printf("Remote control service registered\n");
+
+done:
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_avrtg(sdp_session_t *session, svc_info_t *si)
+{
+       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 = 0x0017, ver = 0x0100, feat = 0x000f;
+       int ret = 0;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
+       svclass_id = sdp_list_append(0, &avrtg);
+       sdp_set_service_classes(&record, svclass_id);
+
+       sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+       profile[0].version = 0x0100;
+       pfseq = sdp_list_append(0, &profile[0]);
+       sdp_set_profile_descs(&record, pfseq);
+
+       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);
+
+       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);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               ret = -1;
+               goto done;
+       }
+
+       printf("Remote target service registered\n");
+
+done:
+       sdp_list_free(proto[0], 0);
+       sdp_list_free(proto[1], 0);
+       sdp_list_free(apseq, 0);
+       sdp_list_free(aproto, 0);
+
+       return ret;
+}
+
+static int add_udi_ue(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass, *proto;
+       uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       uint8_t channel = si->channel ? si->channel: 18;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+       sdp_list_free(root, NULL);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));
+
+       sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+       proto = sdp_list_append(proto, sdp_list_append(
+               sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel)));
+
+       sdp_set_access_protos(&record, sdp_list_append(NULL, proto));
+
+       sdp_uuid16_create(&svclass_uuid, UDI_MT_SVCLASS_ID);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+       sdp_list_free(svclass, NULL);
+
+       sdp_set_info_attr(&record, "UDI UE", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("UDI UE service registered\n");
+
+       return 0;
+}
+
+static int add_udi_te(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass, *proto;
+       uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       uint8_t channel = si->channel ? si->channel: 19;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+       sdp_list_free(root, NULL);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));
+
+       sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+       proto = sdp_list_append(proto, sdp_list_append(
+               sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel)));
+
+       sdp_set_access_protos(&record, sdp_list_append(NULL, proto));
+
+       sdp_uuid16_create(&svclass_uuid, UDI_TA_SVCLASS_ID);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+       sdp_list_free(svclass, NULL);
+
+       sdp_set_info_attr(&record, "UDI TE", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("UDI TE service registered\n");
+
+       return 0;
+}
+
+static unsigned char sr1_uuid[] = {    0xbc, 0x19, 0x9c, 0x24, 0x95, 0x8b, 0x4c, 0xc0,
+                                       0xa2, 0xcb, 0xfd, 0x8a, 0x30, 0xbf, 0x32, 0x06 };
+
+static int add_sr1(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass;
+       uuid_t root_uuid, svclass_uuid;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid128_create(&svclass_uuid, (void *) sr1_uuid);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+
+       sdp_set_info_attr(&record, "TOSHIBA SR-1", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("Toshiba Speech Recognition SR-1 service record registered\n");
+
+       return 0;
+}
+
+static unsigned char syncmls_uuid[] = {        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00,
+                                       0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x02 };
+
+static unsigned char syncmlc_uuid[] = {        0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00,
+                                       0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x02 };
+
+static int add_syncml(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass, *proto;
+       uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
+       uint8_t channel = si->channel ? si->channel: 15;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid128_create(&svclass_uuid, (void *) syncmlc_uuid);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));
+
+       sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+       proto = sdp_list_append(proto, sdp_list_append(
+               sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel)));
+
+       sdp_uuid16_create(&obex_uuid, OBEX_UUID);
+       proto = sdp_list_append(proto, sdp_list_append(NULL, &obex_uuid));
+
+       sdp_set_access_protos(&record, sdp_list_append(NULL, proto));
+
+       sdp_set_info_attr(&record, "SyncML Client", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("SyncML Client service record registered\n");
+
+       return 0;
+}
+
+static unsigned char async_uuid[] = {  0x03, 0x50, 0x27, 0x8F, 0x3D, 0xCA, 0x4E, 0x62,
+                                       0x83, 0x1D, 0xA4, 0x11, 0x65, 0xFF, 0x90, 0x6C };
+
+static int add_activesync(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass, *proto;
+       uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       uint8_t channel = si->channel ? si->channel: 21;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));
+
+       sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+       proto = sdp_list_append(proto, sdp_list_append(
+       sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel)));
+
+       sdp_set_access_protos(&record, sdp_list_append(NULL, proto));
+
+       sdp_uuid128_create(&svclass_uuid, (void *) async_uuid);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+
+       sdp_set_info_attr(&record, "Microsoft ActiveSync", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("ActiveSync service record registered\n");
+
+       return 0;
+}
+
+static unsigned char hotsync_uuid[] = {        0xD8, 0x0C, 0xF9, 0xEA, 0x13, 0x4C, 0x11, 0xD5,
+                                       0x83, 0xCE, 0x00, 0x30, 0x65, 0x7C, 0x54, 0x3C };
+
+static int add_hotsync(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass, *proto;
+       uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       uint8_t channel = si->channel ? si->channel: 22;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));
+
+       sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+       proto = sdp_list_append(proto, sdp_list_append(
+       sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel)));
+
+       sdp_set_access_protos(&record, sdp_list_append(NULL, proto));
+
+       sdp_uuid128_create(&svclass_uuid, (void *) hotsync_uuid);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+
+       sdp_set_info_attr(&record, "PalmOS HotSync", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("HotSync service record registered\n");
+
+       return 0;
+}
+
+static unsigned char palmos_uuid[] = { 0xF5, 0xBE, 0xB6, 0x51, 0x41, 0x71, 0x40, 0x51,
+                                       0xAC, 0xF5, 0x6C, 0xA7, 0x20, 0x22, 0x42, 0xF0 };
+
+static int add_palmos(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass;
+       uuid_t root_uuid, svclass_uuid;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid128_create(&svclass_uuid, (void *) palmos_uuid);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("PalmOS service record registered\n");
+
+       return 0;
+}
+
+static unsigned char nokid_uuid[] = {  0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x10, 0x00,
+                                       0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 };
+
+static int add_nokiaid(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass;
+       uuid_t root_uuid, svclass_uuid;
+       uint16_t verid = 0x005f;
+       sdp_data_t *version = sdp_data_alloc(SDP_UINT16, &verid);
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid128_create(&svclass_uuid, (void *) nokid_uuid);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+
+       sdp_attr_add(&record, SDP_ATTR_SERVICE_VERSION, version);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               sdp_data_free(version);
+               return -1;
+       }
+
+       printf("Nokia ID service record registered\n");
+
+       return 0;
+}
+
+static unsigned char pcsuite_uuid[] = {        0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x10, 0x00,
+                                       0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 };
+
+static int add_pcsuite(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass, *proto;
+       uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid;
+       uint8_t channel = si->channel ? si->channel: 14;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));
+
+       sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+       proto = sdp_list_append(proto, sdp_list_append(
+               sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel)));
+
+       sdp_set_access_protos(&record, sdp_list_append(NULL, proto));
+
+       sdp_uuid128_create(&svclass_uuid, (void *) pcsuite_uuid);
+       svclass = sdp_list_append(NULL, &svclass_uuid);
+       sdp_set_service_classes(&record, svclass);
+
+       sdp_set_info_attr(&record, "Nokia PC Suite", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("Nokia PC Suite service registered\n");
+
+       return 0;
+}
+
+static unsigned char nftp_uuid[] = {   0x00, 0x00, 0x50, 0x05, 0x00, 0x00, 0x10, 0x00,
+                                       0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 };
+
+static unsigned char nsyncml_uuid[] = {        0x00, 0x00, 0x56, 0x01, 0x00, 0x00, 0x10, 0x00,
+                                       0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 };
+
+static unsigned char ngage_uuid[] = {  0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x10, 0x00,
+                                       0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 };
+
+static unsigned char apple_uuid[] = {  0xf0, 0x72, 0x2e, 0x20, 0x0f, 0x8b, 0x4e, 0x90,
+                                       0x8c, 0xc2, 0x1b, 0x46, 0xf5, 0xf2, 0xef, 0xe2 };
+
+static int add_apple(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root;
+       uuid_t root_uuid;
+       uint32_t attr783 = 0x00000000;
+       uint32_t attr785 = 0x00000002;
+       uint16_t attr786 = 0x1234;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_attr_add_new(&record, 0x0780, SDP_UUID128, (void *) apple_uuid);
+       sdp_attr_add_new(&record, 0x0781, SDP_TEXT_STR8, (void *) "Macmini");
+       sdp_attr_add_new(&record, 0x0782, SDP_TEXT_STR8, (void *) "PowerMac10,1");
+       sdp_attr_add_new(&record, 0x0783, SDP_UINT32, (void *) &attr783);
+       sdp_attr_add_new(&record, 0x0784, SDP_TEXT_STR8, (void *) "1.6.6f22");
+       sdp_attr_add_new(&record, 0x0785, SDP_UINT32, (void *) &attr785);
+       sdp_attr_add_new(&record, 0x0786, SDP_UUID16, (void *) &attr786);
+
+       sdp_set_info_attr(&record, "Apple Macintosh Attributes", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("Apple attribute service registered\n");
+
+       return 0;
+}
+
+static int add_isync(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_list_t *root, *svclass, *proto;
+       uuid_t root_uuid, svclass_uuid, serial_uuid, l2cap_uuid, rfcomm_uuid;
+       uint8_t channel = si->channel ? si->channel : 16;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));
+
+       sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+       proto = sdp_list_append(proto, sdp_list_append(
+               sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel)));
+
+       sdp_set_access_protos(&record, sdp_list_append(NULL, proto));
+
+       sdp_uuid16_create(&serial_uuid, SERIAL_PORT_SVCLASS_ID);
+       svclass = sdp_list_append(NULL, &serial_uuid);
+
+       sdp_uuid16_create(&svclass_uuid, APPLE_AGENT_SVCLASS_ID);
+       svclass = sdp_list_append(svclass, &svclass_uuid);
+
+       sdp_set_service_classes(&record, svclass);
+
+       sdp_set_info_attr(&record, "AppleAgent", "Bluetooth acceptor", "Apple Computer Ltd.");
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       printf("Apple iSync service registered\n");
+
+       return 0;
+}
+
+static int add_semchla(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_record_t record;
+       sdp_profile_desc_t profile;
+       sdp_list_t *root, *svclass, *proto, *profiles;
+       uuid_t root_uuid, service_uuid, l2cap_uuid, semchla_uuid;
+       uint16_t psm = 0xf0f9;
+
+       memset(&record, 0, sizeof(record));
+       record.handle = si->handle;
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       proto = sdp_list_append(NULL, sdp_list_append(
+               sdp_list_append(NULL, &l2cap_uuid), sdp_data_alloc(SDP_UINT16, &psm)));
+
+       sdp_uuid32_create(&semchla_uuid, 0x8e770300);
+       proto = sdp_list_append(proto, sdp_list_append(NULL, &semchla_uuid));
+
+       sdp_set_access_protos(&record, sdp_list_append(NULL, proto));
+
+       sdp_uuid32_create(&service_uuid, 0x8e771301);
+       svclass = sdp_list_append(NULL, &service_uuid);
+
+       sdp_set_service_classes(&record, svclass);
+
+       sdp_uuid32_create(&profile.uuid, 0x8e771302);   // Headset
+       //sdp_uuid32_create(&profile.uuid, 0x8e771303); // Phone
+       profile.version = 0x0100;
+       profiles = sdp_list_append(NULL, &profile);
+       sdp_set_profile_descs(&record, profiles);
+
+       sdp_set_info_attr(&record, "SEMC HLA", NULL, NULL);
+
+       if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) {
+               printf("Service Record registration failed\n");
+               return -1;
+       }
+
+       /* SEMC High Level Authentication */
+       printf("SEMC HLA service registered\n");
+
+       return 0;
+}
+
+static int add_gatt(sdp_session_t *session, svc_info_t *si)
+{
+       sdp_list_t *svclass_id, *apseq, *proto[2], *profiles, *root, *aproto;
+       uuid_t root_uuid, proto_uuid, gatt_uuid, l2cap;
+       sdp_profile_desc_t profile;
+       sdp_record_t record;
+       sdp_data_t *psm, *sh, *eh;
+       uint16_t att_psm = 27, start = 0x0001, end = 0x000f;
+       int ret;
+
+       memset(&record, 0, sizeof(sdp_record_t));
+       record.handle = si->handle;
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root = sdp_list_append(NULL, &root_uuid);
+       sdp_set_browse_groups(&record, root);
+       sdp_list_free(root, NULL);
+
+       sdp_uuid16_create(&gatt_uuid, GENERIC_ATTRIB_SVCLASS_ID);
+       svclass_id = sdp_list_append(NULL, &gatt_uuid);
+       sdp_set_service_classes(&record, svclass_id);
+       sdp_list_free(svclass_id, NULL);
+
+       sdp_uuid16_create(&profile.uuid, GENERIC_ATTRIB_PROFILE_ID);
+       profile.version = 0x0100;
+       profiles = sdp_list_append(NULL, &profile);
+       sdp_set_profile_descs(&record, profiles);
+       sdp_list_free(profiles, NULL);
+
+       sdp_uuid16_create(&l2cap, L2CAP_UUID);
+       proto[0] = sdp_list_append(NULL, &l2cap);
+       psm = sdp_data_alloc(SDP_UINT16, &att_psm);
+       proto[0] = sdp_list_append(proto[0], psm);
+       apseq = sdp_list_append(NULL, proto[0]);
+
+       sdp_uuid16_create(&proto_uuid, ATT_UUID);
+       proto[1] = sdp_list_append(NULL, &proto_uuid);
+       sh = sdp_data_alloc(SDP_UINT16, &start);
+       proto[1] = sdp_list_append(proto[1], sh);
+       eh = sdp_data_alloc(SDP_UINT16, &end);
+       proto[1] = sdp_list_append(proto[1], eh);
+       apseq = sdp_list_append(apseq, proto[1]);
+
+       aproto = sdp_list_append(NULL, apseq);
+       sdp_set_access_protos(&record, aproto);
+
+       sdp_set_info_attr(&record, "Generic Attribute Profile", "BlueZ", NULL);
+
+       sdp_set_url_attr(&record, "http://www.bluez.org/",
+                       "http://www.bluez.org/", "http://www.bluez.org/");
+
+       sdp_set_service_id(&record, gatt_uuid);
+
+       ret = sdp_device_record_register(session, &interface, &record,
+                                                       SDP_RECORD_PERSIST);
+       if (ret < 0)
+               printf("Service Record registration failed\n");
+       else
+               printf("Generic Attribute Profile Service registered\n");
+
+       sdp_data_free(psm);
+       sdp_data_free(sh);
+       sdp_data_free(eh);
+       sdp_list_free(proto[0], NULL);
+       sdp_list_free(proto[1], NULL);
+       sdp_list_free(apseq, NULL);
+       sdp_list_free(aproto, NULL);
+
+       return ret;
+}
+
+struct {
+       char            *name;
+       uint32_t        class;
+       int             (*add)(sdp_session_t *sess, svc_info_t *si);
+       unsigned char *uuid;
+} service[] = {
+       { "DID",        PNP_INFO_SVCLASS_ID,            NULL,           },
+
+       { "SP",         SERIAL_PORT_SVCLASS_ID,         add_sp          },
+       { "DUN",        DIALUP_NET_SVCLASS_ID,          add_dun         },
+       { "LAN",        LAN_ACCESS_SVCLASS_ID,          add_lan         },
+       { "FAX",        FAX_SVCLASS_ID,                 add_fax         },
+       { "OPUSH",      OBEX_OBJPUSH_SVCLASS_ID,        add_opush       },
+       { "FTP",        OBEX_FILETRANS_SVCLASS_ID,      add_ftp         },
+       { "PRINT",      DIRECT_PRINTING_SVCLASS_ID,     add_directprint },
+
+       { "HS",         HEADSET_SVCLASS_ID,             add_headset     },
+       { "HSAG",       HEADSET_AGW_SVCLASS_ID,         add_headset_ag  },
+       { "HF",         HANDSFREE_SVCLASS_ID,           add_handsfree   },
+       { "HFAG",       HANDSFREE_AGW_SVCLASS_ID,       add_handsfree_ag},
+       { "SAP",        SAP_SVCLASS_ID,                 add_simaccess   },
+       { "PBAP",       PBAP_SVCLASS_ID,                add_pbap,       },
+
+       { "NAP",        NAP_SVCLASS_ID,                 add_nap         },
+       { "GN",         GN_SVCLASS_ID,                  add_gn          },
+       { "PANU",       PANU_SVCLASS_ID,                add_panu        },
+
+       { "HCRP",       HCR_SVCLASS_ID,                 NULL            },
+       { "HID",        HID_SVCLASS_ID,                 NULL            },
+       { "KEYB",       HID_SVCLASS_ID,                 add_hid_keyb    },
+       { "WIIMOTE",    HID_SVCLASS_ID,                 add_hid_wiimote },
+       { "CIP",        CIP_SVCLASS_ID,                 add_cip         },
+       { "CTP",        CORDLESS_TELEPHONY_SVCLASS_ID,  add_ctp         },
+
+       { "A2SRC",      AUDIO_SOURCE_SVCLASS_ID,        add_a2source    },
+       { "A2SNK",      AUDIO_SINK_SVCLASS_ID,          add_a2sink      },
+       { "AVRCT",      AV_REMOTE_SVCLASS_ID,           add_avrct       },
+       { "AVRTG",      AV_REMOTE_TARGET_SVCLASS_ID,    add_avrtg       },
+
+       { "UDIUE",      UDI_MT_SVCLASS_ID,              add_udi_ue      },
+       { "UDITE",      UDI_TA_SVCLASS_ID,              add_udi_te      },
+
+       { "SEMCHLA",    0x8e771301,                     add_semchla     },
+
+       { "SR1",        0,                              add_sr1,        sr1_uuid        },
+       { "SYNCML",     0,                              add_syncml,     syncmlc_uuid    },
+       { "SYNCMLSERV", 0,                              NULL,           syncmls_uuid    },
+       { "ACTIVESYNC", 0,                              add_activesync, async_uuid      },
+       { "HOTSYNC",    0,                              add_hotsync,    hotsync_uuid    },
+       { "PALMOS",     0,                              add_palmos,     palmos_uuid     },
+       { "NOKID",      0,                              add_nokiaid,    nokid_uuid      },
+       { "PCSUITE",    0,                              add_pcsuite,    pcsuite_uuid    },
+       { "NFTP",       0,                              NULL,           nftp_uuid       },
+       { "NSYNCML",    0,                              NULL,           nsyncml_uuid    },
+       { "NGAGE",      0,                              NULL,           ngage_uuid      },
+       { "APPLE",      0,                              add_apple,      apple_uuid      },
+
+       { "ISYNC",      APPLE_AGENT_SVCLASS_ID,         add_isync,      },
+       { "GATT",       GENERIC_ATTRIB_SVCLASS_ID,      add_gatt,       },
+
+       { 0 }
+};
+
+/* Add local service */
+static int add_service(bdaddr_t *bdaddr, svc_info_t *si)
+{
+       sdp_session_t *sess;
+       int i, ret = -1;
+
+       if (!si->name)
+               return -1;
+
+       sess = sdp_connect(&interface, BDADDR_LOCAL, SDP_RETRY_IF_BUSY);
+       if (!sess)
+               return -1;
+
+       for (i = 0; service[i].name; i++)
+               if (!strcasecmp(service[i].name, si->name)) {
+                       if (service[i].add)
+                               ret = service[i].add(sess, si);
+                       goto done;
+               }
+
+       printf("Unknown service name: %s\n", si->name);
+
+done:
+       free(si->name);
+       sdp_close(sess);
+
+       return ret;
+}
+
+static struct option add_options[] = {
+       { "help",       0, 0, 'h' },
+       { "handle",     1, 0, 'r' },
+       { "psm",        1, 0, 'p' },
+       { "channel",    1, 0, 'c' },
+       { "network",    1, 0, 'n' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *add_help =
+       "Usage:\n"
+       "\tadd [--handle=RECORD_HANDLE --channel=CHANNEL] service\n";
+
+static int cmd_add(int argc, char **argv)
+{
+       svc_info_t si;
+       int opt;
+
+       memset(&si, 0, sizeof(si));
+       si.handle = 0xffffffff;
+
+       for_each_opt(opt, add_options, 0) {
+               switch (opt) {
+               case 'r':
+                       if (strncasecmp(optarg, "0x", 2))
+                               si.handle = atoi(optarg);
+                       else
+                               si.handle = strtol(optarg + 2, NULL, 16);
+                       break;
+               case 'p':
+                       if (strncasecmp(optarg, "0x", 2))
+                               si.psm = atoi(optarg);
+                       else
+                               si.psm = strtol(optarg + 2, NULL, 16);
+                       break;
+               case 'c':
+                       if (strncasecmp(optarg, "0x", 2))
+                               si.channel = atoi(optarg);
+                       else
+                               si.channel = strtol(optarg + 2, NULL, 16);
+                       break;
+               case 'n':
+                       if (strncasecmp(optarg, "0x", 2))
+                               si.network = atoi(optarg);
+                       else
+                               si.network = strtol(optarg + 2, NULL, 16);
+                       break;
+               default:
+                       printf("%s", add_help);
+                       return -1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 1) {
+               printf("%s", add_help);
+               return -1;
+       }
+
+       si.name = strdup(argv[0]);
+
+       return add_service(0, &si);
+}
+
+/* Delete local service */
+static int del_service(bdaddr_t *bdaddr, void *arg)
+{
+       uint32_t handle, range = 0x0000ffff;
+       sdp_list_t *attr;
+       sdp_session_t *sess;
+       sdp_record_t *rec;
+
+       if (!arg) {
+               printf("Record handle was not specified.\n");
+               return -1;
+       }
+
+       sess = sdp_connect(&interface, BDADDR_LOCAL, SDP_RETRY_IF_BUSY);
+       if (!sess) {
+               printf("No local SDP server!\n");
+               return -1;
+       }
+
+       handle = strtoul((char *)arg, 0, 16);
+       attr = sdp_list_append(0, &range);
+       rec = sdp_service_attr_req(sess, handle, SDP_ATTR_REQ_RANGE, attr);
+       sdp_list_free(attr, 0);
+
+       if (!rec) {
+               printf("Service Record not found.\n");
+               sdp_close(sess);
+               return -1;
+       }
+
+       if (sdp_device_record_unregister(sess, &interface, rec)) {
+               printf("Failed to unregister service record: %s\n", strerror(errno));
+               sdp_close(sess);
+               return -1;
+       }
+
+       printf("Service Record deleted.\n");
+       sdp_close(sess);
+
+       return 0;
+}
+
+static struct option del_options[] = {
+       { "help",       0, 0, 'h' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *del_help =
+       "Usage:\n"
+       "\tdel record_handle\n";
+
+static int cmd_del(int argc, char **argv)
+{
+       int opt;
+
+       for_each_opt(opt, del_options, 0) {
+               switch (opt) {
+               default:
+                       printf("%s", del_help);
+                       return -1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 1) {
+               printf("%s", del_help);
+               return -1;
+       }
+
+       return del_service(NULL, argv[0]);
+}
+
+/*
+ * Perform an inquiry and search/browse all peer found.
+ */
+static void inquiry(handler_t handler, void *arg)
+{
+       inquiry_info ii[20];
+       uint8_t count = 0;
+       int i;
+
+       printf("Inquiring ...\n");
+       if (sdp_general_inquiry(ii, 20, 8, &count) < 0) {
+               printf("Inquiry failed\n");
+               return;
+       }
+
+       for (i = 0; i < count; i++)
+               handler(&ii[i].bdaddr, arg);
+}
+
+static void doprintf(void *data, const char *str)
+{
+       printf("%s", str);
+}
+
+/*
+ * Search for a specific SDP service
+ */
+static int do_search(bdaddr_t *bdaddr, struct search_context *context)
+{
+       sdp_list_t *attrid, *search, *seq, *next;
+       uint32_t range = 0x0000ffff;
+       char str[20];
+       sdp_session_t *sess;
+
+       if (!bdaddr) {
+               inquiry(do_search, context);
+               return 0;
+       }
+
+       sess = sdp_connect(&interface, bdaddr, SDP_RETRY_IF_BUSY);
+       ba2str(bdaddr, str);
+       if (!sess) {
+               printf("Failed to connect to SDP server on %s: %s\n", str, strerror(errno));
+               return -1;
+       }
+
+       if (context->view != RAW_VIEW) {
+               if (context->svc)
+                       printf("Searching for %s on %s ...\n", context->svc, str);
+               else
+                       printf("Browsing %s ...\n", str);
+       }
+
+       attrid = sdp_list_append(0, &range);
+       search = sdp_list_append(0, &context->group);
+       if (sdp_service_search_attr_req(sess, search, SDP_ATTR_REQ_RANGE, attrid, &seq)) {
+               printf("Service Search failed: %s\n", strerror(errno));
+               sdp_close(sess);
+               return -1;
+       }
+       sdp_list_free(attrid, 0);
+       sdp_list_free(search, 0);
+
+       for (; seq; seq = next) {
+               sdp_record_t *rec = (sdp_record_t *) seq->data;
+               struct search_context sub_context;
+
+               switch (context->view) {
+               case DEFAULT_VIEW:
+                       /* Display user friendly form */
+                       print_service_attr(rec);
+                       printf("\n");
+                       break;
+               case TREE_VIEW:
+                       /* Display full tree */
+                       print_tree_attr(rec);
+                       printf("\n");
+                       break;
+               case XML_VIEW:
+                       /* Display raw XML tree */
+                       convert_sdp_record_to_xml(rec, 0, doprintf);
+                       break;
+               default:
+                       /* Display raw tree */
+                       print_raw_attr(rec);
+                       break;
+               }
+
+               if (sdp_get_group_id(rec, &sub_context.group) != -1) {
+                       /* Set the subcontext for browsing the sub tree */
+                       memcpy(&sub_context, context, sizeof(struct search_context));
+                       /* Browse the next level down if not done */
+                       if (sub_context.group.value.uuid16 != context->group.value.uuid16)
+                               do_search(bdaddr, &sub_context);
+               }
+               next = seq->next;
+               free(seq);
+               sdp_record_free(rec);
+       }
+
+       sdp_close(sess);
+       return 0;
+}
+
+static struct option browse_options[] = {
+       { "help",       0, 0, 'h' },
+       { "tree",       0, 0, 't' },
+       { "raw",        0, 0, 'r' },
+       { "xml",        0, 0, 'x' },
+       { "uuid",       1, 0, 'u' },
+       { "l2cap",      0, 0, 'l' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *browse_help =
+       "Usage:\n"
+       "\tbrowse [--tree] [--raw] [--xml] [--uuid uuid] [--l2cap] [bdaddr]\n";
+
+/*
+ * Browse the full SDP database (i.e. list all services starting from the
+ * root/top-level).
+ */
+static int cmd_browse(int argc, char **argv)
+{
+       struct search_context context;
+       int opt, num;
+
+       /* Initialise context */
+       memset(&context, '\0', sizeof(struct search_context));
+       /* We want to browse the top-level/root */
+       sdp_uuid16_create(&context.group, PUBLIC_BROWSE_GROUP);
+
+       for_each_opt(opt, browse_options, 0) {
+               switch (opt) {
+               case 't':
+                       context.view = TREE_VIEW;
+                       break;
+               case 'r':
+                       context.view = RAW_VIEW;
+                       break;
+               case 'x':
+                       context.view = XML_VIEW;
+                       break;
+               case 'u':
+                       if (sscanf(optarg, "%i", &num) != 1 || num < 0 || num > 0xffff) {
+                               printf("Invalid uuid %s\n", optarg);
+                               return -1;
+                       }
+                       sdp_uuid16_create(&context.group, num);
+                       break;
+               case 'l':
+                       sdp_uuid16_create(&context.group, L2CAP_UUID);
+                       break;
+               default:
+                       printf("%s", browse_help);
+                       return -1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc >= 1) {
+               bdaddr_t bdaddr;
+               estr2ba(argv[0], &bdaddr);
+               return do_search(&bdaddr, &context);
+       }
+
+       return do_search(NULL, &context);
+}
+
+static struct option search_options[] = {
+       { "help",       0, 0, 'h' },
+       { "bdaddr",     1, 0, 'b' },
+       { "tree",       0, 0, 't' },
+       { "raw",        0, 0, 'r' },
+       { "xml",        0, 0, 'x' },
+       { 0, 0, 0, 0}
+};
+
+static const char *search_help =
+       "Usage:\n"
+       "\tsearch [--bdaddr bdaddr] [--tree] [--raw] [--xml] SERVICE\n"
+       "SERVICE is a name (string) or UUID (0x1002)\n";
+
+/*
+ * Search for a specific SDP service
+ *
+ * Note : we should support multiple services on the command line :
+ *          sdptool search 0x0100 0x000f 0x1002
+ * (this would search a service supporting both L2CAP and BNEP directly in
+ * the top level browse group)
+ */
+static int cmd_search(int argc, char **argv)
+{
+       struct search_context context;
+       unsigned char *uuid = NULL;
+       uint32_t class = 0;
+       bdaddr_t bdaddr;
+       int has_addr = 0;
+       int i;
+       int opt;
+
+       /* Initialise context */
+       memset(&context, '\0', sizeof(struct search_context));
+
+       for_each_opt(opt, search_options, 0) {
+               switch (opt) {
+               case 'b':
+                       estr2ba(optarg, &bdaddr);
+                       has_addr = 1;
+                       break;
+               case 't':
+                       context.view = TREE_VIEW;
+                       break;
+               case 'r':
+                       context.view = RAW_VIEW;
+                       break;
+               case 'x':
+                       context.view = XML_VIEW;
+                       break;
+               default:
+                       printf("%s", search_help);
+                       return -1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 1) {
+               printf("%s", search_help);
+               return -1;
+       }
+
+       /* Note : we need to find a way to support search combining
+        * multiple services */
+       context.svc = strdup(argv[0]);
+       if (!strncasecmp(context.svc, "0x", 2)) {
+               int num;
+               /* This is a UUID16, just convert to int */
+               sscanf(context.svc + 2, "%X", &num);
+               class = num;
+               printf("Class 0x%X\n", class);
+       } else {
+               /* Convert class name to an UUID */
+
+               for (i = 0; service[i].name; i++)
+                       if (strcasecmp(context.svc, service[i].name) == 0) {
+                               class = service[i].class;
+                               uuid = service[i].uuid;
+                               break;
+                       }
+               if (!class && !uuid) {
+                       printf("Unknown service %s\n", context.svc);
+                       return -1;
+               }
+       }
+
+       if (class) {
+               if (class & 0xffff0000)
+                       sdp_uuid32_create(&context.group, class);
+               else {
+                       uint16_t class16 = class & 0xffff;
+                       sdp_uuid16_create(&context.group, class16);
+               }
+       } else
+               sdp_uuid128_create(&context.group, uuid);
+
+       if (has_addr)
+               return do_search(&bdaddr, &context);
+
+       return do_search(NULL, &context);
+}
+
+/*
+ * Show how to get a specific SDP record by its handle.
+ * Not really useful to the user, just show how it can be done...
+ */
+static int get_service(bdaddr_t *bdaddr, struct search_context *context, int quite)
+{
+       sdp_list_t *attrid;
+       uint32_t range = 0x0000ffff;
+       sdp_record_t *rec;
+       sdp_session_t *session = sdp_connect(&interface, bdaddr, SDP_RETRY_IF_BUSY);
+
+       if (!session) {
+               char str[20];
+               ba2str(bdaddr, str);
+               printf("Failed to connect to SDP server on %s: %s\n", str, strerror(errno));
+               return -1;
+       }
+
+       attrid = sdp_list_append(0, &range);
+       rec = sdp_service_attr_req(session, context->handle, SDP_ATTR_REQ_RANGE, attrid);
+       sdp_list_free(attrid, 0);
+       sdp_close(session);
+
+       if (!rec) {
+               if (!quite) {
+                       printf("Service get request failed.\n");
+                       return -1;
+               } else
+                       return 0;
+       }
+
+       switch (context->view) {
+       case DEFAULT_VIEW:
+               /* Display user friendly form */
+               print_service_attr(rec);
+               printf("\n");
+               break;
+       case TREE_VIEW:
+               /* Display full tree */
+               print_tree_attr(rec);
+               printf("\n");
+               break;
+       case XML_VIEW:
+               /* Display raw XML tree */
+               convert_sdp_record_to_xml(rec, 0, doprintf);
+               break;
+       default:
+               /* Display raw tree */
+               print_raw_attr(rec);
+               break;
+       }
+
+       sdp_record_free(rec);
+       return 0;
+}
+
+static struct option records_options[] = {
+       { "help",       0, 0, 'h' },
+       { "tree",       0, 0, 't' },
+       { "raw",        0, 0, 'r' },
+       { "xml",        0, 0, 'x' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *records_help =
+       "Usage:\n"
+       "\trecords [--tree] [--raw] [--xml] bdaddr\n";
+
+/*
+ * Request possible SDP service records
+ */
+static int cmd_records(int argc, char **argv)
+{
+       struct search_context context;
+       uint32_t base[] = { 0x10000, 0x10300, 0x10500,
+                               0x1002e, 0x110b, 0x90000, 0x2008000,
+                                       0x4000000, 0x100000, 0x1000000,
+                                               0x4f491100, 0x4f491200 };
+       bdaddr_t bdaddr;
+       unsigned int i, n, num = 32;
+       int opt, err = 0;
+
+       /* Initialise context */
+       memset(&context, '\0', sizeof(struct search_context));
+
+       for_each_opt(opt, records_options, 0) {
+               switch (opt) {
+               case 't':
+                       context.view = TREE_VIEW;
+                       break;
+               case 'r':
+                       context.view = RAW_VIEW;
+                       break;
+               case 'x':
+                       context.view = XML_VIEW;
+                       break;
+               default:
+                       printf("%s", records_help);
+                       return -1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 1) {
+               printf("%s", records_help);
+               return -1;
+       }
+
+       /* Convert command line parameters */
+       estr2ba(argv[0], &bdaddr);
+
+       for (i = 0; i < sizeof(base) / sizeof(uint32_t); i++)
+               for (n = 0; n < num; n++) {
+                       context.handle = base[i] + n;
+                       err = get_service(&bdaddr, &context, 1);
+                       if (err < 0)
+                               goto done;
+               }
+
+done:
+       return 0;
+}
+
+static struct option get_options[] = {
+       { "help",       0, 0, 'h' },
+       { "bdaddr",     1, 0, 'b' },
+       { "tree",       0, 0, 't' },
+       { "raw",        0, 0, 'r' },
+       { "xml",        0, 0, 'x' },
+       { 0, 0, 0, 0 }
+};
+
+static const char *get_help =
+       "Usage:\n"
+       "\tget [--tree] [--raw] [--xml] [--bdaddr bdaddr] record_handle\n";
+
+/*
+ * Get a specific SDP record on the local SDP server
+ */
+static int cmd_get(int argc, char **argv)
+{
+       struct search_context context;
+       bdaddr_t bdaddr;
+       int has_addr = 0;
+       int opt;
+
+       /* Initialise context */
+       memset(&context, '\0', sizeof(struct search_context));
+
+       for_each_opt(opt, get_options, 0) {
+               switch (opt) {
+               case 'b':
+                       estr2ba(optarg, &bdaddr);
+                       has_addr = 1;
+                       break;
+               case 't':
+                       context.view = TREE_VIEW;
+                       break;
+               case 'r':
+                       context.view = RAW_VIEW;
+                       break;
+               case 'x':
+                       context.view = XML_VIEW;
+                       break;
+               default:
+                       printf("%s", get_help);
+                       return -1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 1) {
+               printf("%s", get_help);
+               return -1;
+       }
+
+       /* Convert command line parameters */
+       context.handle = strtoul(argv[0], 0, 16);
+
+       return get_service(has_addr ? &bdaddr : BDADDR_LOCAL, &context, 0);
+}
+
+static struct {
+       char *cmd;
+       int (*func)(int argc, char **argv);
+       char *doc;
+} command[] = {
+       { "search",  cmd_search,      "Search for a service"          },
+       { "browse",  cmd_browse,      "Browse all available services" },
+       { "records", cmd_records,     "Request all records"           },
+       { "add",     cmd_add,         "Add local service"             },
+       { "del",     cmd_del,         "Delete local service"          },
+       { "get",     cmd_get,         "Get local service"             },
+       { "setattr", cmd_setattr,     "Set/Add attribute to a SDP record"          },
+       { "setseq",  cmd_setseq,      "Set/Add attribute sequence to a SDP record" },
+       { 0, 0, 0 }
+};
+
+static void usage(void)
+{
+       int i, pos = 0;
+
+       printf("sdptool - SDP tool v%s\n", VERSION);
+       printf("Usage:\n"
+               "\tsdptool [options] <command> [command parameters]\n");
+       printf("Options:\n"
+               "\t-h\t\tDisplay help\n"
+               "\t-i\t\tSpecify source interface\n");
+
+       printf("Commands:\n");
+       for (i = 0; command[i].cmd; i++)
+               printf("\t%-4s\t\t%s\n", command[i].cmd, command[i].doc);
+
+       printf("\nServices:\n\t");
+       for (i = 0; service[i].name; i++) {
+               printf("%s ", service[i].name);
+               pos += strlen(service[i].name) + 1;
+               if (pos > 60) {
+                       printf("\n\t");
+                       pos = 0;
+               }
+       }
+       printf("\n");
+}
+
+static struct option main_options[] = {
+       { "help",       0, 0, 'h' },
+       { "device",     1, 0, 'i' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       int i, opt;
+
+       bacpy(&interface, BDADDR_ANY);
+
+       while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
+               switch(opt) {
+               case 'i':
+                       if (!strncmp(optarg, "hci", 3))
+                               hci_devba(atoi(optarg + 3), &interface);
+                       else
+                               str2ba(optarg, &interface);
+                       break;
+
+               case 'h':
+                       usage();
+                       exit(0);
+
+               default:
+                       exit(1);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       optind = 0;
+
+       if (argc < 1) {
+               usage();
+               exit(1);
+       }
+
+       for (i = 0; command[i].cmd; i++)
+               if (strncmp(command[i].cmd, argv[0], 4) == 0)
+                       return command[i].func(argc, argv);
+
+       return 1;
+}
diff --git a/tools/ubcsp.c b/tools/ubcsp.c
new file mode 100644 (file)
index 0000000..93b8c0f
--- /dev/null
@@ -0,0 +1,1180 @@
+/*\r
+ *\r
+ *  BlueZ - Bluetooth protocol stack for Linux\r
+ *\r
+ *  Copyright (C) 2000-2005  CSR Ltd.\r
+ *\r
+ *\r
+ *  Permission is hereby granted, free of charge, to any person obtaining\r
+ *  a copy of this software and associated documentation files (the\r
+ *  "Software"), to deal in the Software without restriction, including\r
+ *  without limitation the rights to use, copy, modify, merge, publish,\r
+ *  distribute, sublicense, and/or sell copies of the Software, and to\r
+ *  permit persons to whom the Software is furnished to do so, subject to\r
+ *  the following conditions:\r
+ *\r
+ *  The above copyright notice and this permission notice shall be included\r
+ *  in all copies or substantial portions of the Software.\r
+ *\r
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+ *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r
+ *  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\r
+ *  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\r
+ *  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\r
+ *  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+ *\r
+ */\r
+\r
+#ifdef HAVE_CONFIG_H\r
+#include <config.h>\r
+#endif\r
+\r
+/*****************************************************************************/\r
+/*****************************************************************************/\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp,c                                                                 **/\r
+/**                                                                         **/\r
+/** MicroBCSP - a very low cost implementation of the BCSP protocol         **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+#include "ubcsp.h"\r
+\r
+#if SHOW_PACKET_ERRORS || SHOW_LE_STATES\r
+#include <stdio.h>\r
+#include <windows.h>\r
+#endif\r
+\r
+static uint16 ubcsp_calc_crc (uint8 ch, uint16 crc);\r
+static uint16 ubcsp_crc_reverse (uint16);\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** Constant Data - ROM                                                     **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+/* This is the storage for the link establishment messages */\r
+\r
+static const uint8 ubcsp_le_buffer[4][4] =\r
+       {\r
+               { 0xDA, 0xDC, 0xED, 0xED },\r
+               { 0xAC, 0xAF, 0xEF, 0xEE },\r
+               { 0xAD, 0xEF, 0xAC, 0xED },\r
+               { 0xDE, 0xAD, 0xD0, 0xD0 },\r
+       };\r
+\r
+/* These are the link establishment headers */\r
+/* The two version are for the CRC and non-CRC varients */\r
+\r
+#if UBCSP_CRC\r
+static const uint8 ubcsp_send_le_header[4] = \r
+       {\r
+               0x40, 0x41, 0x00, 0x7E\r
+       };\r
+#else\r
+static const uint8 ubcsp_send_le_header[4] = \r
+       {\r
+               0x00, 0x41, 0x00, 0xBE\r
+       };\r
+#endif\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** Static Data - RAM                                                       **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+/* This is the storage for all state data for ubcsp */\r
+\r
+static struct ubcsp_configuration ubcsp_config;\r
+\r
+/* This is the ACK packet header - this will be overwritten when\r
+   we create an ack packet */\r
+\r
+static uint8 ubcsp_send_ack_header[4] = \r
+       {\r
+               0x00, 0x00, 0x00, 0x00\r
+       };\r
+\r
+/* This is the deslip lookup table */\r
+\r
+static const uint8 ubcsp_deslip[2] =\r
+       {\r
+               SLIP_FRAME, SLIP_ESCAPE,\r
+       };\r
+\r
+/* This is a state machine table for link establishment */\r
+\r
+static uint8 next_le_packet[16] =\r
+       {\r
+               ubcsp_le_sync,                  // uninit\r
+               ubcsp_le_conf,                  // init\r
+               ubcsp_le_none,                  // active\r
+               ubcsp_le_none,\r
+               ubcsp_le_sync_resp,             // sync_resp\r
+               ubcsp_le_sync_resp,\r
+               ubcsp_le_none,\r
+               ubcsp_le_none,\r
+               ubcsp_le_none,                  // conf_resp\r
+               ubcsp_le_conf_resp,\r
+               ubcsp_le_conf_resp,\r
+               ubcsp_le_none,\r
+       };\r
+\r
+/* This is the storage required for building send and crc data */\r
+\r
+static uint8 ubcsp_send_header[4];\r
+static uint8 ubcsp_send_crc[2];\r
+\r
+/* This is where the receive header is stored before the payload arrives */\r
+\r
+static uint8 ubcsp_receive_header[4];\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** Code - ROM or RAM                                                       **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_initialize                                                        **/\r
+/**                                                                         **/\r
+/** This initializes the state of the ubcsp engine to a known values        **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+void ubcsp_initialize (void)\r
+{\r
+       ubcsp_config.ack_number = 0;\r
+       ubcsp_config.sequence_number = 0;\r
+       ubcsp_config.send_ptr = 0;\r
+       ubcsp_config.send_size = 0;\r
+       ubcsp_config.receive_index = -4;\r
+\r
+       ubcsp_config.delay = 0;\r
+\r
+#if SHOW_LE_STATES\r
+       printf ("Hello Link Uninitialized\n");\r
+#endif\r
+\r
+       ubcsp_config.link_establishment_state = ubcsp_le_uninitialized;\r
+       ubcsp_config.link_establishment_packet = ubcsp_le_sync;\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_send_packet                                                       **/\r
+/**                                                                         **/\r
+/** This sends a packet structure for sending to the ubcsp engine           **/\r
+/** This can only be called when the activity indication from ubcsp_poll    **/\r
+/** indicates that a packet can be sent with UBCSP_PACKET_SENT              **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+void ubcsp_send_packet (struct ubcsp_packet *send_packet)\r
+{\r
+       /* Initialize the send data to the packet we want to send */\r
+\r
+       ubcsp_config.send_packet = send_packet;\r
+\r
+       /* we cannot send the packet at the moment\r
+          when we can at the moment, just set things to 0 */\r
+\r
+       ubcsp_config.send_size = 0;\r
+       ubcsp_config.send_ptr = 0;\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_receive_packet                                                    **/\r
+/**                                                                         **/\r
+/** This sends a packet structure for receiving to the ubcsp engine         **/\r
+/** This can only be called when the activity indication from ubcsp_poll    **/\r
+/** indicates that a packet can be sent with UBCSP_PACKET_RECEIVED          **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+void ubcsp_receive_packet (struct ubcsp_packet *receive_packet)\r
+{\r
+       /* Initialize the receive data to the packet we want to receive */\r
+\r
+       ubcsp_config.receive_packet = receive_packet;\r
+\r
+       /* setup to receive the header first */\r
+\r
+       ubcsp_config.receive_index = -4;\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_calc_crc                                                          **/\r
+/**                                                                         **/\r
+/** Takes the next 8 bit value ch, and updates the crc with this value      **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+\r
+#ifdef UBCSP_CRC\r
+\r
+static uint16 ubcsp_calc_crc (uint8 ch, uint16 crc)\r
+{\r
+       /* Calculate the CRC using the above 16 entry lookup table */\r
+\r
+       static const uint16 crc_table[] =\r
+               {\r
+                       0x0000, 0x1081, 0x2102, 0x3183,\r
+                       0x4204, 0x5285, 0x6306, 0x7387,\r
+                       0x8408, 0x9489, 0xa50a, 0xb58b,\r
+                       0xc60c, 0xd68d, 0xe70e, 0xf78f\r
+               };\r
+\r
+       /* Do this four bits at a time - more code, less space */\r
+\r
+    crc = (crc >> 4) ^ crc_table[(crc ^ ch) & 0x000f];\r
+    crc = (crc >> 4) ^ crc_table[(crc ^ (ch >> 4)) & 0x000f];\r
+\r
+       return crc;\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_crc_reverse                                                       **/\r
+/**                                                                         **/\r
+/** Reserves the bits in crc and returns the new value                      **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+static uint16 ubcsp_crc_reverse (uint16 crc)\r
+{\r
+       int32\r
+               b,\r
+               rev;\r
+\r
+       /* Reserse the bits to compute the actual CRC value */\r
+\r
+       for (b = 0, rev=0; b < 16; b++)\r
+       {\r
+               rev = rev << 1;\r
+               rev |= (crc & 1);\r
+               crc = crc >> 1;\r
+       }\r
+\r
+       return rev;\r
+}\r
+\r
+#endif\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_put_slip_uart                                                     **/\r
+/**                                                                         **/\r
+/** Outputs a single octet to the uart                                      **/\r
+/** If the octet needs to be escaped, then output the escape value          **/\r
+/** and then store the second octet to be output later                      **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+static void ubcsp_put_slip_uart (uint8 ch)\r
+{\r
+       /* output a single UART octet */\r
+\r
+       /* If it needs to be escaped, then output the escape octet\r
+          and set the send_slip_escape so that the next time we\r
+          output the second octet for the escape correctly.\r
+          This is done right at the top of ubcsp_poll */\r
+\r
+       if (ch == SLIP_FRAME)\r
+       {\r
+               put_uart (SLIP_ESCAPE);\r
+               ubcsp_config.send_slip_escape = SLIP_ESCAPE_FRAME;\r
+       }\r
+       else if (ch == SLIP_ESCAPE)\r
+       {\r
+               put_uart (SLIP_ESCAPE);\r
+               ubcsp_config.send_slip_escape = SLIP_ESCAPE_ESCAPE;\r
+       }\r
+       else\r
+       {\r
+               /* Not escaped, so just output octet */\r
+\r
+               put_uart (ch);\r
+       }\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_which_le_payload                                                  **/\r
+/**                                                                         **/\r
+/** Check the payload of this packet, and determine which of the four       **/\r
+/** link establishment packets this was.                                    **/\r
+/** Can return 5 if it is not a valid link establishment packet             **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+static uint32 ubcsp_which_le_payload (const uint8 *payload)\r
+{\r
+       static int32\r
+               octet,\r
+               loop;\r
+\r
+       /* Search through the various link establishment payloads to find\r
+          which one we have received */\r
+\r
+       for (loop = 0; loop < 4; loop ++)\r
+       {\r
+               for (octet = 0; octet < 4; octet ++)\r
+               {\r
+                       if (payload[octet] != ubcsp_le_buffer[loop][octet])\r
+                       {\r
+                               /* Bad match, just to loop again */\r
+                               goto bad_match_loop;\r
+                       }\r
+               }\r
+\r
+               /* All the octets matched, return the value */\r
+\r
+               return loop;\r
+\r
+               /* Jumps out of octet loop if we got a bad match */\r
+bad_match_loop:\r
+               {}\r
+       }\r
+\r
+       /* Non of the link establishment payloads matched - return invalid value */\r
+\r
+       return 5;\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_recevied_packet                                                   **/\r
+/**                                                                         **/\r
+/** This function is called when we have a SLIP END octet and a full        **/\r
+/** packet header and possibly data in the receive packet                   **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+static uint8 ubcsp_recevied_packet (void)\r
+{\r
+       static uint8\r
+               receive_crc,\r
+               receive_seq,\r
+               receive_ack,\r
+               activity;\r
+\r
+#if UBCSP_CRC\r
+       static int32\r
+               loop;\r
+\r
+       static uint16\r
+               crc;\r
+#endif\r
+\r
+       static uint16\r
+               length;\r
+\r
+       /* Keep track of what activity this received packet will cause */\r
+\r
+       activity = 0;\r
+\r
+       /*** Do all error checks that we can ***/\r
+\r
+       /* First check the header checksum */\r
+\r
+       if (((ubcsp_receive_header[0] + ubcsp_receive_header[1] + ubcsp_receive_header[2] + ubcsp_receive_header[3]) & 0xff) != 0xff)\r
+       {\r
+               /* Header Checksum Error */\r
+\r
+#if SHOW_PACKET_ERRORS\r
+               printf ("\n######################## Header Checksum Error %02X %02X %02X %02X\n",\r
+                       ubcsp_receive_header[0],\r
+                       ubcsp_receive_header[1],\r
+                       ubcsp_receive_header[2],\r
+                       ubcsp_receive_header[3]);\r
+#endif\r
+\r
+               /* If we have a header checksum error, send an ack in return\r
+                  this gets a packet to be resent as quickly as possible */\r
+\r
+               ubcsp_config.send_ack = 1;\r
+\r
+               return activity;\r
+       }\r
+\r
+       /* Decode the received packets header */\r
+\r
+       ubcsp_config.receive_packet->reliable = (ubcsp_receive_header[0] & 0x80) >> 7;\r
+\r
+       receive_crc = (ubcsp_receive_header[0] & 0x40) >> 6;\r
+       receive_ack = (ubcsp_receive_header[0] & 0x38) >> 3;\r
+       receive_seq = (ubcsp_receive_header[0] & 0x07);\r
+\r
+       ubcsp_config.receive_packet->channel = (ubcsp_receive_header[1] & 0x0f);\r
+\r
+       length =\r
+               ((ubcsp_receive_header[1] & 0xf0) >> 4) |\r
+               (ubcsp_receive_header[2] << 4);\r
+\r
+#if SHOW_PACKET_ERRORS\r
+       if (ubcsp_config.receive_packet->reliable)\r
+       {\r
+               printf (" : %10d         Recv SEQ: %d ACK %d\n",\r
+                       GetTickCount () % 100000,\r
+                       receive_seq,\r
+                       receive_ack);\r
+       }\r
+       else if (ubcsp_config.receive_packet->channel != 1)\r
+       {\r
+               printf (" : %10d          Recv        ACK %d\n",\r
+                       GetTickCount () % 100000,\r
+                       receive_ack);\r
+       }\r
+#endif\r
+\r
+       /* Check for length errors */\r
+\r
+#if UBCSP_CRC\r
+       if (receive_crc)\r
+       {\r
+               /* If this packet had a CRC, then the length of the payload \r
+                  should be 2 less than the received size of the payload */\r
+\r
+               if (length + 2 != ubcsp_config.receive_index)\r
+               {\r
+                       /* Slip Length Error */\r
+\r
+#if SHOW_PACKET_ERRORS\r
+                       printf ("\n######################## Slip Length Error (With CRC) %d,%d\n", length, ubcsp_config.receive_index - 2);\r
+#endif\r
+\r
+                       /* If we have a payload length error, send an ack in return\r
+                          this gets a packet to be resent as quickly as possible */\r
+\r
+                       ubcsp_config.send_ack = 1;\r
+                       return activity;\r
+               }\r
+\r
+               /* We have a CRC at the end of this packet */\r
+\r
+               ubcsp_config.receive_index -= 2;\r
+\r
+               /* Calculate the packet CRC */\r
+\r
+               crc = 0xffff;\r
+\r
+               /* CRC the packet header */\r
+\r
+               for (loop = 0; loop < 4; loop ++)\r
+               {\r
+                       crc = ubcsp_calc_crc (ubcsp_receive_header[loop], crc);\r
+               }\r
+\r
+               /* CRC the packet payload - without the CRC bytes */\r
+\r
+               for (loop = 0; loop < ubcsp_config.receive_index; loop ++)\r
+               {\r
+                       crc = ubcsp_calc_crc (ubcsp_config.receive_packet->payload[loop], crc);\r
+               }\r
+\r
+               /* Reverse the CRC */\r
+\r
+               crc = ubcsp_crc_reverse (crc);\r
+\r
+               /* Check the CRC is correct */\r
+\r
+               if\r
+               (\r
+                       (((crc & 0xff00) >> 8) != ubcsp_config.receive_packet->payload[ubcsp_config.receive_index]) ||\r
+                       ((crc & 0xff) != ubcsp_config.receive_packet->payload[ubcsp_config.receive_index + 1])\r
+               )\r
+               {\r
+#if SHOW_PACKET_ERRORS\r
+                       printf ("\n######################## CRC Error\n");\r
+#endif\r
+\r
+                       /* If we have a packet crc error, send an ack in return\r
+                          this gets a packet to be resent as quickly as possible */\r
+\r
+                       ubcsp_config.send_ack = 1;\r
+                       return activity;\r
+               }\r
+       }\r
+       else\r
+       {\r
+#endif\r
+               /* No CRC present, so just check the length of payload with that received */\r
+\r
+               if (length != ubcsp_config.receive_index)\r
+               {\r
+                       /* Slip Length Error */\r
+\r
+#if SHOW_PACKET_ERRORS\r
+                       printf ("\n######################## Slip Length Error (No CRC) %d,%d\n", length, ubcsp_config.receive_index);\r
+#endif\r
+\r
+                       /* If we have a payload length error, send an ack in return\r
+                          this gets a packet to be resent as quickly as possible */\r
+\r
+                       ubcsp_config.send_ack = 1;\r
+                       return activity;\r
+               }\r
+#if UBCSP_CRC\r
+       }\r
+#endif\r
+\r
+       /*** We have a fully formed packet having passed all data integrity checks ***/\r
+\r
+       /* Check if we have an ACK for the last packet we sent */\r
+\r
+       if (receive_ack != ubcsp_config.sequence_number)\r
+       {\r
+               /* Since we only have a window size of 1, if the ACK is not equal to SEQ\r
+                  then the packet was sent */\r
+\r
+               if\r
+               (\r
+                       (ubcsp_config.send_packet) &&\r
+                       (ubcsp_config.send_packet->reliable)\r
+               )\r
+               {\r
+                       /* We had sent a reliable packet, so clear this packet\r
+                          Then increament the sequence number for the next packet */\r
+\r
+                       ubcsp_config.send_packet = 0;\r
+                       ubcsp_config.sequence_number ++;\r
+                       ubcsp_config.delay = 0;\r
+\r
+                       /* Notify the caller that we have SENT a packet */\r
+\r
+                       activity |= UBCSP_PACKET_SENT;\r
+               }\r
+       }\r
+\r
+       /*** Now we can concentrate of the packet we have received ***/\r
+\r
+       /* Check for Link Establishment packets */\r
+\r
+       if (ubcsp_config.receive_packet->channel == 1)\r
+       {\r
+               /* Link Establishment */\r
+\r
+               ubcsp_config.delay = 0;\r
+\r
+               /* Find which link establishment packet this payload means\r
+                  This could return 5, meaning none */\r
+\r
+               switch (ubcsp_which_le_payload (ubcsp_config.receive_packet->payload))\r
+               {\r
+                       case 0:\r
+                       {\r
+                               /* SYNC Recv'd */\r
+\r
+#if SHOW_LE_STATES\r
+                               printf ("Recv SYNC\n");\r
+#endif\r
+\r
+                               /* If we receive a SYNC, then we respond to it with a SYNC RESP\r
+                                  but only if we are not active.\r
+                                  If we are active, then we have a PEER RESET */\r
+\r
+                               if (ubcsp_config.link_establishment_state < ubcsp_le_active)\r
+                               {\r
+                                       ubcsp_config.link_establishment_resp = 1;\r
+                               }\r
+                               else\r
+                               {\r
+                                       /* Peer reset !!!! */\r
+\r
+#if SHOW_LE_STATES\r
+                                       printf ("\n\n\n\n\nPEER RESET\n\n");\r
+#endif\r
+\r
+                                       /* Reinitialize the link */\r
+\r
+                                       ubcsp_initialize ();\r
+\r
+                                       /* Tell the host what has happened */\r
+\r
+                                       return UBCSP_PEER_RESET;\r
+                               }\r
+                               break;\r
+                       }\r
+\r
+                       case 1:\r
+                       {\r
+                               /* SYNC RESP Recv'd */\r
+\r
+#if SHOW_LE_STATES\r
+                               printf ("Recv SYNC RESP\n");\r
+#endif\r
+\r
+                               /* If we receive a SYNC RESP, push us into the initialized state */\r
+\r
+                               if (ubcsp_config.link_establishment_state < ubcsp_le_initialized)\r
+                               {\r
+#if SHOW_LE_STATES\r
+                                       printf ("Link Initialized\n");\r
+#endif\r
+                                       ubcsp_config.link_establishment_state = ubcsp_le_initialized;\r
+                               }\r
+\r
+                               break;\r
+                       }\r
+\r
+                       case 2:\r
+                       {\r
+                               /* CONF Recv'd */\r
+\r
+#if SHOW_LE_STATES\r
+                               printf ("Recv CONF\n");\r
+#endif\r
+\r
+                               /* If we receive a CONF, and we are initialized or active\r
+                                  then respond with a CONF RESP */\r
+\r
+                               if (ubcsp_config.link_establishment_state >= ubcsp_le_initialized)\r
+                               {\r
+                                       ubcsp_config.link_establishment_resp = 2;\r
+                               }\r
+\r
+                               break;\r
+                       }\r
+\r
+                       case 3:\r
+                       {\r
+                               /* CONF RESP Recv'd */\r
+\r
+#if SHOW_LE_STATES\r
+                               printf ("Recv CONF RESP\n");\r
+#endif\r
+\r
+                               /* If we received a CONF RESP, then push us into the active state */\r
+\r
+                               if (ubcsp_config.link_establishment_state < ubcsp_le_active)\r
+                               {\r
+#if SHOW_LE_STATES\r
+                                       printf ("Link Active\n");\r
+#endif\r
+\r
+                                       ubcsp_config.link_establishment_state = ubcsp_le_active;\r
+                                       ubcsp_config.send_size = 0;\r
+\r
+                                       return activity | UBCSP_PACKET_SENT;\r
+                               }\r
+\r
+                               break;\r
+                       }\r
+               }\r
+\r
+               /* We have finished processing Link Establishment packets */\r
+       }\r
+       else if (ubcsp_config.receive_index)\r
+       {\r
+               /* We have some payload data we need to process\r
+                  but only if we are active - otherwise, we just ignore it */\r
+\r
+               if (ubcsp_config.link_establishment_state == ubcsp_le_active)\r
+               {\r
+                       if (ubcsp_config.receive_packet->reliable)\r
+                       {\r
+                               /* If the packet we've just received was reliable\r
+                                  then send an ACK */\r
+\r
+                               ubcsp_config.send_ack = 1;\r
+\r
+                               /* We the sequence number we received is the same as \r
+                                  the last ACK we sent, then we have received a packet in sequence */\r
+\r
+                               if (receive_seq == ubcsp_config.ack_number)\r
+                               {\r
+                                       /* Increase the ACK number - which will be sent in the next ACK \r
+                                          or normal packet we send */\r
+\r
+                                       ubcsp_config.ack_number ++;\r
+\r
+                                       /* Set the values in the receive_packet structure, so the caller\r
+                                          knows how much data we have */\r
+\r
+                                       ubcsp_config.receive_packet->length = length;\r
+                                       ubcsp_config.receive_packet = 0;\r
+\r
+                                       /* Tell the caller that we have received a packet, and that it\r
+                                          will be ACK'ed */\r
+\r
+                                       activity |= UBCSP_PACKET_RECEIVED | UBCSP_PACKET_ACK;\r
+                               }\r
+                       }\r
+                       else \r
+                       {\r
+                               /* Set the values in the receive_packet structure, so the caller\r
+                                  knows how much data we have */\r
+\r
+                               ubcsp_config.receive_packet->length = length;\r
+                               ubcsp_config.receive_packet = 0;\r
+\r
+                               /* Tell the caller that we have received a packet */\r
+\r
+                               activity |= UBCSP_PACKET_RECEIVED;\r
+                       }\r
+               }\r
+       }\r
+\r
+       /* Just return any activity that occured */\r
+\r
+       return activity;\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_setup_packet                                                      **/\r
+/**                                                                         **/\r
+/** This function is called to setup a packet to be sent                    **/\r
+/** This allows just a header, or a header and payload to be sent           **/\r
+/** It also allows the header checksum to be precalcuated                   **/\r
+/** or calculated here                                                      **/\r
+/** part1 is always 4 bytes                                                 **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+static void ubcsp_setup_packet (uint8 *part1, uint8 calc, uint8 *part2, uint16 len2)\r
+{\r
+       /* If we need to calculate the checksum, do that now */\r
+\r
+       if (calc)\r
+       {\r
+               part1[3] =\r
+                       ~(part1[0] + part1[1] + part1[2]);\r
+       }\r
+\r
+       /* Setup the header send pointer and size so we can clock this out */\r
+\r
+       ubcsp_config.send_ptr = part1;\r
+       ubcsp_config.send_size = 4;\r
+\r
+       /* Setup the payload send pointer and size */\r
+\r
+       ubcsp_config.next_send_ptr = part2;\r
+       ubcsp_config.next_send_size = len2;\r
+\r
+#if UBCSP_CRC\r
+       /* Initialize the crc as required */\r
+\r
+       ubcsp_config.send_crc = -1;\r
+\r
+       ubcsp_config.need_send_crc = 1;\r
+#endif\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_sent_packet                                                       **/\r
+/**                                                                         **/\r
+/** Called when we have finished sending a packet                           **/\r
+/** If this packet was unreliable, then notify caller, and clear the data   **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+static uint8 ubcsp_sent_packet (void)\r
+{\r
+       if (ubcsp_config.send_packet)\r
+       {\r
+               if (!ubcsp_config.send_packet->reliable)\r
+               {\r
+                       /* We had a packet sent that was unreliable */\r
+\r
+                       /* Forget about this packet */\r
+\r
+                       ubcsp_config.send_packet = 0;\r
+\r
+                       /* Notify caller that they can send another one */\r
+\r
+                       return UBCSP_PACKET_SENT;\r
+               }\r
+       }\r
+\r
+       /* We didn't have a packet, or it was reliable\r
+          Must wait for ACK before allowing another packet to be sent */\r
+\r
+       return 0;\r
+}\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_poll                                                              **/\r
+/**                                                                         **/\r
+/** This is the main function for ubcsp                                     **/\r
+/** It performs a number of tasks                                           **/\r
+/**                                                                         **/\r
+/** 1) Send another octet to the UART - escaping as required                **/\r
+/** 2) Setup the payload to be sent after the header has been sent          **/\r
+/** 3) Send the CRC for the packet if required                              **/\r
+/**                                                                         **/\r
+/** 4) Calculate the next Link Establishment State                          **/\r
+/** 5) Send a Link Establishment packet                                     **/\r
+/** 6) Send a normal packet if available                                    **/\r
+/** 7) Send an ACK packet if required                                       **/\r
+/**                                                                         **/\r
+/** 8) Receive octets from UART and deslip them as required                 **/\r
+/** 9) Place received octets into receive header or receive payload buffer  **/\r
+/** 10) Process received packet when SLIP_END is received                   **/\r
+/**                                                                         **/\r
+/** 11) Keep track of ability of caller to delay recalling                  **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+uint8 ubcsp_poll (uint8 *activity)\r
+{\r
+       uint8\r
+               delay = UBCSP_POLL_TIME_IMMEDIATE;\r
+\r
+       uint8\r
+               value;\r
+\r
+       /* Assume no activity to start with */\r
+\r
+       *activity = 0;\r
+\r
+       /* If we don't have to delay, then send something if we can */\r
+\r
+       if (!ubcsp_config.delay)\r
+       {\r
+               /* Do we have something we are sending to send */\r
+\r
+               if (ubcsp_config.send_size)\r
+               {\r
+                       /* We have something to send so send it */\r
+\r
+                       if (ubcsp_config.send_slip_escape)\r
+                       {\r
+                               /* Last time we send a SLIP_ESCAPE octet\r
+                                  this time send the second escape code */\r
+\r
+                               put_uart (ubcsp_config.send_slip_escape);\r
+\r
+                               ubcsp_config.send_slip_escape = 0;\r
+                       }\r
+                       else\r
+                       {\r
+#if UBCSP_CRC\r
+                               /* get the value to send, and calculate CRC as we go */\r
+\r
+                               value = *ubcsp_config.send_ptr ++;\r
+\r
+                               ubcsp_config.send_crc = ubcsp_calc_crc (value, ubcsp_config.send_crc);\r
+\r
+                               /* Output the octet */\r
+\r
+                               ubcsp_put_slip_uart (value);\r
+#else\r
+                               /* Just output the octet*/\r
+\r
+                               ubcsp_put_slip_uart (*ubcsp_config.send_ptr ++);\r
+#endif\r
+                       }\r
+\r
+                       /* If we did output a SLIP_ESCAPE, then don't process the end of a block */\r
+\r
+                       if ((!ubcsp_config.send_slip_escape) && ((ubcsp_config.send_size = ubcsp_config.send_size - 1) == 0))\r
+                       {\r
+                               /*** We are at the end of a block - either header or payload ***/\r
+\r
+                               /* setup the next block */\r
+\r
+                               ubcsp_config.send_ptr = ubcsp_config.next_send_ptr;\r
+                               ubcsp_config.send_size = ubcsp_config.next_send_size;\r
+                               ubcsp_config.next_send_ptr = 0;\r
+                               ubcsp_config.next_send_size = 0;\r
+\r
+#if UBCSP_CRC\r
+                               /* If we have no successor block\r
+                                  then we might need to send the CRC */\r
+\r
+                               if (!ubcsp_config.send_ptr)\r
+                               {\r
+                                       if (ubcsp_config.need_send_crc)\r
+                                       {\r
+                                               /* reverse the CRC from what we computed along the way */\r
+\r
+                                               ubcsp_config.need_send_crc = 0;\r
+\r
+                                               ubcsp_config.send_crc = ubcsp_crc_reverse (ubcsp_config.send_crc);\r
+\r
+                                               /* Save in the send_crc buffer */\r
+\r
+                                               ubcsp_send_crc[0] = (uint8) (ubcsp_config.send_crc >> 8);\r
+                                               ubcsp_send_crc[1] = (uint8) ubcsp_config.send_crc;\r
+\r
+                                               /* Setup to send this buffer */\r
+\r
+                                               ubcsp_config.send_ptr = ubcsp_send_crc;\r
+                                               ubcsp_config.send_size = 2;\r
+                                       }\r
+                                       else\r
+                                       {\r
+                                               /* We don't need to send the crc\r
+                                                  either we just have, or this packet doesn't include it */\r
+\r
+                                               /* Output the end of FRAME marker */\r
+\r
+                                               put_uart (SLIP_FRAME);\r
+\r
+                                               /* Check if this is an unreliable packet */\r
+\r
+                                               *activity |= ubcsp_sent_packet ();\r
+\r
+                                               /* We've sent the packet, so don't need to have be called quickly soon */\r
+\r
+                                               delay = UBCSP_POLL_TIME_DELAY;\r
+                                       }\r
+                               }\r
+#else\r
+                               /* If we have no successor block\r
+                                  then we might need to send the CRC */\r
+\r
+                               if (!ubcsp_config.send_ptr)\r
+                               {\r
+                                       /* Output the end of FRAME marker */\r
+\r
+                                       put_uart (SLIP_FRAME);\r
+\r
+                                       /* Check if this is an unreliable packet */\r
+\r
+                                       *activity |= ubcsp_sent_packet ();\r
+\r
+                                       /* We've sent the packet, so don't need to have be called quickly soon */\r
+\r
+                                       delay = UBCSP_POLL_TIME_DELAY;\r
+                               }\r
+#endif\r
+                       }\r
+               }\r
+               else if (ubcsp_config.link_establishment_packet == ubcsp_le_none)\r
+               {\r
+                       /* We didn't have something to send\r
+                          AND we have no Link Establishment packet to send */\r
+\r
+                       if (ubcsp_config.link_establishment_resp & 2)\r
+                       {\r
+                               /* Send the start of FRAME packet */\r
+\r
+                               put_uart (SLIP_FRAME);\r
+\r
+                               /* We did require a RESP packet - so setup the send */\r
+\r
+                               ubcsp_setup_packet ((uint8*) ubcsp_send_le_header, 0, (uint8*) ubcsp_le_buffer[ubcsp_le_conf_resp], 4);\r
+\r
+                               /* We have now "sent" this packet */\r
+\r
+                               ubcsp_config.link_establishment_resp = 0;\r
+                       }\r
+                       else if (ubcsp_config.send_packet)\r
+                       {\r
+                               /* There is a packet ready to be sent */\r
+\r
+                               /* Send the start of FRAME packet */\r
+\r
+                               put_uart (SLIP_FRAME);\r
+\r
+                               /* Encode up the packet header using ACK and SEQ numbers */\r
+\r
+                               ubcsp_send_header[0] =\r
+                                       (ubcsp_config.send_packet->reliable << 7) |\r
+#if UBCSP_CRC\r
+                                       0x40 |  /* Always use CRC's */\r
+#endif\r
+                                       (ubcsp_config.ack_number << 3) | \r
+                                       (ubcsp_config.sequence_number);\r
+\r
+                               /* Encode up the packet header's channel and length */\r
+                               ubcsp_send_header[1] =\r
+                                       (ubcsp_config.send_packet->channel & 0x0f) |\r
+                                       ((ubcsp_config.send_packet->length << 4) & 0xf0);\r
+\r
+                               ubcsp_send_header[2] =\r
+                                       (ubcsp_config.send_packet->length >> 4) & 0xff;\r
+\r
+                               /* Let the ubcsp_setup_packet function calculate the header checksum */\r
+\r
+                               ubcsp_setup_packet ((uint8*) ubcsp_send_header, 1, ubcsp_config.send_packet->payload, ubcsp_config.send_packet->length);\r
+\r
+                               /* Don't need to send an ACK - we just place on in this packet */\r
+\r
+                               ubcsp_config.send_ack = 0;\r
+                               \r
+#if SHOW_PACKET_ERRORS\r
+                               printf (" : %10d Send %d Ack %d\n",\r
+                                       GetTickCount () % 100000,\r
+                                       ubcsp_config.sequence_number,\r
+                                       ubcsp_config.ack_number);\r
+#endif\r
+                       }\r
+                       else if (ubcsp_config.send_ack)\r
+                       {\r
+                               /* Send the start of FRAME packet */\r
+\r
+                               put_uart (SLIP_FRAME);\r
+\r
+#if SHOW_PACKET_ERRORS\r
+                               printf (" : %10d Send ACK %d\n",\r
+                                       GetTickCount () % 100000,\r
+                                       ubcsp_config.ack_number);\r
+#endif\r
+\r
+                               /* The ack packet is already computed apart from the first octet */\r
+\r
+                               ubcsp_send_ack_header[0] =\r
+#if UBCSP_CRC\r
+                                       0x40 | \r
+#endif\r
+                                       (ubcsp_config.ack_number << 3);\r
+\r
+                               /* Let the ubcsp_setup_packet function calculate the header checksum */\r
+\r
+                               ubcsp_setup_packet (ubcsp_send_ack_header, 1, 0, 0);\r
+\r
+                               /* We've now sent the ack */\r
+\r
+                               ubcsp_config.send_ack = 0;\r
+                       }\r
+                       else\r
+                       {\r
+                               /* We didn't have a Link Establishment response packet,\r
+                                  a normal packet or an ACK packet to send */\r
+\r
+                               delay = UBCSP_POLL_TIME_DELAY;\r
+                       }\r
+               }\r
+               else\r
+               {\r
+#if SHOW_PACKET_ERRORS\r
+//                     printf (" : %10d Send LE %d\n",\r
+//                             GetTickCount () % 100000,\r
+//                             ubcsp_config.link_establishment_packet);\r
+#endif\r
+\r
+                       /* Send A Link Establishment Message */\r
+\r
+                       put_uart (SLIP_FRAME);\r
+\r
+                       /* Send the Link Establishment header followed by the \r
+                          Link Establishment packet */\r
+\r
+                       ubcsp_setup_packet ((uint8*) ubcsp_send_le_header, 0, (uint8*) ubcsp_le_buffer[ubcsp_config.link_establishment_packet], 4);\r
+\r
+                       /* start sending immediately */\r
+\r
+                       ubcsp_config.delay = 0;\r
+\r
+                       /* workout what the next link establishment packet should be */\r
+\r
+                       ubcsp_config.link_establishment_packet = next_le_packet[ubcsp_config.link_establishment_state + ubcsp_config.link_establishment_resp * 4];\r
+\r
+                       /* We have now delt with any response packet that we needed */\r
+\r
+                       ubcsp_config.link_establishment_resp = 0;\r
+\r
+                       return 0;\r
+               }\r
+       }\r
+\r
+       /* We now need to receive any octets from the UART */\r
+\r
+       while ((ubcsp_config.receive_packet) && (get_uart (&value)))\r
+       {\r
+               /* If the last octet was SLIP_ESCAPE, then special processing is required */\r
+\r
+               if (ubcsp_config.receive_slip_escape)\r
+               {\r
+                       /* WARNING - out of range values are not detected !!!\r
+                          This will probably be caught with the checksum or CRC check */\r
+\r
+                       value = ubcsp_deslip[value - SLIP_ESCAPE_FRAME];\r
+\r
+                       ubcsp_config.receive_slip_escape = 0;\r
+               }\r
+               else\r
+               {\r
+                       /* Check for the SLIP_FRAME octet - must be start or end of packet */\r
+                       if (value == SLIP_FRAME)\r
+                       {\r
+                               /* If we had a full header then we have a packet */\r
+\r
+                               if (ubcsp_config.receive_index >= 0)\r
+                               {\r
+                                       /* process the received packet */\r
+\r
+                                       *activity |= ubcsp_recevied_packet ();\r
+\r
+                                       if (*activity & UBCSP_PACKET_ACK)\r
+                                       {\r
+                                               /* We need to ACK this packet, then don't delay its sending */\r
+                                               ubcsp_config.delay = 0;\r
+                                       }\r
+                               }\r
+\r
+                               /* Setup to receive the next packet */\r
+\r
+                               ubcsp_config.receive_index = -4;\r
+\r
+                               /* Ok, next octet */\r
+\r
+                               goto finished_receive;\r
+                       }\r
+                       else if (value == SLIP_ESCAPE)\r
+                       {\r
+                               /* If we receive a SLIP_ESCAPE,\r
+                                  then remember to process the next special octet */\r
+\r
+                               ubcsp_config.receive_slip_escape = 1;\r
+\r
+                               goto finished_receive;\r
+                       }\r
+               }\r
+\r
+               if (ubcsp_config.receive_index < 0)\r
+               {\r
+                       /* We are still receiving the header */\r
+\r
+                       ubcsp_receive_header[ubcsp_config.receive_index + 4] = value;\r
+\r
+                       ubcsp_config.receive_index ++;\r
+               }\r
+               else if (ubcsp_config.receive_index < ubcsp_config.receive_packet->length)\r
+               {\r
+                       /* We are receiving the payload */\r
+                       /* We might stop comming here if we are receiving a\r
+                          packet which is longer than the receive_packet->length\r
+                          given by the host */\r
+\r
+                       ubcsp_config.receive_packet->payload[ubcsp_config.receive_index] = value;\r
+\r
+                       ubcsp_config.receive_index ++;\r
+               }\r
+\r
+finished_receive:\r
+               {\r
+               }\r
+       }\r
+\r
+       if (ubcsp_config.delay > 0)\r
+       {\r
+               /* We were delayed so delay some more\r
+                  this could be cancelled if we received something */\r
+\r
+               ubcsp_config.delay --;\r
+       }\r
+       else\r
+       {\r
+               /* We had no delay, so use the delay we just decided to us */\r
+\r
+               ubcsp_config.delay = delay;\r
+       }\r
+\r
+       /* Report the current delay to the user */\r
+\r
+       return ubcsp_config.delay;\r
+}\r
diff --git a/tools/ubcsp.h b/tools/ubcsp.h
new file mode 100644 (file)
index 0000000..6a74e9a
--- /dev/null
@@ -0,0 +1,208 @@
+/*\r
+ *\r
+ *  BlueZ - Bluetooth protocol stack for Linux\r
+ *\r
+ *  Copyright (C) 2000-2005  CSR Ltd.\r
+ *\r
+ *\r
+ *  Permission is hereby granted, free of charge, to any person obtaining\r
+ *  a copy of this software and associated documentation files (the\r
+ *  "Software"), to deal in the Software without restriction, including\r
+ *  without limitation the rights to use, copy, modify, merge, publish,\r
+ *  distribute, sublicense, and/or sell copies of the Software, and to\r
+ *  permit persons to whom the Software is furnished to do so, subject to\r
+ *  the following conditions:\r
+ *\r
+ *  The above copyright notice and this permission notice shall be included\r
+ *  in all copies or substantial portions of the Software.\r
+ *\r
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+ *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r
+ *  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\r
+ *  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\r
+ *  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\r
+ *  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+ *\r
+ */\r
+\r
+#ifndef UBCSP_INCLUDE_H\r
+#define UBCSP_INCLUDE_H\r
+\r
+/*****************************************************************************/\r
+/*****************************************************************************/\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp.h                                                                 **/\r
+/**                                                                         **/\r
+/** MicroBCSP - a very low cost implementation of the BCSP protocol         **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+/* If we wish to use CRC's, then change 0 to 1 in the next line */\r
+#define UBCSP_CRC 1\r
+\r
+/* Define some basic types - change these for your architecture */\r
+typedef unsigned char uint8;\r
+typedef unsigned short uint16;\r
+typedef unsigned int uint32;\r
+typedef signed char int8;\r
+typedef signed short int16;\r
+typedef signed int int32;\r
+\r
+/* The defines below require a printf function to be available */\r
+\r
+/* Do we want to show packet errors in debug output */\r
+#define SHOW_PACKET_ERRORS     0\r
+\r
+/* Do we want to show Link Establishment State transitions in debug output */\r
+#define SHOW_LE_STATES         0\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_packet                                                            **/\r
+/**                                                                         **/\r
+/** This is description of a bcsp packet for the upper layer                **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+struct ubcsp_packet\r
+{\r
+       uint8 channel;          /* Which Channel this packet is to/from */\r
+       uint8 reliable;         /* Is this packet reliable */\r
+       uint8 use_crc;          /* Does this packet use CRC data protection */\r
+       uint16 length;          /* What is the length of the payload data */\r
+       uint8 *payload;         /* The payload data itself - size of length */\r
+};\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_configuration                                                     **/\r
+/**                                                                         **/\r
+/** This is the main configuration of the ubcsp engine                      **/\r
+/** All state variables are stored in this structure                        **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+enum ubcsp_link_establishment_state\r
+{\r
+       ubcsp_le_uninitialized,\r
+       ubcsp_le_initialized,\r
+       ubcsp_le_active\r
+};\r
+\r
+enum ubcsp_link_establishment_packet\r
+{\r
+       ubcsp_le_sync,\r
+       ubcsp_le_sync_resp,\r
+       ubcsp_le_conf,\r
+       ubcsp_le_conf_resp,\r
+       ubcsp_le_none\r
+};\r
+\r
+struct ubcsp_configuration\r
+{\r
+       uint8 link_establishment_state;\r
+       uint8 link_establishment_resp;\r
+       uint8 link_establishment_packet;\r
+\r
+       uint8 sequence_number:3;\r
+       uint8 ack_number:3;\r
+       uint8 send_ack;\r
+       struct ubcsp_packet *send_packet;\r
+       struct ubcsp_packet *receive_packet;\r
+\r
+       uint8 receive_header_checksum;\r
+       uint8 receive_slip_escape;\r
+       int32 receive_index;\r
+\r
+       uint8 send_header_checksum;\r
+#ifdef UBCSP_CRC\r
+       uint8 need_send_crc;\r
+       uint16 send_crc;\r
+#endif\r
+       uint8 send_slip_escape;\r
+\r
+       uint8 *send_ptr;\r
+       int32 send_size;\r
+       uint8 *next_send_ptr;\r
+       int32 next_send_size;\r
+\r
+       int8 delay;\r
+};\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** ubcsp_poll sets activity from an OR of these flags                      **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+#define UBCSP_PACKET_SENT 0x01\r
+#define UBCSP_PACKET_RECEIVED 0x02\r
+#define UBCSP_PEER_RESET 0x04\r
+#define UBCSP_PACKET_ACK 0x08\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** This is the functional interface for ucbsp                              **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+void ubcsp_initialize (void);\r
+void ubcsp_send_packet (struct ubcsp_packet *send_packet);\r
+void ubcsp_receive_packet (struct ubcsp_packet *receive_packet);\r
+uint8 ubcsp_poll (uint8 *activity);\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** Slip Escape Values                                                      **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+#define SLIP_FRAME 0xC0\r
+#define SLIP_ESCAPE 0xDB\r
+#define SLIP_ESCAPE_FRAME 0xDC\r
+#define SLIP_ESCAPE_ESCAPE 0xDD\r
+\r
+/*****************************************************************************/\r
+/*****************************************************************************/\r
+/*****************************************************************************/\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** These functions need to be linked into your system                      **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** put_uart outputs a single octet over the UART Tx line                   **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+extern void put_uart (uint8);\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** get_uart receives a single octet over the UART Rx line                  **/\r
+/** if no octet is available, then this returns 0                           **/\r
+/** if an octet was read, then this is returned in the argument and         **/\r
+/**   the function returns 1                                                **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+extern uint8 get_uart (uint8 *);\r
+\r
+/*****************************************************************************/\r
+/**                                                                         **/\r
+/** These defines should be changed to your systems concept of 100ms        **/\r
+/**                                                                         **/\r
+/*****************************************************************************/\r
+\r
+#define UBCSP_POLL_TIME_IMMEDIATE   0\r
+#define UBCSP_POLL_TIME_DELAY       25\r
+\r
+/*****************************************************************************/\r
+/*****************************************************************************/\r
+/*****************************************************************************/\r
+#endif
diff --git a/tracer/main.c b/tracer/main.c
new file mode 100644 (file)
index 0000000..0806ffe
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#include <glib.h>
+
+#ifdef HAVE_CAPNG
+#include <cap-ng.h>
+#endif
+
+static GMainLoop *event_loop;
+
+static void sig_term(int sig)
+{
+       g_main_loop_quit(event_loop);
+}
+
+static gboolean option_detach = TRUE;
+static gboolean option_debug = FALSE;
+
+static GOptionEntry options[] = {
+       { "nodaemon", 'n', G_OPTION_FLAG_REVERSE,
+                               G_OPTION_ARG_NONE, &option_detach,
+                               "Don't run as daemon in background" },
+       { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug,
+                               "Enable debug information output" },
+       { NULL },
+};
+
+static void debug(const char *format, ...)
+{
+       va_list ap;
+
+       if (!option_debug)
+               return;
+
+       va_start(ap, format);
+
+       vsyslog(LOG_DEBUG, format, ap);
+
+       va_end(ap);
+}
+
+static void sig_debug(int sig)
+{
+       option_debug = !option_debug;
+}
+
+int main(int argc, char *argv[])
+{
+       GOptionContext *context;
+       GError *err = NULL;
+       struct sigaction sa;
+
+#ifdef HAVE_CAPNG
+       /* Drop capabilities */
+       capng_clear(CAPNG_SELECT_BOTH);
+       capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED,
+                                       CAP_NET_BIND_SERVICE, CAP_NET_ADMIN,
+                                               CAP_NET_RAW, CAP_IPC_LOCK, -1);
+       capng_apply(CAPNG_SELECT_BOTH);
+#endif
+
+       context = g_option_context_new(NULL);
+       g_option_context_add_main_entries(context, options, NULL);
+
+       if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) {
+               if (err != NULL) {
+                       g_printerr("%s\n", err->message);
+                       g_error_free(err);
+               } else
+                       g_printerr("An unknown error occurred\n");
+               exit(1);
+       }
+
+       g_option_context_free(context);
+
+       if (option_detach == TRUE) {
+               if (daemon(0, 0)) {
+                       perror("Can't start daemon");
+                       exit(1);
+               }
+       }
+
+       umask(0077);
+
+       openlog("hcitrace", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
+
+       syslog(LOG_INFO, "HCI trace deamon %s", VERSION);
+
+       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_debug;
+       sigaction(SIGUSR2, &sa, NULL);
+
+       sa.sa_handler = SIG_IGN;
+       sigaction(SIGPIPE, &sa, NULL);
+
+       if (option_debug == TRUE) {
+               syslog(LOG_INFO, "Enabling debug information");
+       }
+
+       event_loop = g_main_loop_new(NULL, FALSE);
+
+       debug("Entering main loop");
+
+       g_main_loop_run(event_loop);
+
+       g_main_loop_unref(event_loop);
+
+       syslog(LOG_INFO, "Exit");
+
+       closelog();
+
+       return 0;
+}