eclair snapshot
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d5b85d3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,83 @@
+*.o
+*.a
+*.lo
+*.la
+*.so
+.deps
+.libs
+Makefile
+Makefile.in
+aclocal.m4
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+compile
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+autom4te.cache
+
+ylwrap
+bluez.pc
+include/bluetooth
+src/bluetoothd
+audio/telephony.c
+scripts/bluetooth.rules
+
+sbc/sbcdec
+sbc/sbcenc
+sbc/sbcinfo
+sbc/sbctester
+
+tools/avctrl
+tools/avinfo
+tools/bccmd
+tools/ciptool
+tools/dfubabel
+tools/dfutool
+tools/hciattach
+tools/hciconfig
+tools/hcieventmask
+tools/hcisecfilter
+tools/hcitool
+tools/hid2hci
+tools/l2ping
+tools/ppporc
+tools/sdptool
+audio/ipctest
+cups/bluetooth
+test/agent
+test/bdaddr
+test/hciemu
+test/attest
+test/hstest
+test/avtest
+test/l2test
+test/rctest
+test/scotest
+test/sdptest
+test/lmptest
+test/btiotest
+rfcomm/rfcomm
+compat/dund
+compat/hidd
+compat/pand
+common/test_textfile
+
+doc/*.bak
+doc/*.stamp
+doc/bluez.*
+doc/bluez-*.txt
+doc/*.sgml
+doc/version.xml
+doc/xml
+doc/html
+src/bluetoothd.8
+src/hcid.conf.5
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..cd0189e
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,46 @@
+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>
diff --git a/Android.mk b/Android.mk
new file mode 100755
index 0000000..456efdd
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+ifneq ($(TARGET_SIMULATOR),true)
+ifeq ($(BOARD_HAVE_BLUETOOTH),true)
+  include $(all-subdir-makefiles)
+endif
+endif
diff --git a/COPYING b/COPYING
new file mode 100644
index 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
index 0000000..1f7c8cc
--- /dev/null
+++ b/COPYING.LIB
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <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
index 0000000..e7066b8
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,1230 @@
+ver 4.47:
+	Add support for RFKILL unblock handling.
+	Add support for serial proxy configurations.
+	Add support for caching service class updates.
+	Fix issues with updating SDP service records.
+	Fix usage of limited discoverable mode.
+	Remove deprecated methods and signals for AudioSource.
+
+ver 4.46:
+	Add support for A2DP sink role.
+	Fix clearing svc_cache before the adapter is up.
+	Fix various pointer after free usages.
+	Fix various memory leaks.
+
+ver 4.45:
+	Fix UDEV_DATADIR fallback if pkg-config fails.
+	Fix adapter cleanup and setup prototypes.
+	Fix double-free with out-of-range devices.
+	Fix inband ring setting to be per-headset.
+	Fix handling of Maemo CSD startup.
+
+ver 4.44:
+	Add some missing manual pages.
+	Fix missing number prefix when installing udev rules.
+	Fix program prefix used in Bluetooth udev rules.
+	Fix three-way calling indicator order.
+	Fix downgrade/upgrade of callheld indicator.
+	Fix +CIEV sending when indicator value changes.
+	Fix signal handling for Maemo telephony driver.
+	Fix parsing issues with messages from Maemo CSD.
+	Fix issue with duplicate active calls.
+
+ver 4.43:
+	Add support for udev based on-demand startup.
+	Fix verbose error reporting of CUPS backend.
+	Fix various string length issues.
+	Fix issues with Maemo telephony driver.
+	Fix another device setup and temporary flag issue.
+	Fix and update example agent implementation.
+
+ver 4.42:
+	Add TI WL1271 to Texas Instruments chip list.
+	Add special udev mode to bluetoothd.
+	Fix regression when there is no agent registered.
+	Fix error return when bonding socket hang up.
+	Fix SCO server socket for HFP handsfree role.
+	Fix shutdown on SCO socket before closing.
+	Fix shutdown on A2DP audio stream channel before closing.
+	Fix issue with asserting on AVDTP reference count bugs.
+	Fix authorization denied issue with certain headsets.
+	Fix AVRCP UNITINFO and SUBUNIT INFO responses.
+	Fix discovery cancel issues in case SDP discovery fails.
+
+ver 4.41:
+	Fix pairing even if the ACL gets dropped before successful SDP.
+	Fix regression which caused device to be removed after pairing.
+	Fix HSP record fetching when remote device doesn't support it.
+	Fix SDP discovery canceling when clearing hs->pending.
+	Fix headset never connecting on the first attempt.
+	Fix headset state tracking if bt_search_service() fails.
+	Fix maximum headset connection count check.
+	Fix AVDTP Discover timeout handling.
+	Fix also UI_SET_KEYBIT for the new pause and play key codes.
+
+ver 4.40:
+	Add telephony driver for oFono telephony stack.
+	Add support for Dell specific HID proxy switching.
+	Add support for running hid2hci from udev.
+	Add mapping for AVRCP Play and Pause to dedicated key codes.
+	Fix AVRCP keycodes to better match existing X keymap support.
+	Fix various quoting issues within telephony support.
+	Fix memory allocation issue when generating PDUs for SDP.
+	Fix race condition on device removal.
+	Fix non-cancelable issue with CreateDevice method.
+	Fix non-working CancelDiscovery method call.
+
+ver 4.39:
+	Add workaround for dealing with unknown inquiry complete.
+	Fix discovering when using software scheduler.
+	Fix wrong NoInputNoOutput IO capability string.
+	Fix race condition with agent during pairing.
+	Fix agent cancellation for security mode 3 acceptor failure.
+	Fix temporary flag removal when device creation fails.
+	Fix hciattach to use ppoll instead of poll.
+	Fix service class update when adapter is down.
+	Fix service classes race condition during startup.
+	Fix release of audio client before freeing the device.
+
+ver 4.38:
+	Add support for builtin plugins.
+	Add framework for adapter operations.
+	Add constants for Enhanced Retransmission modes.
+	Fix HCI socket leak in device_remove_bonding.
+	Fix various format string issues.
+	Fix crashes with various free functions.
+	Fix issues with Headset and A2DP drivers to load again.
+	Fix sending AVRCP button released passthrough messages
+	Fix bug which prevent input devices to work after restart.
+	Fix issue with interpretation of UUID-128 as channel.
+
+ver 4.37:
+	Add version value for Bluetooth 3.0 devices.
+	Add additional L2CAP extended feature mask bits.
+	Add support for loading plugins in priority order.
+	Add support for more detailed usage of disconnect watches.
+	Add support for AVRCP volume control.
+	Add saturated clipping of SBC decoder output to 16-bit.
+	Fix potentially infinite recursion of adapter_up.
+	Fix SCO handling in the case of an incoming call.
+	Fix input service to use confirm callback.
+	Fix cleanup of temporary device entries from storage.
+
+ver 4.36:
+	Add proper tracking of AVCTP connect attempts.
+	Add support to channel pattern in Serial interface.
+	Fix A2DP sink crash if removing device while connecting.
+	Fix error handling if HFP indicators aren't initialized.
+	Fix segfault while handling an incoming SCO connection.
+	Fix Serial.Disconnect to abort connection attempt.
+
+ver 4.35:
+	Add support for Handsfree profile headset role.
+	Add additional checks for open SEIDs from clients.
+	Fix device removal while audio IPC client is connected.
+	Fix device removal when an authorization request is pending.
+	Fix incoming AVDTP connect while authorization in progress.
+	Fix disconnection timers for audio support.
+	Fix various potential NULL pointer deferences.
+	Fix callheld indicator value for multiple calls.
+	Fix voice number type usage.
+	Fix GDBus watch handling.
+
+ver 4.34:
+	Add support for version checks of plugins.
+	Add support for class property on adapter interface.
+	Add support for second SDP attempt after connection reset.
+	Add support for more detailed audio states.
+	Add support for HFP+A2DP auto connection feature.
+	Add support for new and improved audio IPC.
+	Add program for testing audio IPC interface.
+	Fix various AVDTP qualification related issues.
+	Fix broken SDP AttributeIdList parsing.
+	Fix invalid memory access of SDP URL handling.
+	Fix local class of device race conditions.
+	Fix issue with periodic inquiry on startup.
+	Fix missing temporary devices in some situations.
+	Fix SBC alignment issue for encoding with four subbands.
+
+ver 4.33:
+	Add Paired property to the DeviceFound signals.
+	Add support for Headset profile 1.2 version.
+	Fix broken network configuration when IPv6 is disabled.
+	Fix network regression that caused disconnection.
+	Fix SDP truncation of strings with NULL values.
+	Fix service discovery handling of CUPS helper.
+
+ver 4.32:
+	Fix broken SDP record handling.
+	Fix SDP data buffer parsing.
+	Fix more SDP memory leaks.
+	Fix read scan enable calls.
+	Fix A2DP stream handling.
+
+ver 4.31:
+	Add support for new BtIO helper library.
+	Fix AVDTP session close issue.
+	Fix SDP memory leaks.
+	Fix various uninitialized memory issues.
+	Fix duplicate signal emissions.
+	Fix property changes request handling.
+	Fix class of device storage handling.
+
+ver 4.30:
+	Add CID field to L2CAP socket address structure.
+	Fix reset of authentication requirements after bonding.
+	Fix storing of link keys when using dedicated bonding.
+	Fix storing of pre-Bluetooth 2.1 link keys.
+	Fix resetting trust settings on every reboot.
+	Fix handling of local name changes.
+	Fix memory leaks in hciconfig and hcitool
+
+ver 4.29:
+	Use AVRCP version 1.0 for now.
+	Decrease AVDTP idle timeout to one second.
+	Delay AVRCP connection when remote device connects A2DP.
+	Add workaround for AVDTP stream setup with broken headsets.
+	Add missing three-way calling feature bit for Handsfree.
+	Fix handsfree callheld indicator updating.
+	Fix parsing of all AT commands within the buffer.
+	Fix authentication replies when disconnected.
+	Fix handling of debug combination keys.
+	Fix handling of changed combination keys.
+	Fix handling of link keys when using no bonding.
+	Fix handling of invalid/unknown authentication requirements.
+	Fix closing of L2CAP raw socket used for dedicated bonding.
+
+ver 4.28:
+	Add AVDTP signal fragmentation support.
+	Add more SBC performance optimizations.
+	Add more SBC audio quality improvements.
+	Use native byte order for audio plugins.
+	Set the adapter alias only after checking the EIR data.
+	Fix auto-disconnect issue with explicit A2DP connections.
+	Fix invalid memory access of ALSA plugin.
+	Fix compilation with -Wsign-compare.
+
+ver 4.27:
+	Add more SBC optimization (MMX and ARM NEON).
+	Add BT_SECURITY and BT_DEFER_SETUP definitions.
+	Add support for deferred connection setup.
+	Add support for fragmentation of data packets.
+	Add option to trigger dedicated bonding.
+	Follow MITM requirements from remote device.
+	Require MITM for dedicated bonding if capabilities allow it.
+	Fix IO capabilities for non-pairing and pairing cases.
+	Fix no-bonding connections in non-bondable mode.
+	Fix new pairing detection with SSP.
+	Fix bonding with pre-2.1 devices and newer kernels.
+	Fix LIAC setting while toggling Pairable property.
+	Fix device creation for incoming security mode 3 connects.
+	Fix crash within A2DP with bogus pointer.
+	Fix issue with sdp_copy_record() function.
+	Fix crash with extract_des() if sdp_uuid_extract() fails.
+
+ver 4.26:
+	Use of constant shift in SBC quantization code.
+	Add possibility to analyze 4 blocks at once in encoder.
+	Fix correct handling of frame sizes in the encoder.
+	Fix for big endian problems in SBC codec.
+	Fix audio client socket to always be non-blocking.
+	Update telephony support for Maemo.
+
+ver 4.25:
+	Fix receiving data over the audio control socket.
+	Fix subbands selection for joint-stereo in SBC encoder.
+	Add new SBC analysis filter function.
+
+ver 4.24:
+	Fix signal emissions when removing adapters.
+	Fix missing adapter signals on exit.
+	Add support for bringing adapters down on exit.
+	Add support for RememberPowered option.
+	Add support for verbose compiler warnings.
+	Add more options to SBC encoder.
+
+ver 4.23:
+	Update audio IPC for better codec handling.
+	Fix bitstream optimization for SBC encoder.
+	Fix length header values of IPC messages.
+	Fix multiple coding style violations.
+	Fix FindDevice to handle temporary devices.
+	Add configuration option for DeviceID.
+	Add support for InitiallyPowered option.
+	Add missing signals for manager properties.
+	Add telephony support for Maemo.
+
+ver 4.22:
+	Add deny statements to D-Bus access policy.
+	Add support for LegacyPairing property.
+	Add support for global properties.
+	Add more commands to telephony testing script.
+	Add sender checks for serial and network interfaces.
+	Remove deprecated methods and signals from input interface.
+	Remove deprecated methods and signals from network interface.
+	Remove OffMode option and always use device down.
+
+ver 4.21:
+	Fix adapter initialization logic.
+	Fix adapter setup and start security manager early.
+	Fix usage issue with first_init variable.
+
+ver 4.20:
+	Cleanup session handling.
+	Cleanup mode setting handling.
+	Fix issue with concurrent audio clients.
+	Fix issue with HFP/HSP suspending.
+	Fix AT result code syntax handling.
+	Add Handsfree support for AT+NREC.
+	Add PairableTimeout adapter property.
+
+ver 4.19:
+	Fix installation of manual pages for old daemons.
+	Fix D-Bus signal emmissions for CreateDevice.
+	Fix issues with UUID probing.
+	Fix +BSRF syntax issue.
+	Add Pairable adapter property.
+	Add sdp_copy_record() library function.
+
+ver 4.18:
+	Fix release before close issue with RFCOMM TTYs.
+	Fix Connected property on input interface.
+	Fix DeviceFound signals during initial name resolving.
+	Fix service discovery handling.
+	Fix duplicate UUID detection.
+	Fix SBC gain mismatch and decoding handling.
+	Add more options to SBC encoder and decoder.
+	Add special any adapter object for service interface.
+	Add variable prefix to adapter and device object paths.
+
+ver 4.17:
+	Fix SBC encoder not writing last frame.
+	Fix missing timer for A2DP suspend.
+	Add more supported devices to hid2hci utility.
+	Add additional functionality to Handsfree support.
+
+ver 4.16:
+	Fix wrong parameter usage of watch callbacks.
+	Fix parameters for callback upon path removal.
+	Fix unloading of adapter drivers.
+
+ver 4.15:
+	Fix various A2DP state machine issues.
+	Fix some issues with the Handsfree error reporting.
+	Fix format string warnings with recent GCC versions.
+	Remove dependency on GModule.
+
+ver 4.14:
+	Fix types of property arrays.
+	Fix potential crash with input devices.
+	Fix PS3 BD remote input event generation.
+	Allow dynamic adapter driver registration.
+	Update udev rules.
+
+ver 4.13:
+	Fix service discovery and UUID handling.
+	Fix bonding issues with Simple Pairing.
+	Fix file descriptor misuse of SCO connections.
+	Fix various memory leaks in the device handling.
+	Fix AVCTP disconnect handling.
+	Fix GStreamer modes for MP3 encoding.
+	Add operator selection to Handsfree support.
+
+ver 4.12:
+	Fix crash with missing icon value.
+	Fix error checks of HAL plugin.
+	Fix SCO server socket cleanup on exit.
+	Fix memory leaks from DBusPendingCall.
+	Fix handling of pending authorization requests.
+	Fix missing protocol UUIDs in record pattern.
+
+ver 4.11:
+	Change SCO server socket into a generic one.
+	Add test script for dummy telephony plugin.
+	Fix uninitialized reply of multiple GetProperties methods.
+
+ver 4.10:
+	Fix memory leaks with HAL messages.
+	Add more advanced handsfree features.
+	Add properties to audio, input and network interfaces.
+	Stop device discovery timer on device removal.
+
+ver 4.9:
+	Fix signals for Powered and Discoverable properties.
+	Fix handling of Alias and Icon properties.
+	Fix duplicate entries for service UUIDs.
+
+ver 4.8:
+	Fix retrieving of formfactor value.
+	Fix retrieving of local and remote extended features.
+	Fix potential NULL pointer dereference during pairing.
+	Fix crash with browsing due to a remotely initated pairing.
+
+ver 4.7:
+	Fix pairing and service discovery logic.
+	Fix crashes during suspend and resume.
+	Fix race condition within devdown mode.
+	Add RequestSession and ReleaseSession methods.
+	Add Powered and Discoverable properties.
+	Add Devices property and deprecate ListDevices.
+	Add workaround for a broken carkit from Nokia.
+
+ver 4.6:
+	Fix Device ID record handling.
+	Fix service browsing and storage.
+	Fix authentication and encryption for input devices.
+	Fix adapter name initialization.
+
+ver 4.5:
+	Fix initialization issue with new adapters.
+	Send HID authentication request without blocking.
+	Hide the verbose SDP debug behind SDP_DEBUG.
+	Add extra UUIDs for service discovery.
+	Add SCO server socket listener.
+	Add authorization support to service plugin.
+
+ver 4.4:
+	Add temporary fix for the CUPS compile issue.
+	Add service-api.txt to distribution.
+	Mention the variable prefix of an object path
+
+ver 4.3:
+	Add dummy driver for telephony support.
+	Add support for discovery sessions.
+	Add service plugin for external services.
+	Various cleanups.
+
+ver 4.2:
+	Avoid memory copies in A2DP write routine.
+	Fix broken logic with Simple Pairing check and old kernels.
+	Allow non-bondable and outgoing SDP without agent.
+	Only remove the bonding for non-temporary devices.
+	Cleanup various unnecessary includes.
+	Make more unexported functions static.
+	Add basic infrastructure for gtk-doc support.
+
+ver 4.1:
+	Add 30 seconds timeout to BNEP connection setup phase.
+	Avoid memory copies in A2DP write routine for ALSA.
+	Make sure to include compat/sdp.h in the distribution.
+
+ver 4.0:
+	Initial public release.
+
+ver 3.36:
+	Add init routines for TI BRF chips.
+	Add extra attributes to the serial port record.
+	Add example record for headset audio gateway record.
+	Use Handsfree version 0x0105 for the gateway role.
+	Fix SDP record registration with specific record handles.
+	Fix BCSP sent/receive handling.
+	Fix various includes for cross-compilation.
+	Allow link mode settings for outgoing connections.
+	Allow bonding during periodic inquiry.
+
+ver 3.35:
+	Add two additional company identifiers.
+	Add UUID-128 support for service discovery.
+	Fix usage of friendly names for service discovery.
+	Fix authorization when experiemental is disabled.
+	Fix uninitialized variable in passkey request handling.
+	Enable output of timestamps for l2test and rctest.
+
+ver 3.34:
+	Replace various SDP functions with safe versions.
+	Add additional length validation for incoming SDP packets.
+	Use safe function versions for SDP client handling.
+	Fix issue with RemoveDevice during discovery procedure.
+	Fix collect for non-persistent service records.
+
+ver 3.33:
+	Add functions for reading and writing the link policy settings.
+	Add definition for authentication requirements.
+	Add support for handling Simple Pairing.
+	Add Simple Pairing support to Agent interface.
+	Add ReleaseMode method to Adapter interface.
+	Add DiscoverServices method to Device interface.
+	Remove obsolete code and cleanup the repository.
+	Move over to use the libgdbus API.
+	Enable PIE by default if supported.
+
+ver 3.32:
+	Add OCF constants for synchronous flow control enabling.
+	Add support for switching HID proxy devices from Dell.
+	Add more Bluetooth client/server helper functions.
+	Add support for input service idle timeout option.
+	Fix BNEP reconnection handling.
+	Fix return value for snd_pcm_hw_params() calls.
+	Use upper-case addresses for object paths.
+	Remove HAL support helpers.
+	Remove inotify support.
+	Remove service daemon activation handling.
+	Remove uneeded D-Bus API extension.
+
+ver 3.31:
+	Create device object for all pairing cases.
+	Convert authorization to internal function calls.
+	Add initial support for Headset Audio Gateway role.
+	Add generic Bluetooth helper functions for GLib.
+	Fix endiannes handling of connection handles.
+	Don't optimize when debug is enabled.
+
+ver 3.30:
+	Convert audio service into a plugin.
+	Convert input service into a plugin.
+	Convert serial service into a plugin.
+	Convert network service into a plugin.
+	Emit old device signals when a property is changed.
+	Fix missing DiscoverDevices and CancelDiscovery methods.
+	Add another company identifier.
+	Add basic support for Bluetooth sessions.
+	Add avinfo utility for AVDTP/A2DP classification.
+	Remove build option for deprecated sdpd binary.
+
+ver 3.29:
+	Introduce new D-Bus based API.
+	Add more SBC optimizations.
+	Add support for PS3 remote devices.
+	Fix alignment trap in SDP server.
+	Fix memory leak in sdp_get_uuidseq_attr function.
+
+ver 3.28:
+	Add support for MCAP UUIDs.
+	Add support for role switch for audio service.
+	Add disconnect timer for audio service.
+	Add disconnect detection to ALSA plugin.
+	Add more SBC optimizations.
+	Fix alignment issue of SDP server.
+	Remove support for SDP parsing via expat.
+
+ver 3.27:
+	Update uinput.h with extra key definitions.
+	Add support for input connect/disconnect callbacks.
+	Add ifdefs around some baud rate definitions.
+	Add another company identifier.
+	Add proper HFP service level connection handling.
+	Add basic headset automatic disconnect support.
+	Add support for new SBC API.
+	Fix SBC decoder noise at high bitpools.
+	Use 32-bit multipliers for further SBC optimization.
+	Check for RFCOMM connection state in SCO connect callback.
+	Make use of parameters selected in ALSA plugin.
+
+ver 3.26:
+	Fix compilation issues with UCHAR_MAX, USHRT_MAX and UINT_MAX.
+	Improve handling of different audio transports.
+	Enable services by default and keep old daemons disabled.
+
+ver 3.25:
+	Add limited support for Handsfree profile.
+	Add limited support for MPEG12/MP3 codec.
+	Add basic support for UNITINFO and SUBUNITINFO.
+	Add more SBC optimizations.
+	Fix external service (un)registration.
+	Allow GetInfo and GetAddress to fail.
+
+ver 3.24:
+	Add definitions for MDP.
+	Add TCP connection support for serial proxy.
+	Add fix for Logitech HID proxy switching.
+	Add missing macros, MIN, MAX, ABS and CLAMP.
+	Add more SBC encoder optimizations.
+	Add initial mechanism to handle headset commands.
+	Fix connecting to handsfree profile headsets.
+	Use proper function for checking signal name.
+
+ver 3.23:
+	Fix remote name request handling bug.
+	Fix key search function to honor the mmap area size.
+	Fix Avahi integration of network service.
+	Add new plugin communication for audio service.
+	Enable basic AVRCP support by default.
+	More optimizations to the SBC library.
+	Create common error definitions.
+
+ver 3.22:
+	Add missing include file from audio service.
+	Add SBC conformance test utility.
+	Add basic uinput support for AVRCP.
+	Fix L2CAP socket leak in audio service.
+	Fix buffer usage in GStreamer plugin.
+	Fix remote name request event handling.
+
+ver 3.21:
+	Add constant for Bluetooth socket options level.
+	Add initial AVRCP support.
+	Add A2DP sink support to GStreamer plugin.
+	Fix interoperability with A2DP suspend.
+	Fix sign error in 8-subband encoder.
+	Fix handling of service classes length size.
+	Store Extended Inquiry Response data information.
+	Publish device id information through EIR.
+	Support higher baud rates for Ericcson based chips.
+
+ver 3.20:
+	Fix GStreamer plugin file type detection.
+	Fix potential infinite loop in inotify support.
+	Fix D-Bus signatures for dict handling.
+	Fix issues with service activation.
+	Fix SDP failure handling of audio service.
+	Fix various memory leaks in input service.
+	Add secure device creation method to input service.
+	Add service information methods to serial service.
+	Add config file support to network service.
+	Add scripting capability to network service.
+	Add special on-mode handling.
+	Add optimization for SBC encoder.
+	Add tweaks for D-Bus 1.1.x libraries.
+	Add support for inquiry transmit power level.
+
+ver 3.19:
+	Limit range of bitpool announced while in ACP side.
+	Use poll instead of usleep to wait for worker thread.
+	Use default event mask from the specification.
+	Add L2CAP mode constants.
+	Add HID proxy support for Logitech diNovo Edge dongle.
+	Add refresh option to re-request device names.
+	Show correct connection link type.
+
+ver 3.18:
+	Don't allocate memory for the Bluetooth base UUID.
+	Implement proper locking for headsets.
+	Fix various A2DP SEP locking issues.
+	Fix and cleanup audio stream handling.
+	Fix stream starting if suspend request is pending.
+	Fix A2DP and AVDTP endianess problems.
+	Add network timeout and retransmission support.
+	Add more detailed decoding of EIR elements.
+
+ver 3.17:
+	Fix supported commands bit calculation.
+	Fix crashes in audio and network services.
+	Check PAN source and destination roles.
+	Only export the needed symbols for the plugins.
+
+ver 3.16:
+	Update company identifier list.
+	Add support for headsets with SCO audio over HCI.
+	Add support for auto-create through ALSA plugin.
+	Add support for ALSA plugin parameters.
+	Add GStreamer plugin with SBC decoder and encoder.
+	Fix network service NAP, GN and PANU servers.
+	Set EIR information from SDP database.
+
+ver 3.15:
+	Add A2DP support to the audio service.
+	Add proxy support to the serial service.
+	Extract main service class for later use.
+	Set service classes value from SDP database.
+
+ver 3.14:
+	Add missing signals for the adapter interface.
+	Add definitions and functions for Simple Pairing.
+	Add basic commands for Simple Pairing.
+	Add correct Simple Pairing and EIR interaction.
+	Add missing properties for remote information.
+	Add EPoX endian quirk to the input service.
+	Fix HID descriptor import and storage functions.
+	Fix handling of adapters in raw mode.
+	Fix remote device listing methods.
+
+ver 3.13:
+	Fix some issues with the headset support.
+	Fix concurrent pending connection attempts.
+	Fix usage of devname instead of netdev.
+	Add identifier for Nokia SyncML records.
+	Add command for reading the CSR chip revision.
+	Add generic CSR radio test support.
+	Update HCI command table.
+
+ver 3.12:
+	Add missing HCI command text descriptions
+	Add missing HCI commands structures.
+	Add missing HCI event structures.
+	Add common bachk() function.
+	Add support for limited discovery mode.
+	Add support for setting of event mask.
+	Add GetRemoteServiceIdentifiers method.
+	Add skeleton for local D-Bus server.
+	Add headset gain control methods.
+	Fix various headset implementation issues.
+	Fix various serial port service issues.
+	Fix various input service issues.
+	Let CUPS plugin discover printers in range.
+	Improve the BCM2035 UART init routine.
+	Ignore connection events for non-ACL links.
+
+ver 3.11:
+	Update API documentation.
+	Minimize SDP root records and browse groups.
+	Use same decoder for text and URL strings.
+	Fix URL data size handling.
+	Fix SDP pattern extraction for XML.
+	Fix network connection persistent state.
+	Add network connection helper methods.
+	Add initial version of serial port support.
+	Add class of device tracking.
+
+ver 3.10.1:
+	Add option to disable installation of manual pages.
+	Fix input service encryption setup.
+	Fix serial service methods.
+	Fix network service connection handling.
+	Provide a simple init script.
+
+ver 3.10:
+	Add initial version of network service.
+	Add initial version of serial service.
+	Add initial version of input service.
+	Add initial version of audio service.
+	Add authorization framework.
+	Add integer based SBC library.
+	Add version code for Bluetooth 2.1 specification.
+	Add ESCO_LINK connection type constant.
+	Export sdp_uuid32_to_uuid128() function.
+
+ver 3.9:
+	Add RemoteDeviceDisconnectRequested signal.
+	Add updated service framework.
+	Add embedded GLib library.
+	Add support for using system GLib library.
+	Create internal SDP server library.
+
+ver 3.8:
+	Sort discovered devices list based on their RSSI.
+	Send DiscoverableTimeoutChanged signal.
+	Fix local and remote name validity checking.
+	Add ListRemoteDevices and ListRecentRemoteDevices methods.
+	Add basic integration of confirmation concept.
+	Add support for service record description via XML.
+	Add support for external commands to the RFCOMM utility.
+	Add experimental service and authorization API.
+	Add functions for registering binary records.
+
+ver 3.7:
+	Fix class of device handling.
+	Fix error replies with pairing and security mode 3.
+	Fix disconnect method for RFCOMM connections.
+	Add match pattern for service searches.
+	Add support for prioritized watches.
+	Add additional PDU length checks.
+	Fix CSRC value for partial responses.
+
+ver 3.6.1:
+	Fix IO channel race conditions.
+	Fix pairing issues on big endian systems.
+	Fix pairing issues with page timeout errors.
+	Fix pairing state for security mode 3 requests.
+	Switch to user as default security manager mode.
+
+ver 3.6:
+	Update D-Bus based RFCOMM interface support.
+	Use L2CAP raw sockets for HCI connection creation.
+	Add periodic discovery support to the D-Bus interface.
+	Add initial support for device names via EIR.
+	Add proper UTF-8 validation of device names.
+	Add support for the J-Three keyboard.
+	Fix issues with the asynchronous API for SDP.
+
+ver 3.5:
+	Fix and cleanup watch functionality.
+	Add support for periodic inquiry mode.
+	Add support for asynchronous SDP requests.
+	Add more request owner tracking.
+	Add asynchronous API for SDP.
+	Document pageto and discovto options.
+
+ver 3.4:
+	Improve error reporting for failed HCI commands.
+	Improve handling of CancelBonding.
+	Fixed bonding reply message when disconnected.
+	Fix UUID128 string lookup handling.
+	Fix malloc() versus bt_malloc() usage.
+
+ver 3.3:
+	Don't change inquiry mode for Bluetooth 1.1 adapters.
+	Add udev rules for Bluetooth serial PCMCIA cards.
+	Add Cancel and Release methods for passkey agents.
+	Add GetRemoteClass method.
+	Convert to using ppoll() and pselect().
+	Initialize allocated memory to zero.
+	Remove bcm203x firmware loader.
+	Remove kernel specific timeouts.
+	Add additional private data field for SDP sessions.
+	Add host controller to host flow control defines.
+	Add host number of completed packets defines.
+	Initialize various memory to zero before usage.
+
+ver 3.2:
+	Only check for the low-level D-Bus library.
+	Update possible device minor classes.
+	Fix timeout for pending reply.
+	Add more Inquiry with RSSI quirks.
+	Sleep only 100 msecs for device detection.
+	Don't send BondingCreated on link key renewal.
+	Allow storing of all UTF-8 remote device names.
+	Create storage filenames with a generic function.
+	Fix handling of SDP strings.
+	Add adapter type for SDIO cards.
+	Add features bit for link supervision timeout.
+
+ver 3.1:
+	Add missing placeholders for feature bits.
+	Fix handling of raw mode devices.
+	Fix busy loop in UUID extraction routine.
+	Remove inquiry mode setting.
+	Remove auth and encrypt settings.
+
+ver 3.0:
+	Implement the new BlueZ D-Bus API.
+	Fix broken behavior with EVT_CMD_STATUS.
+	Add features bit for pause encryption.
+	Add additional EIR error code.
+	Add more company identifiers.
+	Add another Phonebook Access identifier.
+	Update sniff subrating data structures.
+
+ver 2.25:
+	Use %jx instead of %llx for uint64_t and int64_t.
+	Allow null-terminated text strings.
+	Add UUID for N-Gage games.
+	Add UUID for Apple Macintosh Attributes.
+	Add Apple attributes and iSync records.
+	Add definitions for Apple Agent.
+	Add support for the Handsfree Audio Gateway service.
+	Add support for choosing a specific record handle.
+	Add support for dialup/telephone connections.
+	Add definitions for Apple Agent.
+	Add support for record handle on service registration.
+
+ver 2.24:
+	Fix display of SDP text and data strings.
+	Add support for device scan property.
+	Add support for additional access protocols.
+	Update the D-Bus policy configuration file.
+
+ver 2.23:
+	Update the new D-Bus interface.
+	Make dfutool ready for big endian architectures.
+	Add support for AVRCP specific service records.
+	Add support for writing complex BCCMD commands.
+	Add the new BCCMD interface utility.
+	Add MicroBCSP implementation from CSR.
+	Add constants and definitions for sniff subrating.
+	Add support for allocation of binary text elements.
+	Add HCI emulation tool.
+	Add fake HID support for old EPoX presenters.
+	Reject connections from unknown HID devices.
+	Fix service discovery deadlocks with Samsung D600 phones.
+
+ver 2.22:
+	Remove D-Bus 0.23 support.
+	Add initial version of the new D-Bus interface.
+	Add support for extended inquiry response commands.
+	Add support for the Logitech diNovo Media Desktop Laser.
+	Add compile time buffer checks (FORTIFY SOURCE).
+	Decode reserved LMP feature bits.
+	Fix errno overwrite problems.
+	Fix profile descriptor problem with Samsung phones.
+
+ver 2.21:
+	Move create_dirs() and create_file() into the textfile library.
+	Let textfile_put() also replace the last key value pair.
+	Fix memory leaks with textfile_get() usage.
+	Fix infinite loops and false positive matches.
+	Don't retrieve stored link keys for RAW devices.
+	Document the putkey and delkey commands.
+	Show supported commands also in clear text.
+	Support volatile changes of the BD_ADDR for CSR chips.
+	Add support for identification of supported commands.
+	Add missing OCF declarations for the security filter.
+	Add two new company identifiers.
+
+ver 2.20:
+	Add UUIDs for video distribution profile.
+	Add UUIDs for phonebook access profile.
+	Add attribute identifier for supported repositories.
+	Add definitions for extended inquiry response.
+	Add functions for extended inquiry response.
+	Add support for extended inquiry response.
+	Add support for HotSync service record.
+	Add support for ActiveSync service record.
+	Add ActiveSync networking support.
+	Fix D-Bus crashes with new API versions.
+
+ver 2.19:
+	Fix the GCC 4.0 warnings.
+	Fix the routing for dealing with raw devices.
+	Fix off by one memory allocation error.
+	Fix security problem with escape characters in device name.
+	Add per device service record functions.
+	Send D-Bus signals for inquiry results and remote name resolves.
+	Add support for device specific SDP records.
+
+ver 2.18:
+	Support D-Bus 0.23 and 0.33 API versions.
+	Support reading of complex BCCMD values.
+	Support minimum and maximum encryption key length.
+	Add support for reading and writing the inquiry scan type.
+	Add definitions for connection accept timeout and scan enable.
+	Add support for inquiry scan type.
+	Add tool for the CSR BCCMD interface.
+	Add first draft of the Audio/Video control utility.
+	Add disconnect timer support for the A2DP ALSA plugin.
+	Make SBC parameters configurable.
+	Replace non-printable characters in device names.
+	Remove hci_vhci.h header file.
+	Remove hci_uart.h header file.
+
+ver 2.17:
+	Set the storage directory through ${localstatedir}.
+	Add the textfile library for ASCII based file access.
+	Add support for return link keys event.
+	Add support for voice setting configuration.
+	Add support for page scan timeout configuration.
+	Add support for storing and deleting of stored link keys.
+	Add support for searching for services with UUID-128.
+	Add support for retrieving all possible service records.
+	Add support for a raw mode view of service records.
+	Add support for HID information caching in hidd.
+	Add support for authentication in pand and dund.
+	Add support for changing BD_ADDR of CSR chips.
+	Add pskey utility for changing CSR persistent storage values.
+	Add the firmware upgrade utility.
+	Add connection caching for the A2DP ALSA plugin.
+	Add functions for stored link keys.
+	Add definitions for PIN type and unit key.
+	Add SDP_WAIT_ON_CLOSE flag for sdp_connect().
+	Include stdio.h in bluetooth.h header file.
+	Include sys/socket.h in the header files.
+
+ver 2.16:
+	Store link keys in ASCII based file format.
+	Support device name caching.
+	Support zero length data sizes in l2test.
+	Change default l2ping data size to 44 bytes.
+	Hide the server record and the public browse group root.
+	Read BD_ADDR if not set and if it is a raw device.
+	Add SDP language attributes.
+	Add support for browsing the L2CAP group.
+	Add support for stored pin codes for outgoing connections.
+	Add support for local commands and extended features.
+	Add support for reading CSR panic and fault codes.
+	Add config option for setting the inquiry mode.
+	Add OUI decoding support.
+	Use unlimited inquiry responses as default.
+	Use cached device names for PIN request.
+	Use the clock offset when getting the remote names.
+	Add function for reading local supported commands.
+	Add function for reading local extended features.
+	Add function for reading remote extended features.
+	Add function for getting the remote name with a clock offset.
+	Add function for extracting the OUI from a BD_ADDR.
+	Add inquiry info structure with RSSI and page scan mode.
+	Fix buffer allocation for features to string conversion.
+	Support inquiry with unlimited number of responses.
+
+ver 2.15:
+	Enable the RFCOMM service level security.
+	Add deprecated functions for reading the name.
+	Add command for reading the clock offset.
+	Add command for reading the clock.
+	Add function for reading the clock.
+	Add function for reading the local Bluetooth address.
+	Add function for reading the local supported features.
+	Don't configure raw devices.
+	Don't set inquiry scan or page scan on raw devices.
+	Don't show extended information for raw devices.
+	Support L2CAP signal sizes bigger than 2048 bytes.
+	Cleanup of the socket handling code of the test programs.
+	Use better way for unaligned access.
+	Remove sdp_internal.h and its usage.
+
+ver 2.14:
+	Make use of additional connection information.
+	Use library function for reading the RSSI.
+	Use library function for reading the link quality.
+	Use library function for reading the transmit power level.
+	Use library functions for the link supervision timeout.
+	Add tool for changing the device address.
+	Add function for reading the RSSI.
+	Add function for reading the link quality.
+	Add function for reading the transmit power level.
+	Add functions for the link supervision timeout.
+	Remove deprecated functions.
+	Update AM_PATH_BLUEZ macro.
+
+ver 2.13:
+	Use file permission 0600 for the link key file.
+	Add support for HID attribute descriptions.
+	Add support for Device ID attributes.
+	Add Device ID and HID attribute definitions.
+	Update the UUID constants and its translations.
+	Update L2CAP socket option definitions.
+	Update connection information definitions.
+	Various whitespace cleanups.
+
+ver 2.12:
+	Inherit the device specific options from the default.
+	Use --device for selecting the source device.
+	Add --nosdp option for devices with resource limitation.
+	Add support and parameter option for secure mode.
+	Add a lot of build ids and hardware revisions.
+	Add service classes and profile ids for WAP.
+	Add simple AM_PATH_BLUEZ macro.
+	Update UUID translation tables.
+	Correct kernel interface for CMTP and HIDP support.
+
+ver 2.11:
+	Initial support for the kernel security manager.
+	Various cleanups to avoid inclusion of kernel headers.
+	Fix output when the CUPS backend is called without arguments.
+	Fix problems with a 64 bit userland.
+	Use Bluetooth library functions if available.
+	Use standard numbering scheme of SDP record handles.
+	Use bit zero for vendor packets in the filter type bitmask.
+	Add SIM Access types for service discovery.
+	Add more audio/video profile translations.
+	Add another company identifier.
+	Add the missing HCI error codes.
+	Add RFCOMM socket options.
+	Add definition for the SECURE link mode.
+	Add functions for reading and writing the inquiry mode.
+	Add functions for AFH related settings and information.
+	Add version identifier for the Bluetooth 2.0 specification.
+	Add a master option to the hidd.
+	Add support for changing the link key of a connection.
+	Add support for requesting encryption on keyboards.
+	Add support for revision information of Digianswer devices.
+	Add support for the Zoom, IBM and TDK PCMCIA cards.
+	Add checks for the OpenOBEX and the ALSA libraries.
+	Add experimental mRouter support.
+
+ver 2.10:
+	Use a define for the configuration directory.
+	Fix string initialization for flags translation.
+	Fix and extend the unaligned access macros.
+	Make compiling with debug information optional.
+	Don't override CFLAGS from configure.
+	Check for usb_get_busses() and usb_interrupt_read().
+	Add optional support for compiling with PIE.
+	Make installation of the init scripts optional.
+	Make compiling with debug information optional.
+	Don't override CFLAGS from configure.
+
+ver 2.9:
+	Retry SDP connect if busy in the CUPS backend.
+	Use packet type and allow role switch in hcitool.
+	Use the functions from the USB library for hid2hci.
+	Add Broadcom firmware loader.
+	Add EPoX endian quirk for buggy keyboards.
+	Add L2CAP info type and info result definitions.
+	Add value for L2CAP_CONF_RFC_MODE.
+	Change RSSI value to signed instead of unsigned.
+	Allow UUID32 values as protocol identifiers.
+	Update the autoconf/automake scripts.
+
+ver 2.8:
+	Use LIBS and LDADD instead of LDFLAGS.
+	Use HIDP subclass field for HID boot protocol.
+	Set olen before calling getsockopt() in pand.
+	Restore signals for dev-up script.
+	Add PID file support for pand.
+	Add size parameter to expand_name() in hcid.
+	Add support for audio source and audio sink SDP records.
+	Add support for HID virtual cable unplug.
+	Add support for AmbiCom BT2000C card.
+	Add defines and UUID's for audio/video profiles.
+	Add AVDTP protocol identifier.
+	Add HIDP subclass field.
+	Add PKGConfig support.
+	Fix the event code of inquiry with RSSI.
+	Remove dummy SDP library.
+
+ver 2.7:
+	Fix display of decoded LMP features.
+	Update company identifiers.
+	Add AFH related types.
+	Add first bits from EDR prototyping specification.
+	Add support for inquiry with RSSI.
+	Add HCRP related SDP functions.
+	Add HIDP header file.
+	Add support for getting the AFH channel map.
+	Add support for AFH mode.
+	Add support for inquiry mode.
+	Add Bluetooth backend for CUPS.
+	Add the hid2hci utility.
+	Add the hidd utility.
+	Add the pand utility.
+	Add the dund utility.
+	More endian bug fixes.
+	Give udev some time to create the RFCOMM device nodes.
+	Release the TTY if no device node is found.
+	New startup script for the Bluetooth subsystem.
+	Update to the autoconf stuff.
+
+ver 2.6:
+	Change default prefix to /usr.
+	Add manpages for hcid and hcid.conf.
+	Add the sdpd server daemon.
+	Add the sdptool utility.
+	Add the ciptool utility.
+	Add new company identifiers.
+	Add BNEP and CMTP header files.
+	Add the SDP library.
+	Use R2 for default value of pscan_rep_mode.
+
+ver 2.5:
+	Add decoding of Bluetooth 1.2 features.
+	Add link manager version parameter for Bluetooth 1.2.
+	Add new company identifiers.
+	Add D-Bus support for PIN request.
+	Support for transmit power level.
+	Support for park, sniff and hold mode.
+	Support for role switch.
+	Support for reading the clock offset.
+	Support for requesting authentication.
+	Support for setting connection encryption.
+	Show revision information for Broadcom devices.
+	Replace unprintable characters in device name.
+	Use R1 for default value of pscan_rep_mode.
+	Fix some 64-bit problems.
+	Fix some endian problems.
+	Report an error on PIN helper failure.
+	Update bluepin script for GTK2.
+
+ver 2.4:
+	Increase number of inquiry responses.
+	Support for transmit power level.
+	Display all 8 bytes of the features.
+	Add support for reading and writing of IAC.
+	Correct decoding class of device.
+	Use Ericsson revision command for ST Microelectronics devices.
+	Display AVM firmware version with 'revision' command.
+	New code for CSR specific revision information.
+	Support for ST Microelectronics specific initialization.
+	Support for 3Com card version 3.0.
+	Support for TDK, IBM and Socket cards.
+	Support for initial baud rate.
+	Update man pages.
+	Fixes for some memory leaks.
+
+ver 2.3:
+	Added const qualifiers to appropriate function arguments.
+	Minor fixes.
+	CSR firmware version is now displayed by 'revision' command.
+	Voice command is working properly on big endian machines.
+	Added support for Texas Bluetooth modules.
+	Added support for high UART baud rates on Ericsson modules.
+	BCSP initialization fixes.
+	Support for role switch command (hcitool).
+	RFCOMM config file parser fixes.
+	Update man pages.
+	Removed GLib dependency.
+
+ver 2.2:
+	Updated RFCOMM header file.
+	Additional HCI command and event defines.
+	Support for voice settings (hciconfig).
+	Minor hcitool fixes.
+	Improved configure script.
+	Added Headset testing tool.
+	Updated man pages.
+	RPM package.
+
+ver 2.1.1:
+	Resurrect hci_remote_name.
+
+ver 2.1:
+	Added hci_{read, write}_class_of_dev().
+	Added hci_{read, write}_current_iac_lap().
+	Added hci_write_local_name().
+	Added RFCOMM header file.
+	Minor fixes.
+	Improved BCSP initialization (hciattach).
+	Support for displaying link quality (hcitool).
+	Support for changing link supervision timeout (hcitool).
+	New RFCOMM TTY configuration tool (rfcomm).
+	Minor fixes and updates.
+
+ver 2.0:
+	Additional company IDs.
+	BCSP initialization (hciattach).
+	Minor hciconfig fixes.
+
+ver 2.0-pr13:
+	Support for multiple pairing modes.
+	Link key database handling fixes.
+
+ver 2.0-pre12:
+	Removed max link key limit. Keys never expire.
+	Link key database is always updated. Reread PIN on SIGHUP (hcid).
+	Bluetooth script starts SDPd, if installed.
+	Other minor fixes.
+
+ver 2.0-pre11:
+	Improved link key management and more verbose logging (hcid).
+	Fixed scan command (hcitool).
+
+ver 2.0-pre10:
+	Fix hci_inquiry function to return errors and accept user buffers.
+	New functions hci_devba, hci_devid, hci_for_each_dev and hci_get_route.
+	Additional company IDs.
+	Makefile and other minor fixes.
+	Support for reading RSSI, remote name and changing
+	connection type (hcitool). 
+	Device initialization fixes (hcid).
+	Other minor fixes and improvements.
+	Build environment cleanup and fixes.
+
+ver 2.0-pre9:
+	Improved bluepin. Working X authentication.
+	Improved hcitool. New flexible cmd syntax, additional commands.
+	Human readable display of the device features.
+	LMP features to string translation support.
+	Additional HCI command and event defines.
+	Extended hci_filter API.
+
+ver 2.0-pre8:
+	Additional HCI ioctls and defines.
+	All strings and buffers are allocated dynamically.
+	ba2str, str2ba automatically swap bdaddress.
+	Additional hciconfig commands. Support for ACL and SCO MTU ioctls.
+	Support for Inventel and COM1 UART based devices.
+	Minor hcitool fixes.
+	Improved l2test. New L2CAP test modes.
+	Minor fixes and cleanup.
+
+ver 2.0-pre7:
+	Bluetooth libraries and header files is now a separate package.
+	New build environment uses automake and libtool.
+	Massive header files cleanup.
+	Bluetooth utilities is now a separate package.
+	New build environment uses automake.
+	Moved all config files and security data to /etc/bluetooth.
+	Various cleanups.
+
+ver 2.0-pre6:
+	API cleanup and additions.
+	Improved hcitool.
+	l2test minor output fixes.
+	hciattach opt to display list of supported devices.
+
+ver 2.0-pre4:
+	HCI filter enhancements.
+
+ver 2.0-pre3:
+	Cleanup.
+
+ver 2.0-pre2:
+	Additional HCI library functions.
+	Improved CSR baud rate initialization.
+	PCMCIA scripts fixes and enhancements.
+	Documentation update.
+
+ver 2.0-pre1:
+	New UART initialization utility.
+	Hot plugging support for UART based PCMCIA devices.
+	SCO testing utility.
+	New authentication utility (bluepin).
+	Minor fixes and improvements.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 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
index 0000000..c4c6322
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,20 @@
+
+SUBDIRS = include lib sbc gdbus common plugins src client\
+			network serial input audio tools \
+			rfcomm compat cups test scripts doc
+
+EXTRA_DIST = bluez.m4
+
+pkgconfigdir = $(libdir)/pkgconfig
+
+pkgconfig_DATA = bluez.pc
+
+DISTCHECK_CONFIGURE_FLAGS = --disable-gtk-doc \
+				--disable-udevrules
+
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+MAINTAINERCLEANFILES = Makefile.in \
+	aclocal.m4 configure config.h.in config.sub config.guess \
+	ltmain.sh depcomp compile missing install-sh mkinstalldirs ylwrap
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
index 0000000..3cc6547
--- /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-2009  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/acinclude.m4 b/acinclude.m4
new file mode 100644
index 0000000..248fed3
--- /dev/null
+++ b/acinclude.m4
@@ -0,0 +1,380 @@
+AC_DEFUN([AC_PROG_CC_PIE], [
+	AC_CACHE_CHECK([whether ${CC-cc} accepts -fPIE], ac_cv_prog_cc_pie, [
+		echo 'void f(){}' > conftest.c
+		if test -z "`${CC-cc} -fPIE -pie -c conftest.c 2>&1`"; then
+			ac_cv_prog_cc_pie=yes
+		else
+			ac_cv_prog_cc_pie=no
+		fi
+		rm -rf conftest*
+	])
+])
+
+AC_DEFUN([COMPILER_FLAGS], [
+	if (test "${CFLAGS}" = ""); then
+		CFLAGS="-Wall -O2"
+	fi
+	if (test "$USE_MAINTAINER_MODE" = "yes"); then
+		CFLAGS+=" -Werror -Wextra"
+		CFLAGS+=" -Wno-unused-parameter"
+		CFLAGS+=" -Wno-missing-field-initializers"
+		CFLAGS+=" -Wdeclaration-after-statement"
+		CFLAGS+=" -Wmissing-declarations"
+		CFLAGS+=" -Wredundant-decls"
+		CFLAGS+=" -Wcast-align"
+	fi
+])
+
+AC_DEFUN([GTK_DOC_CHECK], [
+	AC_ARG_WITH([html-dir],
+		AS_HELP_STRING([--with-html-dir=PATH], [path to installed docs]),,
+					[with_html_dir='${datadir}/gtk-doc/html'])
+	HTML_DIR="$with_html_dir"
+	AC_SUBST([HTML_DIR])
+
+	AC_ARG_ENABLE([gtk-doc],
+		AS_HELP_STRING([--enable-gtk-doc], [use gtk-doc to build documentation [[default=no]]]),,
+					[enable_gtk_doc=no])
+
+	if test x$enable_gtk_doc = xyes; then
+		ifelse([$1],[],
+			[PKG_CHECK_EXISTS([gtk-doc],,
+				AC_MSG_ERROR([gtk-doc not installed and --enable-gtk-doc requested]))],
+			[PKG_CHECK_EXISTS([gtk-doc >= $1],,
+				AC_MSG_ERROR([You need to have gtk-doc >= $1 installed to build gtk-doc]))])
+	fi
+
+	AC_MSG_CHECKING([whether to build gtk-doc documentation])
+	AC_MSG_RESULT($enable_gtk_doc)
+
+	AC_PATH_PROGS(GTKDOC_CHECK,gtkdoc-check,)
+
+	AM_CONDITIONAL([ENABLE_GTK_DOC], [test x$enable_gtk_doc = xyes])
+	AM_CONDITIONAL([GTK_DOC_USE_LIBTOOL], [test -n "$LIBTOOL"])
+])
+
+AC_DEFUN([AC_FUNC_PPOLL], [
+	AC_CHECK_FUNC(ppoll, dummy=yes, AC_DEFINE(NEED_PPOLL, 1,
+			[Define to 1 if you need the ppoll() function.]))
+])
+
+AC_DEFUN([AC_INIT_BLUEZ], [
+	AC_PREFIX_DEFAULT(/usr/local)
+
+	if (test "${prefix}" = "NONE"); then
+		dnl no prefix and no sysconfdir, so default to /etc
+		if (test "$sysconfdir" = '${prefix}/etc'); then
+			AC_SUBST([sysconfdir], ['/etc'])
+		fi
+
+		dnl no prefix and no localstatedir, so default to /var
+		if (test "$localstatedir" = '${prefix}/var'); then
+			AC_SUBST([localstatedir], ['/var'])
+		fi
+
+		dnl no prefix and no libexecdir, so default to /lib
+		if (test "$libexecdir" = '${exec_prefix}/libexec'); then
+			AC_SUBST([libexecdir], ['/lib'])
+		fi
+
+		dnl no prefix and no mandir, so use ${prefix}/share/man as default
+		if (test "$mandir" = '${prefix}/man'); then
+			AC_SUBST([mandir], ['${prefix}/share/man'])
+		fi
+
+		prefix="${ac_default_prefix}"
+	fi
+
+	if (test "${libdir}" = '${exec_prefix}/lib'); then
+		libdir="${prefix}/lib"
+	fi
+
+	plugindir="${libdir}/bluetooth/plugins"
+
+	if (test "$sysconfdir" = '${prefix}/etc'); then
+		configdir="${prefix}/etc/bluetooth"
+	else
+		configdir="${sysconfdir}/bluetooth"
+	fi
+
+	if (test "$localstatedir" = '${prefix}/var'); then
+		storagedir="${prefix}/var/lib/bluetooth"
+	else
+		storagedir="${localstatedir}/lib/bluetooth"
+	fi
+
+	AC_DEFINE_UNQUOTED(CONFIGDIR, "${configdir}",
+				[Directory for the configuration files])
+	AC_DEFINE_UNQUOTED(STORAGEDIR, "${storagedir}",
+				[Directory for the storage files])
+
+	AC_SUBST(CONFIGDIR, "${configdir}")
+	AC_SUBST(STORAGEDIR, "${storagedir}")
+
+	UDEV_DATADIR="`$PKG_CONFIG --variable=udevdir udev`"
+	if (test -z "${UDEV_DATADIR}"); then
+		UDEV_DATADIR="${sysconfdir}/udev/rules.d"
+	else
+		UDEV_DATADIR="${UDEV_DATADIR}/rules.d"
+	fi
+	AC_SUBST(UDEV_DATADIR)
+])
+
+AC_DEFUN([AC_PATH_DBUS], [
+	PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.0, dummy=yes,
+				AC_MSG_ERROR(D-Bus library is required))
+	AC_CHECK_LIB(dbus-1, dbus_watch_get_unix_fd, dummy=yes,
+		AC_DEFINE(NEED_DBUS_WATCH_GET_UNIX_FD, 1,
+			[Define to 1 if you need the dbus_watch_get_unix_fd() function.]))
+	AC_SUBST(DBUS_CFLAGS)
+	AC_SUBST(DBUS_LIBS)
+])
+
+AC_DEFUN([AC_PATH_GLIB], [
+	PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.14, dummy=yes,
+				AC_MSG_ERROR(GLib library version 2.14 or later is required))
+	AC_SUBST(GLIB_CFLAGS)
+	AC_SUBST(GLIB_LIBS)
+])
+
+AC_DEFUN([AC_PATH_GSTREAMER], [
+	PKG_CHECK_MODULES(GSTREAMER, gstreamer-0.10 gstreamer-plugins-base-0.10, gstreamer_found=yes, gstreamer_found=no)
+	AC_SUBST(GSTREAMER_CFLAGS)
+	AC_SUBST(GSTREAMER_LIBS)
+	GSTREAMER_PLUGINSDIR=`$PKG_CONFIG --variable=pluginsdir gstreamer-0.10`
+	AC_SUBST(GSTREAMER_PLUGINSDIR)
+])
+
+AC_DEFUN([AC_PATH_PULSE], [
+	PKG_CHECK_MODULES(PULSE, libpulse, pulse_found=yes, pulse_found=no)
+	AC_SUBST(PULSE_CFLAGS)
+	AC_SUBST(PULSE_LIBS)
+])
+
+AC_DEFUN([AC_PATH_ALSA], [
+	PKG_CHECK_MODULES(ALSA, alsa, alsa_found=yes, alsa_found=no)
+	AC_CHECK_LIB(rt, clock_gettime, ALSA_LIBS="$ALSA_LIBS -lrt", alsa_found=no)
+	AC_SUBST(ALSA_CFLAGS)
+	AC_SUBST(ALSA_LIBS)
+])
+
+AC_DEFUN([AC_PATH_USB], [
+	PKG_CHECK_MODULES(USB, libusb, usb_found=yes, usb_found=no)
+	AC_SUBST(USB_CFLAGS)
+	AC_SUBST(USB_LIBS)
+	AC_CHECK_LIB(usb, usb_get_busses, dummy=yes,
+		AC_DEFINE(NEED_USB_GET_BUSSES, 1,
+			[Define to 1 if you need the usb_get_busses() function.]))
+	AC_CHECK_LIB(usb, usb_interrupt_read, dummy=yes,
+		AC_DEFINE(NEED_USB_INTERRUPT_READ, 1,
+			[Define to 1 if you need the usb_interrupt_read() function.]))
+])
+
+AC_DEFUN([AC_PATH_NETLINK], [
+	PKG_CHECK_MODULES(NETLINK, libnl-1, netlink_found=yes, netlink_found=no)
+	AC_SUBST(NETLINK_CFLAGS)
+	AC_SUBST(NETLINK_LIBS)
+])
+
+AC_DEFUN([AC_PATH_SNDFILE], [
+	PKG_CHECK_MODULES(SNDFILE, sndfile, sndfile_found=yes, sndfile_found=no)
+	AC_SUBST(SNDFILE_CFLAGS)
+	AC_SUBST(SNDFILE_LIBS)
+])
+
+AC_DEFUN([AC_ARG_BLUEZ], [
+	debug_enable=no
+	optimization_enable=yes
+	fortify_enable=yes
+	pie_enable=yes
+	sndfile_enable=${sndfile_found}
+	netlink_enable=no
+	hal_enable=${hal_found}
+	usb_enable=${usb_found}
+	alsa_enable=${alsa_found}
+	gstreamer_enable=${gstreamer_found}
+	audio_enable=yes
+	input_enable=yes
+	serial_enable=yes
+	network_enable=yes
+	service_enable=yes
+	tools_enable=yes
+	hidd_enable=no
+	pand_enable=no
+	dund_enable=no
+	cups_enable=no
+	test_enable=no
+	bccmd_enable=no
+	pcmcia_enable=no
+	hid2hci_enable=no
+	dfutool_enable=no
+	manpages_enable=yes
+	udevrules_enable=yes
+	configfiles_enable=yes
+	telephony_driver=dummy
+
+	AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable code optimization]), [
+		optimization_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(fortify, AC_HELP_STRING([--disable-fortify], [disable compile time buffer checks]), [
+		fortify_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(pie, AC_HELP_STRING([--disable-pie], [disable position independent executables flag]), [
+		pie_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(network, AC_HELP_STRING([--disable-network], [disable network plugin]), [
+		network_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(serial, AC_HELP_STRING([--disable-serial], [disable serial plugin]), [
+		serial_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(input, AC_HELP_STRING([--disable-input], [disable input plugin]), [
+		input_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(audio, AC_HELP_STRING([--disable-audio], [disable audio plugin]), [
+		audio_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(service, AC_HELP_STRING([--disable-service], [disable service plugin]), [
+		service_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(gstreamer, AC_HELP_STRING([--enable-gstreamer], [enable GStreamer support]), [
+		gstreamer_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(alsa, AC_HELP_STRING([--enable-alsa], [enable ALSA support]), [
+		alsa_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(usb, AC_HELP_STRING([--enable-usb], [enable USB support]), [
+		usb_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(netlink, AC_HELP_STRING([--enable-netlink], [enable NETLINK support]), [
+		netlink_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(tools, AC_HELP_STRING([--enable-tools], [install Bluetooth utilities]), [
+		tools_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(bccmd, AC_HELP_STRING([--enable-bccmd], [install BCCMD interface utility]), [
+		bccmd_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(pcmcia, AC_HELP_STRING([--enable-pcmcia], [install PCMCIA serial script]), [
+		pcmcia_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(hid2hci, AC_HELP_STRING([--enable-hid2hci], [install HID mode switching utility]), [
+		hid2hci_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(dfutool, AC_HELP_STRING([--enable-dfutool], [install DFU firmware upgrade utility]), [
+		dfutool_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(hidd, AC_HELP_STRING([--enable-hidd], [install HID daemon]), [
+		hidd_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(pand, AC_HELP_STRING([--enable-pand], [install PAN daemon]), [
+		pand_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(dund, AC_HELP_STRING([--enable-dund], [install DUN daemon]), [
+		dund_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(cups, AC_HELP_STRING([--enable-cups], [install CUPS backend support]), [
+		cups_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(test, AC_HELP_STRING([--enable-test], [install test programs]), [
+		test_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(manpages, AC_HELP_STRING([--enable-manpages], [install Bluetooth manual pages]), [
+		manpages_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(udevrules, AC_HELP_STRING([--enable-udevrules], [install Bluetooth udev rules]), [
+		udevrules_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(configfiles, AC_HELP_STRING([--enable-configfiles], [install Bluetooth configuration files]), [
+		configfiles_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], [enable compiling with debugging information]), [
+		debug_enable=${enableval}
+	])
+
+	AC_ARG_WITH(telephony, AC_HELP_STRING([--with-telephony=DRIVER], [select telephony driver]), [
+		telephony_driver=${withval}
+	])
+
+	AC_SUBST([TELEPHONY_DRIVER], [telephony-${telephony_driver}.c])
+
+	if (test "${fortify_enable}" = "yes"); then
+		CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2"
+	fi
+
+	if (test "${pie_enable}" = "yes" && test "${ac_cv_prog_cc_pie}" = "yes"); then
+		CFLAGS="$CFLAGS -fPIC"
+		LDFLAGS="$LDFLAGS -pie"
+	fi
+
+	if (test "${debug_enable}" = "yes" && test "${ac_cv_prog_cc_g}" = "yes"); then
+		CFLAGS="$CFLAGS -g"
+	fi
+
+	if (test "${optimization_enable}" = "no"); then
+		CFLAGS="$CFLAGS -O0"
+	fi
+
+	if (test "${usb_enable}" = "yes" && test "${usb_found}" = "yes"); then
+		AC_DEFINE(HAVE_LIBUSB, 1, [Define to 1 if you have USB library.])
+	fi
+
+	AC_SUBST([BLUEZ_CFLAGS], ['-I$(top_builddir)/include'])
+	AC_SUBST([BLUEZ_LIBS], ['$(top_builddir)/lib/libbluetooth.la'])
+
+	AC_SUBST([GDBUS_CFLAGS], ['-I$(top_srcdir)/gdbus'])
+	AC_SUBST([GDBUS_LIBS], ['$(top_builddir)/gdbus/libgdbus.la'])
+
+	AC_SUBST([SBC_CFLAGS], ['-I$(top_srcdir)/sbc'])
+	AC_SUBST([SBC_LIBS], ['$(top_builddir)/sbc/libsbc.la'])
+
+	AM_CONDITIONAL(SNDFILE, test "${sndfile_enable}" = "yes" && test "${sndfile_found}" = "yes")
+	AM_CONDITIONAL(NETLINK, test "${netlink_enable}" = "yes" && test "${netlink_found}" = "yes")
+	AM_CONDITIONAL(USB, test "${usb_enable}" = "yes" && test "${usb_found}" = "yes")
+	AM_CONDITIONAL(SBC, test "${alsa_enable}" = "yes" || test "${gstreamer_enable}" = "yes")
+	AM_CONDITIONAL(ALSA, test "${alsa_enable}" = "yes" && test "${alsa_found}" = "yes")
+	AM_CONDITIONAL(GSTREAMER, test "${gstreamer_enable}" = "yes" && test "${gstreamer_found}" = "yes")
+	AM_CONDITIONAL(AUDIOPLUGIN, test "${audio_enable}" = "yes")
+	AM_CONDITIONAL(INPUTPLUGIN, test "${input_enable}" = "yes")
+	AM_CONDITIONAL(SERIALPLUGIN, test "${serial_enable}" = "yes")
+	AM_CONDITIONAL(NETWORKPLUGIN, test "${network_enable}" = "yes")
+	AM_CONDITIONAL(SERVICEPLUGIN, test "${service_enable}" = "yes")
+	AM_CONDITIONAL(HIDD, test "${hidd_enable}" = "yes")
+	AM_CONDITIONAL(PAND, test "${pand_enable}" = "yes")
+	AM_CONDITIONAL(DUND, test "${dund_enable}" = "yes")
+	AM_CONDITIONAL(CUPS, test "${cups_enable}" = "yes")
+	AM_CONDITIONAL(TEST, test "${test_enable}" = "yes")
+	AM_CONDITIONAL(TOOLS, test "${tools_enable}" = "yes")
+	AM_CONDITIONAL(BCCMD, test "${bccmd_enable}" = "yes")
+	AM_CONDITIONAL(PCMCIA, test "${pcmcia_enable}" = "yes")
+	AM_CONDITIONAL(HID2HCI, test "${hid2hci_enable}" = "yes" && test "${usb_found}" = "yes")
+	AM_CONDITIONAL(DFUTOOL, test "${dfutool_enable}" = "yes" && test "${usb_found}" = "yes")
+	AM_CONDITIONAL(MANPAGES, test "${manpages_enable}" = "yes")
+	AM_CONDITIONAL(UDEVRULES, test "${udevrules_enable}" = "yes")
+	AM_CONDITIONAL(CONFIGFILES, test "${configfiles_enable}" = "yes")
+])
diff --git a/audio/Android.mk b/audio/Android.mk
new file mode 100755
index 0000000..9537e33
--- /dev/null
+++ b/audio/Android.mk
@@ -0,0 +1,74 @@
+LOCAL_PATH:= $(call my-dir)
+
+# A2DP plugin
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	a2dp.c \
+	avdtp.c \
+	control.c \
+	device.c \
+	gateway.c \
+	headset.c \
+	ipc.c \
+	main.c \
+	manager.c \
+	module-bluetooth-sink.c \
+	sink.c \
+	source.c \
+	telephony-dummy.c \
+	unix.c
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\" \
+	-DSTORAGEDIR=\"/data/misc/bluetoothd\" \
+	-DCONFIGDIR=\"/etc/bluez\" \
+	-DANDROID \
+	-D__S_IFREG=0100000  # missing from bionic stat.h
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+	$(LOCAL_PATH)/../gdbus \
+	$(LOCAL_PATH)/../src \
+	$(call include-path-for, glib) \
+	$(call include-path-for, dbus)
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth \
+	libbluetoothd \
+	libdbus
+
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/bluez-plugin
+LOCAL_UNSTRIPPED_PATH := $(TARGET_OUT_SHARED_LIBRARIES_UNSTRIPPED)/bluez-plugin
+LOCAL_MODULE := audio
+
+include $(BUILD_SHARED_LIBRARY)
+
+#
+# liba2dp
+# This is linked to Audioflinger so **LGPL only**
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	liba2dp.c \
+	ipc.c \
+	../sbc/sbc.c.arm \
+	../sbc/sbc_primitives.c \
+	../sbc/sbc_primitives_neon.c
+
+# to improve SBC performance
+LOCAL_CFLAGS:= -funroll-loops
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../sbc \
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils
+
+LOCAL_MODULE := liba2dp
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/audio/Makefile.am b/audio/Makefile.am
new file mode 100644
index 0000000..52ad212
--- /dev/null
+++ b/audio/Makefile.am
@@ -0,0 +1,87 @@
+
+BUILT_SOURCES = telephony.c
+
+if AUDIOPLUGIN
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES = audio.la
+
+audio_la_SOURCES = main.c \
+	ipc.h ipc.c unix.h unix.c manager.h manager.c telephony.h \
+	device.h device.c headset.h headset.c gateway.h gateway.c \
+	avdtp.h avdtp.c a2dp.h a2dp.c sink.h sink.c source.h source.c \
+	control.h control.c
+
+nodist_audio_la_SOURCES = $(BUILT_SOURCES)
+
+audio_la_LDFLAGS = -module -avoid-version -no-undefined
+
+LDADD = $(top_builddir)/common/libhelper.a \
+		@GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+
+if ALSA
+alsadir = $(libdir)/alsa-lib
+
+alsa_LTLIBRARIES = libasound_module_pcm_bluetooth.la libasound_module_ctl_bluetooth.la
+
+libasound_module_pcm_bluetooth_la_SOURCES = pcm_bluetooth.c rtp.h ipc.h ipc.c
+libasound_module_pcm_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_pcm_.*
+libasound_module_pcm_bluetooth_la_LIBADD = @SBC_LIBS@ @BLUEZ_LIBS@ @ALSA_LIBS@
+libasound_module_pcm_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ @BLUEZ_CFLAGS@ @SBC_CFLAGS@
+
+libasound_module_ctl_bluetooth_la_SOURCES = ctl_bluetooth.c rtp.h ipc.h ipc.c
+libasound_module_ctl_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_ctl_.*
+libasound_module_ctl_bluetooth_la_LIBADD = @BLUEZ_LIBS@ @ALSA_LIBS@
+libasound_module_ctl_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ @BLUEZ_CFLAGS@
+
+if CONFIGFILES
+alsaconfdir = $(sysconfdir)/alsa
+
+alsaconf_DATA = bluetooth.conf
+endif
+endif
+
+if GSTREAMER
+gstreamerdir = $(libdir)/gstreamer-0.10
+
+gstreamer_LTLIBRARIES = libgstbluetooth.la
+
+libgstbluetooth_la_SOURCES = gstbluetooth.c \
+				gstsbcenc.h gstsbcenc.c \
+				gstsbcdec.h gstsbcdec.c \
+				gstsbcparse.h gstsbcparse.c \
+				gstavdtpsink.h gstavdtpsink.c \
+				gsta2dpsink.h gsta2dpsink.c \
+				gstsbcutil.h gstsbcutil.c \
+				gstrtpsbcpay.h gstrtpsbcpay.c \
+				rtp.h ipc.h ipc.c
+libgstbluetooth_la_LDFLAGS = -module -avoid-version
+libgstbluetooth_la_LIBADD = @SBC_LIBS@ @BLUEZ_LIBS@ @GSTREAMER_LIBS@ \
+						-lgstaudio-0.10 -lgstrtp-0.10
+libgstbluetooth_la_CFLAGS = -fvisibility=hidden -fno-strict-aliasing \
+			 @GSTREAMER_CFLAGS@ @BLUEZ_CFLAGS@ @SBC_CFLAGS@
+endif
+endif
+
+noinst_LTLIBRARIES = libipc.la
+
+libipc_la_SOURCES = ipc.h ipc.c
+
+noinst_PROGRAMS = ipctest
+
+ipctest_LDADD= libipc.la @SBC_LIBS@ @GLIB_LIBS@
+
+AM_CFLAGS = -fvisibility=hidden @SBC_CFLAGS@ \
+		@BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+CLEANFILES = $(BUILT_SOURCES)
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/src
+
+EXTRA_DIST = audio.conf telephony-dummy.c telephony-maemo.c telephony-ofono.c \
+		bluetooth.conf
+
+MAINTAINERCLEANFILES = Makefile.in
+
+telephony.c: @TELEPHONY_DRIVER@
+	@if [ ! -e $@ ] ; then $(LN_S) $< $@ ; fi
diff --git a/audio/a2dp.c b/audio/a2dp.c
new file mode 100644
index 0000000..45be5d4
--- /dev/null
+++ b/audio/a2dp.c
@@ -0,0 +1,1626 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 "logging.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "sink.h"
+#include "source.h"
+#include "a2dp.h"
+#include "sdpd.h"
+
+/* The duration that streams without users are allowed to stay in
+ * STREAMING state. */
+#define SUSPEND_TIMEOUT 5
+#define RECONFIGURE_TIMEOUT 500
+
+#ifndef MIN
+# define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+#ifndef MAX
+# define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+struct a2dp_sep {
+	uint8_t type;
+	uint8_t codec;
+	struct avdtp_local_sep *sep;
+	struct avdtp *session;
+	struct avdtp_stream *stream;
+	guint suspend_timer;
+	gboolean locked;
+	gboolean suspending;
+	gboolean starting;
+};
+
+struct a2dp_setup_cb {
+	a2dp_config_cb_t config_cb;
+	a2dp_stream_cb_t resume_cb;
+	a2dp_stream_cb_t suspend_cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct a2dp_setup {
+	struct audio_device *dev;
+	struct avdtp *session;
+	struct a2dp_sep *sep;
+	struct avdtp_stream *stream;
+	struct avdtp_error *err;
+	GSList *client_caps;
+	gboolean reconfigure;
+	gboolean canceled;
+	gboolean start;
+	GSList *cb;
+	int ref;
+};
+
+static DBusConnection *connection = NULL;
+
+struct a2dp_server {
+	bdaddr_t src;
+	GSList *sinks;
+	GSList *sources;
+	uint32_t source_record_id;
+	uint32_t sink_record_id;
+};
+
+static GSList *servers = NULL;
+static GSList *setups = NULL;
+static unsigned int cb_id = 0;
+
+static struct a2dp_setup *setup_ref(struct a2dp_setup *setup)
+{
+	setup->ref++;
+
+	debug("setup_ref(%p): ref=%d", setup, setup->ref);
+
+	return setup;
+}
+
+static void setup_free(struct a2dp_setup *s)
+{
+	debug("setup_free(%p)", s);
+	setups = g_slist_remove(setups, s);
+	if (s->session)
+		avdtp_unref(s->session);
+	g_slist_foreach(s->cb, (GFunc) g_free, NULL);
+	g_slist_free(s->cb);
+	g_free(s);
+}
+
+static void setup_unref(struct a2dp_setup *setup)
+{
+	setup->ref--;
+
+	debug("setup_unref(%p): ref=%d", setup, setup->ref);
+
+	if (setup->ref <= 0)
+		setup_free(setup);
+}
+
+static struct audio_device *a2dp_get_dev(struct avdtp *session)
+{
+	bdaddr_t src, dst;
+
+	avdtp_get_peers(session, &src, &dst);
+
+	return manager_find_device(NULL, &src, &dst, NULL, FALSE);
+}
+
+static gboolean finalize_config(struct a2dp_setup *s)
+{
+	GSList *l;
+
+	setup_ref(s);
+	for (l = s->cb; l != NULL; l = l->next) {
+		struct a2dp_setup_cb *cb = l->data;
+		struct avdtp_stream *stream = s->err ? NULL : s->stream;
+
+		if (!cb->config_cb)
+			continue;
+
+		cb->config_cb(s->session, s->sep, stream, s->err,
+							cb->user_data);
+		cb->config_cb = NULL;
+		setup_unref(s);
+	}
+
+	setup_unref(s);
+	return FALSE;
+}
+
+static gboolean finalize_config_errno(struct a2dp_setup *s, int err)
+{
+	struct avdtp_error avdtp_err;
+
+	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+	s->err = err ? &avdtp_err : NULL;
+
+	return finalize_config(s);
+}
+
+static gboolean finalize_resume(struct a2dp_setup *s)
+{
+	GSList *l;
+
+	setup_ref(s);
+	for (l = s->cb; l != NULL; l = l->next) {
+		struct a2dp_setup_cb *cb = l->data;
+
+		if (cb && cb->resume_cb) {
+			cb->resume_cb(s->session, s->err, cb->user_data);
+			cb->resume_cb = NULL;
+			setup_unref(s);
+		}
+	}
+
+	setup_unref(s);
+	return FALSE;
+}
+
+static gboolean finalize_suspend(struct a2dp_setup *s)
+{
+	GSList *l;
+
+	setup_ref(s);
+	for (l = s->cb; l != NULL; l = l->next) {
+		struct a2dp_setup_cb *cb = l->data;
+
+		if (cb->suspend_cb) {
+			cb->suspend_cb(s->session, s->err, cb->user_data);
+			cb->suspend_cb = NULL;
+			setup_unref(s);
+		}
+	}
+
+	setup_unref(s);
+	return FALSE;
+}
+
+static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err)
+{
+	struct avdtp_error avdtp_err;
+
+	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+	s->err = err ? &avdtp_err : NULL;
+
+	return finalize_suspend(s);
+}
+
+static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
+{
+	GSList *l;
+
+	for (l = setups; l != NULL; l = l->next) {
+		struct a2dp_setup *setup = l->data;
+
+		if (setup->session == session)
+			return setup;
+	}
+
+	return NULL;
+}
+
+static struct a2dp_setup *find_setup_by_dev(struct audio_device *dev)
+{
+	GSList *l;
+
+	for (l = setups; l != NULL; l = l->next) {
+		struct a2dp_setup *setup = l->data;
+
+		if (setup->dev == dev)
+			return setup;
+	}
+
+	return NULL;
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+					avdtp_state_t old_state,
+					avdtp_state_t new_state,
+					struct avdtp_error *err,
+					void *user_data)
+{
+	struct a2dp_sep *sep = user_data;
+
+	if (new_state != AVDTP_STATE_IDLE)
+		return;
+
+	if (sep->suspend_timer) {
+		g_source_remove(sep->suspend_timer);
+		sep->suspend_timer = 0;
+	}
+
+	if (sep->session) {
+		avdtp_unref(sep->session);
+		sep->session = NULL;
+	}
+
+	sep->stream = NULL;
+
+}
+
+static gboolean sbc_setconf_ind(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream,
+				GSList *caps, uint8_t *err,
+				uint8_t *category, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct audio_device *dev;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap;
+	struct sbc_codec_cap *sbc_cap;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Set_Configuration_Ind", sep);
+	else
+		debug("Source %p: Set_Configuration_Ind", sep);
+
+	dev = a2dp_get_dev(session);
+	if (!dev) {
+		*err = AVDTP_UNSUPPORTED_CONFIGURATION;
+		*category = 0x00;
+		return FALSE;
+	}
+
+	/* Check bipool range */
+	for (codec_cap = NULL; caps; caps = g_slist_next(caps)) {
+		cap = caps->data;
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		if (cap->length < sizeof(struct sbc_codec_cap))
+			continue;
+
+		codec_cap = (void *) cap->data;
+
+		if (codec_cap->media_codec_type != A2DP_CODEC_SBC)
+			continue;
+
+		sbc_cap = (void *) codec_cap;
+
+		if (sbc_cap->min_bitpool < MIN_BITPOOL ||
+					sbc_cap->max_bitpool > MAX_BITPOOL) {
+			*err = AVDTP_UNSUPPORTED_CONFIGURATION;
+			*category = AVDTP_MEDIA_CODEC;
+			return FALSE;
+		}
+
+		break;
+	}
+
+	avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
+	a2dp_sep->stream = stream;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
+		sink_new_stream(dev, session, stream);
+
+	return TRUE;
+}
+
+static gboolean sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				GSList **caps, uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct sbc_codec_cap sbc_cap;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Get_Capability_Ind", sep);
+	else
+		debug("Source %p: Get_Capability_Ind", sep);
+
+	*caps = NULL;
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	memset(&sbc_cap, 0, sizeof(struct sbc_codec_cap));
+
+	sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC;
+
+#ifdef ANDROID
+	sbc_cap.frequency = SBC_SAMPLING_FREQ_44100;
+#else
+	sbc_cap.frequency = ( SBC_SAMPLING_FREQ_48000 |
+				SBC_SAMPLING_FREQ_44100 |
+				SBC_SAMPLING_FREQ_32000 |
+				SBC_SAMPLING_FREQ_16000 );
+#endif
+
+	sbc_cap.channel_mode = ( SBC_CHANNEL_MODE_JOINT_STEREO |
+					SBC_CHANNEL_MODE_STEREO |
+					SBC_CHANNEL_MODE_DUAL_CHANNEL |
+					SBC_CHANNEL_MODE_MONO );
+
+	sbc_cap.block_length = ( SBC_BLOCK_LENGTH_16 |
+					SBC_BLOCK_LENGTH_12 |
+					SBC_BLOCK_LENGTH_8 |
+					SBC_BLOCK_LENGTH_4 );
+
+	sbc_cap.subbands = ( SBC_SUBBANDS_8 | SBC_SUBBANDS_4 );
+
+	sbc_cap.allocation_method = ( SBC_ALLOCATION_LOUDNESS |
+					SBC_ALLOCATION_SNR );
+
+	sbc_cap.min_bitpool = MIN_BITPOOL;
+	sbc_cap.max_bitpool = MAX_BITPOOL;
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+						sizeof(sbc_cap));
+
+	*caps = g_slist_append(*caps, media_codec);
+
+	return TRUE;
+}
+
+static gboolean mpeg_setconf_ind(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream,
+				GSList *caps, uint8_t *err,
+				uint8_t *category, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct audio_device *dev;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Set_Configuration_Ind", sep);
+	else
+		debug("Source %p: Set_Configuration_Ind", sep);
+
+	dev = a2dp_get_dev(session);
+	if (!dev) {
+		*err = AVDTP_UNSUPPORTED_CONFIGURATION;
+		*category = 0x00;
+		return FALSE;
+	}
+
+	avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
+	a2dp_sep->stream = stream;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
+		sink_new_stream(dev, session, stream);
+
+	return TRUE;
+}
+
+static gboolean mpeg_getcap_ind(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				GSList **caps, uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct mpeg_codec_cap mpeg_cap;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Get_Capability_Ind", sep);
+	else
+		debug("Source %p: Get_Capability_Ind", sep);
+
+	*caps = NULL;
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	memset(&mpeg_cap, 0, sizeof(struct mpeg_codec_cap));
+
+	mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12;
+
+	mpeg_cap.frequency = ( MPEG_SAMPLING_FREQ_48000 |
+				MPEG_SAMPLING_FREQ_44100 |
+				MPEG_SAMPLING_FREQ_32000 |
+				MPEG_SAMPLING_FREQ_24000 |
+				MPEG_SAMPLING_FREQ_22050 |
+				MPEG_SAMPLING_FREQ_16000 );
+
+	mpeg_cap.channel_mode = ( MPEG_CHANNEL_MODE_JOINT_STEREO |
+					MPEG_CHANNEL_MODE_STEREO |
+					MPEG_CHANNEL_MODE_DUAL_CHANNEL |
+					MPEG_CHANNEL_MODE_MONO );
+
+	mpeg_cap.layer = ( MPEG_LAYER_MP3 | MPEG_LAYER_MP2 | MPEG_LAYER_MP1 );
+
+	mpeg_cap.bitrate = 0xFFFF;
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap,
+						sizeof(mpeg_cap));
+
+	*caps = g_slist_append(*caps, media_codec);
+
+	return TRUE;
+}
+
+static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+	struct audio_device *dev;
+	int ret;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Set_Configuration_Cfm", sep);
+	else
+		debug("Source %p: Set_Configuration_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+
+	if (err) {
+		if (setup) {
+			setup->err = err;
+			finalize_config(setup);
+		}
+		return;
+	}
+
+	avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
+	a2dp_sep->stream = stream;
+
+	if (!setup)
+		return;
+
+	dev = a2dp_get_dev(session);
+
+	/* Notify D-Bus interface of the new stream */
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
+		sink_new_stream(dev, session, setup->stream);
+	else
+		source_new_stream(dev, session, setup->stream);
+
+	ret = avdtp_open(session, stream);
+	if (ret < 0) {
+		error("Error on avdtp_open %s (%d)", strerror(-ret), -ret);
+		setup->stream = NULL;
+		finalize_config_errno(setup, ret);
+	}
+}
+
+static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Get_Configuration_Ind", sep);
+	else
+		debug("Source %p: Get_Configuration_Ind", sep);
+	return TRUE;
+}
+
+static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Set_Configuration_Cfm", sep);
+	else
+		debug("Source %p: Set_Configuration_Cfm", sep);
+}
+
+static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Open_Ind", sep);
+	else
+		debug("Source %p: Open_Ind", sep);
+	return TRUE;
+}
+
+static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Open_Cfm", sep);
+	else
+		debug("Source %p: Open_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	if (setup->canceled) {
+		if (!err)
+			avdtp_close(session, stream);
+		setup_unref(setup);
+		return;
+	}
+
+	if (setup->reconfigure)
+		setup->reconfigure = FALSE;
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+	}
+
+	finalize_config(setup);
+}
+
+static gboolean suspend_timeout(struct a2dp_sep *sep)
+{
+	if (avdtp_suspend(sep->session, sep->stream) == 0)
+		sep->suspending = TRUE;
+
+	sep->suspend_timer = 0;
+
+	avdtp_unref(sep->session);
+	sep->session = NULL;
+
+	return FALSE;
+}
+
+static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Start_Ind", sep);
+	else
+		debug("Source %p: Start_Ind", sep);
+
+	setup = find_setup_by_session(session);
+	if (setup) {
+		if (setup->canceled)
+			setup_unref(setup);
+		else
+			finalize_resume(setup);
+	}
+
+	if (!a2dp_sep->locked) {
+		a2dp_sep->session = avdtp_ref(session);
+		a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT,
+						(GSourceFunc) suspend_timeout,
+						a2dp_sep);
+	}
+
+	return TRUE;
+}
+
+static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Start_Cfm", sep);
+	else
+		debug("Source %p: Start_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	if (setup->canceled) {
+		if (!err)
+			avdtp_close(session, stream);
+		setup_unref(setup);
+		return;
+	}
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+	}
+
+	finalize_resume(setup);
+}
+
+static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Suspend_Ind", sep);
+	else
+		debug("Source %p: Suspend_Ind", sep);
+
+	if (a2dp_sep->suspend_timer) {
+		g_source_remove(a2dp_sep->suspend_timer);
+		a2dp_sep->suspend_timer = 0;
+		avdtp_unref(a2dp_sep->session);
+		a2dp_sep->session = NULL;
+	}
+
+	return TRUE;
+}
+
+static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+	gboolean start;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Suspend_Cfm", sep);
+	else
+		debug("Source %p: Suspend_Cfm", sep);
+
+	a2dp_sep->suspending = FALSE;
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	start = setup->start;
+	setup->start = FALSE;
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+		finalize_suspend(setup);
+	}
+	else
+		finalize_suspend_errno(setup, 0);
+
+	if (!start)
+		return;
+
+	if (err) {
+		setup->err = err;
+		finalize_suspend(setup);
+	} else if (avdtp_start(session, a2dp_sep->stream) < 0) {
+		struct avdtp_error start_err;
+		error("avdtp_start failed");
+		avdtp_error_init(&start_err, AVDTP_ERROR_ERRNO, EIO);
+		setup->err = err;
+		finalize_suspend(setup);
+	}
+}
+
+static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Close_Ind", sep);
+	else
+		debug("Source %p: Close_Ind", sep);
+
+	return TRUE;
+}
+
+static gboolean a2dp_reconfigure(gpointer data)
+{
+	struct a2dp_setup *setup = data;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_remote_sep *rsep;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap = NULL;
+	GSList *l;
+	int posix_err;
+
+	for (l = setup->client_caps; l != NULL; l = l->next) {
+		cap = l->data;
+
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		codec_cap = (void *) cap->data;
+		break;
+	}
+
+	if (!codec_cap) {
+		error("Cannot find capabilities to reconfigure");
+		posix_err = -EINVAL;
+		goto failed;
+	}
+
+	posix_err = avdtp_get_seps(setup->session, AVDTP_SEP_TYPE_SINK,
+					codec_cap->media_type,
+					codec_cap->media_codec_type,
+					&lsep, &rsep);
+	if (posix_err < 0) {
+		error("No matching ACP and INT SEPs found");
+		goto failed;
+	}
+
+	posix_err = avdtp_set_configuration(setup->session, rsep, lsep,
+						setup->client_caps,
+						&setup->stream);
+	if (posix_err < 0) {
+		error("avdtp_set_configuration: %s", strerror(-posix_err));
+		goto failed;
+	}
+
+	return FALSE;
+
+failed:
+	finalize_config_errno(setup, posix_err);
+	return FALSE;
+}
+
+static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Close_Cfm", sep);
+	else
+		debug("Source %p: Close_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	if (setup->canceled) {
+		setup_unref(setup);
+		return;
+	}
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+		finalize_config(setup);
+		return;
+	}
+
+	if (setup->reconfigure)
+		g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup);
+}
+
+static gboolean abort_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Abort_Ind", sep);
+	else
+		debug("Source %p: Abort_Ind", sep);
+
+	a2dp_sep->stream = NULL;
+
+	return TRUE;
+}
+
+static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Abort_Cfm", sep);
+	else
+		debug("Source %p: Abort_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	setup_unref(setup);
+}
+
+static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: ReConfigure_Ind", sep);
+	else
+		debug("Source %p: ReConfigure_Ind", sep);
+	return TRUE;
+}
+
+static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: ReConfigure_Cfm", sep);
+	else
+		debug("Source %p: ReConfigure_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	if (setup->canceled) {
+		if (!err)
+			avdtp_close(session, stream);
+		setup_unref(setup);
+		return;
+	}
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+	}
+
+	finalize_config(setup);
+}
+
+static struct avdtp_sep_cfm cfm = {
+	.set_configuration	= setconf_cfm,
+	.get_configuration	= getconf_cfm,
+	.open			= open_cfm,
+	.start			= start_cfm,
+	.suspend		= suspend_cfm,
+	.close			= close_cfm,
+	.abort			= abort_cfm,
+	.reconfigure		= reconf_cfm
+};
+
+static struct avdtp_sep_ind sbc_ind = {
+	.get_capability		= sbc_getcap_ind,
+	.set_configuration	= sbc_setconf_ind,
+	.get_configuration	= getconf_ind,
+	.open			= open_ind,
+	.start			= start_ind,
+	.suspend		= suspend_ind,
+	.close			= close_ind,
+	.abort			= abort_ind,
+	.reconfigure		= reconf_ind
+};
+
+static struct avdtp_sep_ind mpeg_ind = {
+	.get_capability		= mpeg_getcap_ind,
+	.set_configuration	= mpeg_setconf_ind,
+	.get_configuration	= getconf_ind,
+	.open			= open_ind,
+	.start			= start_ind,
+	.suspend		= suspend_ind,
+	.close			= close_ind,
+	.abort			= abort_ind,
+	.reconfigure		= reconf_ind
+};
+
+static sdp_record_t *a2dp_record(uint8_t type)
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid;
+	sdp_profile_desc_t profile[1];
+	sdp_list_t *aproto, *proto[2];
+	sdp_record_t *record;
+	sdp_data_t *psm, *version, *features;
+	uint16_t lp = AVDTP_UUID, ver = 0x0100, feat = 0x000F;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	if (type == AVDTP_SEP_TYPE_SOURCE)
+		sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID);
+	else
+		sdp_uuid16_create(&a2dp_uuid, AUDIO_SINK_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &a2dp_uuid);
+	sdp_set_service_classes(record, svclass_id);
+
+	sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID);
+	profile[0].version = 0x0100;
+	pfseq = sdp_list_append(0, &profile[0]);
+	sdp_set_profile_descs(record, pfseq);
+
+	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap_uuid);
+	psm = sdp_data_alloc(SDP_UINT16, &lp);
+	proto[0] = sdp_list_append(proto[0], psm);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&avdtp_uuid, AVDTP_UUID);
+	proto[1] = sdp_list_append(0, &avdtp_uuid);
+	version = sdp_data_alloc(SDP_UINT16, &ver);
+	proto[1] = sdp_list_append(proto[1], version);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	features = sdp_data_alloc(SDP_UINT16, &feat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	if (type == AVDTP_SEP_TYPE_SOURCE)
+		sdp_set_info_attr(record, "Audio Source", 0, 0);
+	else
+		sdp_set_info_attr(record, "Audio Sink", 0, 0);
+
+	free(psm);
+	free(version);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
+					uint8_t codec)
+{
+	struct a2dp_sep *sep;
+	GSList **l;
+	uint32_t *record_id;
+	sdp_record_t *record;
+	struct avdtp_sep_ind *ind;
+
+	sep = g_new0(struct a2dp_sep, 1);
+
+	ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
+	sep->sep = avdtp_register_sep(&server->src, type,
+					AVDTP_MEDIA_TYPE_AUDIO, codec, ind,
+					&cfm, sep);
+	if (sep->sep == NULL) {
+		g_free(sep);
+		return NULL;
+	}
+
+	sep->codec = codec;
+	sep->type = type;
+
+	if (type == AVDTP_SEP_TYPE_SOURCE) {
+		l = &server->sources;
+		record_id = &server->source_record_id;
+	} else {
+		l = &server->sinks;
+		record_id = &server->sink_record_id;
+	}
+
+	if (*record_id != 0)
+		goto add;
+
+	record = a2dp_record(type);
+	if (!record) {
+		error("Unable to allocate new service record");
+		avdtp_unregister_sep(sep->sep);
+		g_free(sep);
+		return NULL;
+	}
+
+	if (add_record_to_server(&server->src, record) < 0) {
+		error("Unable to register A2DP service record");\
+		sdp_record_free(record);
+		avdtp_unregister_sep(sep->sep);
+		g_free(sep);
+		return NULL;
+	}
+	*record_id = record->handle;
+
+add:
+	*l = g_slist_append(*l, sep);
+
+	return sep;
+}
+
+static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		struct a2dp_server *server = l->data;
+
+		if (bacmp(&server->src, src) == 0)
+			return server;
+	}
+
+	return NULL;
+}
+
+int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
+{
+	int sbc_srcs = 1, sbc_sinks = 1;
+	int mpeg12_srcs = 0, mpeg12_sinks = 0;
+	gboolean source = TRUE, sink = FALSE;
+	char *str;
+	GError *err = NULL;
+	int i;
+	struct a2dp_server *server;
+
+	if (!config)
+		goto proceed;
+
+	str = g_key_file_get_string(config, "General", "Enable", &err);
+
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		if (strstr(str, "Sink"))
+			source = TRUE;
+		if (strstr(str, "Source"))
+			sink = TRUE;
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "General", "Disable", &err);
+
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		if (strstr(str, "Sink"))
+			source = FALSE;
+		if (strstr(str, "Source"))
+			sink = FALSE;
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "A2DP", "SBCSources", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		sbc_srcs = atoi(str);
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "A2DP", "MPEG12Sources", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		mpeg12_srcs = atoi(str);
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "A2DP", "SBCSinks", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		sbc_sinks = atoi(str);
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "A2DP", "MPEG12Sinks", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		mpeg12_sinks = atoi(str);
+		g_free(str);
+	}
+
+proceed:
+	if (!connection)
+		connection = dbus_connection_ref(conn);
+
+	server = find_server(servers, src);
+	if (!server) {
+		int av_err;
+
+		server = g_new0(struct a2dp_server, 1);
+		if (!server)
+			return -ENOMEM;
+
+		av_err = avdtp_init(src, config);
+		if (av_err < 0)
+			return av_err;
+
+		bacpy(&server->src, src);
+		servers = g_slist_append(servers, server);
+	}
+
+	if (source) {
+		for (i = 0; i < sbc_srcs; i++)
+			a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
+					A2DP_CODEC_SBC);
+
+		for (i = 0; i < mpeg12_srcs; i++)
+			a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
+					A2DP_CODEC_MPEG12);
+	}
+
+	if (sink) {
+		for (i = 0; i < sbc_sinks; i++)
+			a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
+					A2DP_CODEC_SBC);
+
+		for (i = 0; i < mpeg12_sinks; i++)
+			a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
+					A2DP_CODEC_MPEG12);
+	}
+
+	return 0;
+}
+
+static void a2dp_unregister_sep(struct a2dp_sep *sep)
+{
+	avdtp_unregister_sep(sep->sep);
+	g_free(sep);
+}
+
+void a2dp_unregister(const bdaddr_t *src)
+{
+	struct a2dp_server *server;
+
+	server = find_server(servers, src);
+	if (!server)
+		return;
+
+	g_slist_foreach(server->sinks, (GFunc) a2dp_unregister_sep, NULL);
+	g_slist_free(server->sinks);
+
+	g_slist_foreach(server->sources, (GFunc) a2dp_unregister_sep, NULL);
+	g_slist_free(server->sources);
+
+	avdtp_exit(src);
+
+	if (server->source_record_id)
+		remove_record_from_server(server->source_record_id);
+
+	if (server->sink_record_id)
+		remove_record_from_server(server->sink_record_id);
+
+	servers = g_slist_remove(servers, server);
+	g_free(server);
+
+	if (servers)
+		return;
+
+	dbus_connection_unref(connection);
+	connection = NULL;
+}
+
+struct a2dp_sep *a2dp_get(struct avdtp *session,
+				struct avdtp_remote_sep *rsep)
+{
+	GSList *l;
+	struct a2dp_server *server;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap = NULL;
+	bdaddr_t src;
+
+	avdtp_get_peers(session, &src, NULL);
+	server = find_server(servers, &src);
+	if (!server)
+		return NULL;
+
+	cap = avdtp_get_codec(rsep);
+	codec_cap = (void *) cap->data;
+
+	if (avdtp_get_type(rsep) == AVDTP_SEP_TYPE_SINK)
+		l = server->sources;
+	else
+		l = server->sinks;
+
+	for (; l != NULL; l = l->next) {
+		struct a2dp_sep *sep = l->data;
+
+		if (sep->locked)
+			continue;
+
+		if (sep->codec != codec_cap->media_codec_type)
+			continue;
+
+		if (!sep->stream || avdtp_has_stream(session, sep->stream))
+			return sep;
+	}
+
+	return NULL;
+}
+
+unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_config_cb_t cb, GSList *caps,
+				void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	GSList *l;
+	struct a2dp_server *server;
+	struct a2dp_setup *setup;
+	struct a2dp_sep *tmp;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_remote_sep *rsep;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap = NULL;
+	int posix_err;
+	bdaddr_t src;
+	uint8_t remote_type;
+
+	avdtp_get_peers(session, &src, NULL);
+	server = find_server(servers, &src);
+	if (!server)
+		return 0;
+
+	for (l = caps; l != NULL; l = l->next) {
+		cap = l->data;
+
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		codec_cap = (void *) cap->data;
+		break;
+	}
+
+	if (!codec_cap)
+		return 0;
+
+	if (sep->codec != codec_cap->media_codec_type)
+		return 0;
+
+	debug("a2dp_config: selected SEP %p", sep->sep);
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->config_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+	setup->client_caps = caps;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
+			l = server->sources;
+			remote_type = AVDTP_SEP_TYPE_SINK;
+		} else {
+			remote_type = AVDTP_SEP_TYPE_SOURCE;
+			l = server->sinks;
+		}
+
+		for (; l != NULL; l = l->next) {
+			tmp = l->data;
+			if (avdtp_has_stream(session, tmp->stream))
+				break;
+		}
+
+		if (l != NULL) {
+			setup->reconfigure = TRUE;
+			if (avdtp_close(session, tmp->stream) < 0) {
+				error("avdtp_close failed");
+				goto failed;
+			}
+			break;
+		}
+
+		if (avdtp_get_seps(session, remote_type,
+				codec_cap->media_type,
+				codec_cap->media_codec_type,
+				&lsep, &rsep) < 0) {
+			error("No matching ACP and INT SEPs found");
+			goto failed;
+		}
+
+		posix_err = avdtp_set_configuration(session, rsep, lsep,
+							caps, &setup->stream);
+		if (posix_err < 0) {
+			error("avdtp_set_configuration: %s",
+				strerror(-posix_err));
+			goto failed;
+		}
+		break;
+	case AVDTP_STATE_OPEN:
+	case AVDTP_STATE_STREAMING:
+		if (avdtp_stream_has_capabilities(setup->stream, caps)) {
+			debug("Configuration match: resuming");
+			g_idle_add((GSourceFunc) finalize_config, setup);
+		} else if (!setup->reconfigure) {
+			setup->reconfigure = TRUE;
+			if (avdtp_close(session, sep->stream) < 0) {
+				error("avdtp_close failed");
+				goto failed;
+			}
+		}
+		break;
+	default:
+		error("SEP in bad state for requesting a new stream");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
+unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->resume_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		goto failed;
+		break;
+	case AVDTP_STATE_OPEN:
+		if (avdtp_start(session, sep->stream) < 0) {
+			error("avdtp_start failed");
+			goto failed;
+		}
+		break;
+	case AVDTP_STATE_STREAMING:
+		if (!sep->suspending && sep->suspend_timer) {
+			g_source_remove(sep->suspend_timer);
+			sep->suspend_timer = 0;
+			avdtp_unref(sep->session);
+			sep->session = NULL;
+		}
+		if (sep->suspending)
+			setup->start = TRUE;
+		else
+			g_idle_add((GSourceFunc) finalize_resume, setup);
+		break;
+	default:
+		error("SEP in bad state for resume");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
+unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->suspend_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		error("a2dp_suspend: no stream to suspend");
+		goto failed;
+		break;
+	case AVDTP_STATE_OPEN:
+		g_idle_add((GSourceFunc) finalize_suspend, setup);
+		break;
+	case AVDTP_STATE_STREAMING:
+		if (avdtp_suspend(session, sep->stream) < 0) {
+			error("avdtp_suspend failed");
+			goto failed;
+		}
+		break;
+	default:
+		error("SEP in bad state for suspend");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
+gboolean a2dp_cancel(struct audio_device *dev, unsigned int id)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+	GSList *l;
+
+	debug("a2dp_cancel()");
+
+	setup = find_setup_by_dev(dev);
+	if (!setup)
+		return FALSE;
+
+	for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) {
+		struct a2dp_setup_cb *cb = l->data;
+
+		if (cb->id == id) {
+			cb_data = cb;
+			break;
+		}
+	}
+
+	if (!cb_data)
+		error("a2dp_cancel: no matching callback with id %u", id);
+
+	setup->cb = g_slist_remove(setup->cb, cb_data);
+	g_free(cb_data);
+
+	if (!setup->cb) {
+		setup->canceled = TRUE;
+		setup->sep = NULL;
+	}
+
+	return TRUE;
+}
+
+gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
+{
+	if (sep->locked)
+		return FALSE;
+
+	debug("SEP %p locked", sep->sep);
+	sep->locked = TRUE;
+
+	return TRUE;
+}
+
+gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session)
+{
+	avdtp_state_t state;
+
+	state = avdtp_sep_get_state(sep->sep);
+
+	sep->locked = FALSE;
+
+	debug("SEP %p unlocked", sep->sep);
+
+	if (!sep->stream || state == AVDTP_STATE_IDLE)
+		return TRUE;
+
+	switch (state) {
+	case AVDTP_STATE_OPEN:
+		/* Set timer here */
+		break;
+	case AVDTP_STATE_STREAMING:
+		if (avdtp_suspend(session, sep->stream) == 0)
+			sep->suspending = TRUE;
+		break;
+	default:
+		break;
+	}
+
+	return TRUE;
+}
+
+gboolean a2dp_sep_get_lock(struct a2dp_sep *sep)
+{
+	return sep->locked;
+}
+
+static int stream_cmp(gconstpointer data, gconstpointer user_data)
+{
+	const struct a2dp_sep *sep = data;
+	const struct avdtp_stream *stream = user_data;
+
+	return (sep->stream != stream);
+}
+
+struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
+				struct avdtp_stream *stream)
+{
+	struct a2dp_server *server;
+	bdaddr_t src, dst;
+	GSList *l;
+
+	avdtp_get_peers(session, &src, &dst);
+
+	for (l = servers; l; l = l->next) {
+		server = l->data;
+
+		if (bacmp(&src, &server->src) == 0)
+			break;
+	}
+
+	if (!l)
+		return NULL;
+
+	l = g_slist_find_custom(server->sources, stream, stream_cmp);
+	if (l)
+		return l->data;
+
+	l = g_slist_find_custom(server->sinks, stream, stream_cmp);
+	if (l)
+		return l->data;
+
+	return NULL;
+}
diff --git a/audio/a2dp.h b/audio/a2dp.h
new file mode 100644
index 0000000..f08c643
--- /dev/null
+++ b/audio/a2dp.h
@@ -0,0 +1,149 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err,
+					void *user_data);
+typedef void (*a2dp_stream_cb_t) (struct avdtp *session,
+					struct avdtp_error *err,
+					void *user_data);
+
+int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
+void a2dp_unregister(const bdaddr_t *src);
+
+struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep);
+unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_config_cb_t cb, GSList *caps,
+				void *user_data);
+unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data);
+unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data);
+gboolean a2dp_cancel(struct audio_device *dev, unsigned int id);
+
+gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
+gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session);
+gboolean a2dp_sep_get_lock(struct a2dp_sep *sep);
+struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
+				struct avdtp_stream *stream);
diff --git a/audio/audio.conf b/audio/audio.conf
new file mode 100644
index 0000000..a093ffb
--- /dev/null
+++ b/audio/audio.conf
@@ -0,0 +1,43 @@
+# Configuration file for the audio service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+Enable=Source,Control,Sink
+Disable=Headset,Gateway
+
+# Switch to master role for incoming connections (defaults to true)
+#Master=true
+
+# If we want to disable support for specific services
+# Defaults to supporting all implemented services
+#Disable=Control,Source
+
+# SCO routing. Either PCM or HCI (in which case audio is routed to/from ALSA)
+# Defaults to HCI
+#SCORouting=PCM
+
+# Automatically connect both A2DP and HFP/HSP profiles for incoming
+# connections. Some headsets that support both profiles will only connect the
+# other one automatically so the default setting of true is usually a good
+# idea.
+#AutoConnect=true
+
+# Headset interface specific options (i.e. options which affect how the audio
+# service interacts with remote headset devices)
+#[Headset]
+
+# Set to true to support HFP (in addition to HSP only which is the default)
+# Defaults to false
+#HFP=true
+
+# Maximum number of connected HSP/HFP devices per adapter. Defaults to 1
+#MaxConnections=1
+
+# Just an example of potential config options for the other interfaces
+[A2DP]
+SBCSources=1
+MPEG12Sources=0
+
+[AVRCP]
+InputDeviceName=AVRCP
diff --git a/audio/avdtp.c b/audio/avdtp.c
new file mode 100644
index 0000000..939db10
--- /dev/null
+++ b/audio/avdtp.c
@@ -0,0 +1,3510 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 <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 <glib.h>
+#include <dbus/dbus.h>
+
+#include "logging.h"
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "device.h"
+#include "manager.h"
+#include "control.h"
+#include "avdtp.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "sink.h"
+#include "source.h"
+
+#include <bluetooth/l2cap.h>
+
+#define AVDTP_PSM 25
+
+#define MAX_SEID 0x3E
+
+#define AVDTP_DISCOVER				0x01
+#define AVDTP_GET_CAPABILITIES			0x02
+#define AVDTP_SET_CONFIGURATION			0x03
+#define AVDTP_GET_CONFIGURATION			0x04
+#define AVDTP_RECONFIGURE			0x05
+#define AVDTP_OPEN				0x06
+#define AVDTP_START				0x07
+#define AVDTP_CLOSE				0x08
+#define AVDTP_SUSPEND				0x09
+#define AVDTP_ABORT				0x0A
+#define AVDTP_SECURITY_CONTROL			0x0B
+
+#define AVDTP_PKT_TYPE_SINGLE			0x00
+#define AVDTP_PKT_TYPE_START			0x01
+#define AVDTP_PKT_TYPE_CONTINUE			0x02
+#define AVDTP_PKT_TYPE_END			0x03
+
+#define AVDTP_MSG_TYPE_COMMAND			0x00
+#define AVDTP_MSG_TYPE_ACCEPT			0x02
+#define AVDTP_MSG_TYPE_REJECT			0x03
+
+#define REQ_TIMEOUT 4
+#define DISCONNECT_TIMEOUT 1
+#define STREAM_TIMEOUT 20
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_common_header {
+	uint8_t message_type:2;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+} __attribute__ ((packed));
+
+struct avdtp_single_header {
+	uint8_t message_type:2;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+	uint8_t signal_id:6;
+	uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct avdtp_start_header {
+	uint8_t message_type:2;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+	uint8_t no_of_packets;
+	uint8_t signal_id:6;
+	uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct avdtp_continue_header {
+	uint8_t message_type:2;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+} __attribute__ ((packed));
+
+struct seid_info {
+	uint8_t rfa0:1;
+	uint8_t inuse:1;
+	uint8_t seid:6;
+	uint8_t rfa2:3;
+	uint8_t type:1;
+	uint8_t media_type:4;
+} __attribute__ ((packed));
+
+struct seid {
+	uint8_t rfa0:2;
+	uint8_t seid:6;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_common_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t message_type:2;
+} __attribute__ ((packed));
+
+struct avdtp_single_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t message_type:2;
+	uint8_t rfa0:2;
+	uint8_t signal_id:6;
+} __attribute__ ((packed));
+
+struct avdtp_start_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t message_type:2;
+	uint8_t no_of_packets;
+	uint8_t rfa0:2;
+	uint8_t signal_id:6;
+} __attribute__ ((packed));
+
+struct avdtp_continue_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t message_type:2;
+} __attribute__ ((packed));
+
+struct seid_info {
+	uint8_t seid:6;
+	uint8_t inuse:1;
+	uint8_t rfa0:1;
+	uint8_t media_type:4;
+	uint8_t type:1;
+	uint8_t rfa2:3;
+} __attribute__ ((packed));
+
+struct seid {
+	uint8_t seid:6;
+	uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+/* packets */
+
+struct discover_resp {
+	struct seid_info seps[0];
+} __attribute__ ((packed));
+
+struct getcap_resp {
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct start_req {
+	struct seid first_seid;
+	struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct suspend_req {
+	struct seid first_seid;
+	struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct seid_rej {
+	uint8_t error;
+} __attribute__ ((packed));
+
+struct conf_rej {
+	uint8_t category;
+	uint8_t error;
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct seid_req {
+	uint8_t rfa0:2;
+	uint8_t acp_seid:6;
+} __attribute__ ((packed));
+
+struct setconf_req {
+	uint8_t rfa0:2;
+	uint8_t acp_seid:6;
+	uint8_t rfa1:2;
+	uint8_t int_seid:6;
+
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+	uint8_t rfa0:2;
+	uint8_t acp_seid:6;
+	uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+	uint8_t rfa0:2;
+	uint8_t acp_seid:6;
+
+	uint8_t serv_cap;
+	uint8_t serv_cap_len;
+
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct seid_req {
+	uint8_t acp_seid:6;
+	uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct setconf_req {
+	uint8_t acp_seid:6;
+	uint8_t rfa0:2;
+	uint8_t int_seid:6;
+	uint8_t rfa1:2;
+
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+	uint8_t acp_seid:6;
+	uint8_t rfa0:2;
+	uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+	uint8_t acp_seid:6;
+	uint8_t rfa0:2;
+
+	uint8_t serv_cap;
+	uint8_t serv_cap_len;
+
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct in_buf {
+	gboolean active;
+	int no_of_packets;
+	uint8_t transaction;
+	uint8_t message_type;
+	uint8_t signal_id;
+	uint8_t buf[1024];
+	uint8_t data_size;
+};
+
+struct pending_req {
+	uint8_t transaction;
+	uint8_t signal_id;
+	void *data;
+	size_t data_size;
+	struct avdtp_stream *stream; /* Set if the request targeted a stream */
+	guint timeout;
+};
+
+struct avdtp_remote_sep {
+	uint8_t seid;
+	uint8_t type;
+	uint8_t media_type;
+	struct avdtp_service_capability *codec;
+	GSList *caps; /* of type struct avdtp_service_capability */
+	struct avdtp_stream *stream;
+};
+
+struct avdtp_server {
+	bdaddr_t src;
+	GIOChannel *io;
+	GSList *seps;
+	GSList *sessions;
+};
+
+struct avdtp_local_sep {
+	avdtp_state_t state;
+	struct avdtp_stream *stream;
+	struct seid_info info;
+	uint8_t codec;
+	GSList *caps;
+	struct avdtp_sep_ind *ind;
+	struct avdtp_sep_cfm *cfm;
+	void *user_data;
+	struct avdtp_server *server;
+};
+
+struct stream_callback {
+	avdtp_stream_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct avdtp_state_callback {
+	avdtp_session_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct avdtp_stream {
+	GIOChannel *io;
+	uint16_t imtu;
+	uint16_t omtu;
+	struct avdtp *session;
+	struct avdtp_local_sep *lsep;
+	uint8_t rseid;
+	GSList *caps;
+	GSList *callbacks;
+	struct avdtp_service_capability *codec;
+	guint io_id;		/* Transport GSource ID */
+	guint timer;		/* Waiting for other side to close or open
+				 * the transport channel */
+	gboolean open_acp;	/* If we are in ACT role for Open */
+	gboolean close_int;	/* If we are in INT role for Close */
+	gboolean abort_int;	/* If we are in INT role for Abort */
+	guint idle_timer;
+};
+
+/* Structure describing an AVDTP connection between two devices */
+
+struct avdtp {
+	int ref;
+	int free_lock;
+
+	struct avdtp_server *server;
+	bdaddr_t dst;
+
+	avdtp_session_state_t state;
+
+	/* True if the session should be automatically disconnected */
+	gboolean auto_dc;
+
+	GIOChannel *io;
+	guint io_id;
+
+	GSList *seps; /* Elements of type struct avdtp_remote_sep * */
+
+	GSList *streams; /* Elements of type struct avdtp_stream * */
+
+	GSList *req_queue; /* Elements of type struct pending_req * */
+	GSList *prio_queue; /* Same as req_queue but is processed before it */
+
+	struct avdtp_stream *pending_open;
+
+	uint16_t imtu;
+	uint16_t omtu;
+
+	struct in_buf in;
+
+	char *buf;
+
+	avdtp_discover_cb_t discov_cb;
+	void *user_data;
+
+	struct pending_req *req;
+
+	guint dc_timer;
+
+	/* Attempt stream setup instead of disconnecting */
+	gboolean stream_setup;
+
+	DBusPendingCall *pending_auth;
+};
+
+static GSList *servers = NULL;
+
+static GSList *avdtp_callbacks = NULL;
+
+static gboolean auto_connect = TRUE;
+
+static int send_request(struct avdtp *session, gboolean priority,
+			struct avdtp_stream *stream, uint8_t signal_id,
+			void *buffer, size_t size);
+static gboolean avdtp_parse_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					uint8_t transaction, uint8_t signal_id,
+					void *buf, int size);
+static gboolean avdtp_parse_rej(struct avdtp *session,
+					struct avdtp_stream *stream,
+					uint8_t transaction, uint8_t signal_id,
+					void *buf, int size);
+static int process_queue(struct avdtp *session);
+static void connection_lost(struct avdtp *session, int err);
+static void avdtp_sep_set_state(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				avdtp_state_t state);
+
+static struct avdtp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		struct avdtp_server *server = l->data;
+
+		if (bacmp(&server->src, src) == 0)
+			return server;
+	}
+
+	return NULL;
+}
+
+static const char *avdtp_statestr(avdtp_state_t state)
+{
+	switch (state) {
+	case AVDTP_STATE_IDLE:
+		return "IDLE";
+	case AVDTP_STATE_CONFIGURED:
+		return "CONFIGURED";
+	case AVDTP_STATE_OPEN:
+		return "OPEN";
+	case AVDTP_STATE_STREAMING:
+		return "STREAMING";
+	case AVDTP_STATE_CLOSING:
+		return "CLOSING";
+	case AVDTP_STATE_ABORTING:
+		return "ABORTING";
+	default:
+		return "<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);
+	}
+
+	/* Count the number of needed fragments */
+	cont_fragments = (len - (session->omtu - sizeof(start))) /
+					(session->omtu - sizeof(cont)) + 1;
+
+	debug("avdtp_send: %zu bytes split into %d fragments", len,
+							cont_fragments + 1);
+
+	/* Send the start packet */
+	memset(&start, 0, sizeof(start));
+	start.transaction = transaction;
+	start.packet_type = AVDTP_PKT_TYPE_START;
+	start.message_type = message_type;
+	start.no_of_packets = cont_fragments + 1;
+	start.signal_id = signal_id;
+
+	memcpy(session->buf, &start, sizeof(start));
+	memcpy(session->buf + sizeof(start), data,
+					session->omtu - sizeof(start));
+
+	if (!try_send(sock, session->buf, session->omtu))
+		return FALSE;
+
+	debug("avdtp_send: first packet with %zu bytes sent",
+						session->omtu - sizeof(start));
+
+	sent = session->omtu - sizeof(start);
+
+	/* Send the continue fragments and the end packet */
+	while (sent < len) {
+		int left, to_copy;
+
+		left = len - sent;
+		if (left + sizeof(cont) > session->omtu) {
+			cont.packet_type = AVDTP_PKT_TYPE_CONTINUE;
+			to_copy = session->omtu - sizeof(cont);
+			debug("avdtp_send: sending continue with %d bytes",
+								to_copy);
+		} else {
+			cont.packet_type = AVDTP_PKT_TYPE_END;
+			to_copy = left;
+			debug("avdtp_send: sending end with %d bytes",
+								to_copy);
+		}
+
+		cont.transaction = transaction;
+		cont.message_type = message_type;
+
+		memcpy(session->buf, &cont, sizeof(cont));
+		memcpy(session->buf + sizeof(cont), data + sent, to_copy);
+
+		if (!try_send(sock, session->buf, to_copy + sizeof(cont)))
+			return FALSE;
+
+		sent += to_copy;
+	}
+
+	return TRUE;
+}
+
+static void pending_req_free(struct pending_req *req)
+{
+	if (req->timeout)
+		g_source_remove(req->timeout);
+	g_free(req->data);
+	g_free(req);
+}
+
+static void close_stream(struct avdtp_stream *stream)
+{
+	int sock;
+
+	if (stream->io == NULL)
+		return;
+
+	sock = g_io_channel_unix_get_fd(stream->io);
+
+	shutdown(sock, SHUT_RDWR);
+
+	g_io_channel_shutdown(stream->io, FALSE, NULL);
+
+	g_io_channel_unref(stream->io);
+	stream->io = NULL;
+}
+
+static gboolean stream_close_timeout(gpointer user_data)
+{
+	struct avdtp_stream *stream = user_data;
+
+	debug("Timed out waiting for peer to close the transport channel");
+
+	stream->timer = 0;
+
+	close_stream(stream);
+
+	return FALSE;
+}
+
+static gboolean stream_open_timeout(gpointer user_data)
+{
+	struct avdtp_stream *stream = user_data;
+
+	debug("Timed out waiting for peer to open the transport channel");
+
+	stream->timer = 0;
+
+	stream->session->pending_open = NULL;
+
+	avdtp_abort(stream->session, stream);
+
+	return FALSE;
+}
+
+static gboolean disconnect_timeout(gpointer user_data)
+{
+	struct avdtp *session = user_data;
+	struct audio_device *dev;
+	gboolean stream_setup;
+
+	session->dc_timer = 0;
+	stream_setup = session->stream_setup;
+	session->stream_setup = FALSE;
+
+	dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+
+	if (dev && dev->sink && stream_setup)
+		sink_setup_stream(dev->sink, session);
+	else if (dev && dev->source && stream_setup)
+		source_setup_stream(dev->source, session);
+	else
+		connection_lost(session, ETIMEDOUT);
+
+	return FALSE;
+}
+
+static void remove_disconnect_timer(struct avdtp *session)
+{
+	g_source_remove(session->dc_timer);
+	session->dc_timer = 0;
+	session->stream_setup = FALSE;
+}
+
+static void set_disconnect_timer(struct avdtp *session)
+{
+	if (session->dc_timer)
+		remove_disconnect_timer(session);
+
+	session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT,
+						disconnect_timeout,
+						session);
+}
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id)
+{
+	err->type = type;
+	switch (type) {
+	case AVDTP_ERROR_ERRNO:
+		err->err.posix_errno = id;
+		break;
+	case AVDTP_ERROR_ERROR_CODE:
+		err->err.error_code = id;
+		break;
+	}
+}
+
+avdtp_error_type_t avdtp_error_type(struct avdtp_error *err)
+{
+	return err->type;
+}
+
+int avdtp_error_error_code(struct avdtp_error *err)
+{
+	assert(err->type == AVDTP_ERROR_ERROR_CODE);
+	return err->err.error_code;
+}
+
+int avdtp_error_posix_errno(struct avdtp_error *err)
+{
+	assert(err->type == AVDTP_ERROR_ERRNO);
+	return err->err.posix_errno;
+}
+
+static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session,
+							uint8_t rseid)
+{
+	GSList *l;
+
+	for (l = session->streams; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_stream *stream = l->data;
+
+		if (stream->rseid == rseid)
+			return stream;
+	}
+
+	return NULL;
+}
+
+static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid)
+{
+	GSList *l;
+
+	for (l = seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_remote_sep *sep = l->data;
+
+		if (sep->seid == seid)
+			return sep;
+	}
+
+	return NULL;
+}
+
+static void avdtp_set_state(struct avdtp *session,
+					avdtp_session_state_t new_state)
+{
+	GSList *l;
+	struct audio_device *dev;
+	bdaddr_t src, dst;
+	avdtp_session_state_t old_state = session->state;
+
+	session->state = new_state;
+
+	avdtp_get_peers(session, &src, &dst);
+	dev = manager_get_device(&src, &dst, FALSE);
+	if (dev == NULL) {
+		error("avdtp_set_state(): no matching audio device");
+		return;
+	}
+
+	for (l = avdtp_callbacks; l != NULL; l = l->next) {
+		struct avdtp_state_callback *cb = l->data;
+		cb->cb(dev, session, old_state, new_state, cb->user_data);
+	}
+}
+
+static void stream_free(struct avdtp_stream *stream)
+{
+	struct avdtp_remote_sep *rsep;
+
+	stream->lsep->info.inuse = 0;
+	stream->lsep->stream = NULL;
+
+	rsep = find_remote_sep(stream->session->seps, stream->rseid);
+	if (rsep)
+		rsep->stream = NULL;
+
+	if (stream->timer)
+		g_source_remove(stream->timer);
+
+	if (stream->io)
+		g_io_channel_unref(stream->io);
+
+	if (stream->io_id)
+		g_source_remove(stream->io_id);
+
+	g_slist_foreach(stream->callbacks, (GFunc) g_free, NULL);
+	g_slist_free(stream->callbacks);
+
+	g_slist_foreach(stream->caps, (GFunc) g_free, NULL);
+	g_slist_free(stream->caps);
+
+	g_free(stream);
+}
+
+static gboolean stream_timeout(gpointer user_data)
+{
+	struct avdtp_stream *stream = user_data;
+	struct avdtp *session = stream->session;
+
+	avdtp_close(session, stream);
+
+	stream->idle_timer = 0;
+
+	return FALSE;
+}
+
+static gboolean transport_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct avdtp_stream *stream = data;
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (stream->close_int && sep->cfm && sep->cfm->close)
+		sep->cfm->close(stream->session, sep, stream, NULL,
+				sep->user_data);
+
+	if (!(cond & G_IO_NVAL))
+		close_stream(stream);
+
+	stream->io_id = 0;
+
+	if (!stream->abort_int)
+		avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE);
+
+	return FALSE;
+}
+
+static void handle_transport_connect(struct avdtp *session, GIOChannel *io,
+					uint16_t imtu, uint16_t omtu)
+{
+	struct avdtp_stream *stream = session->pending_open;
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	session->pending_open = NULL;
+
+	if (stream->timer) {
+		g_source_remove(stream->timer);
+		stream->timer = 0;
+	}
+
+	if (io == NULL) {
+		if (!stream->open_acp && sep->cfm && sep->cfm->open) {
+			struct avdtp_error err;
+			avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+			sep->cfm->open(session, sep, NULL, &err,
+					sep->user_data);
+		}
+		return;
+	}
+
+	stream->io = g_io_channel_ref(io);
+	stream->omtu = omtu;
+	stream->imtu = imtu;
+
+	if (!stream->open_acp && sep->cfm && sep->cfm->open)
+		sep->cfm->open(session, sep, stream, NULL, sep->user_data);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+	stream->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+					(GIOFunc) transport_cb, stream);
+}
+
+static int pending_req_cmp(gconstpointer a, gconstpointer b)
+{
+	const struct pending_req *req = a;
+	const struct avdtp_stream *stream = b;
+
+	if (req->stream == stream)
+		return 0;
+
+	return -1;
+}
+
+static void cleanup_queue(struct avdtp *session, struct avdtp_stream *stream)
+{
+	GSList *l;
+	struct pending_req *req;
+
+	while ((l = g_slist_find_custom(session->prio_queue, stream,
+							pending_req_cmp))) {
+		req = l->data;
+		pending_req_free(req);
+		session->prio_queue = g_slist_remove(session->prio_queue, req);
+	}
+
+	while ((l = g_slist_find_custom(session->req_queue, stream,
+							pending_req_cmp))) {
+		req = l->data;
+		pending_req_free(req);
+		session->req_queue = g_slist_remove(session->req_queue, req);
+	}
+}
+
+static void avdtp_sep_set_state(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				avdtp_state_t state)
+{
+	struct avdtp_stream *stream = sep->stream;
+	avdtp_state_t old_state;
+	struct avdtp_error err, *err_ptr = NULL;
+	GSList *l;
+
+	if (!stream) {
+		error("Error changing sep state: stream not available");
+		return;
+	}
+
+	if (sep->state == state) {
+		avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+		debug("stream state change failed: %s", avdtp_strerror(&err));
+		err_ptr = &err;
+	} else {
+		err_ptr = NULL;
+		debug("stream state changed: %s -> %s",
+				avdtp_statestr(sep->state),
+				avdtp_statestr(state));
+	}
+
+	old_state = sep->state;
+	sep->state = state;
+
+	for (l = stream->callbacks; l != NULL; l = g_slist_next(l)) {
+		struct stream_callback *cb = l->data;
+		cb->cb(stream, old_state, state, err_ptr, cb->user_data);
+	}
+
+	switch (state) {
+	case AVDTP_STATE_OPEN:
+		if (old_state > AVDTP_STATE_OPEN && session->auto_dc)
+			stream->idle_timer = g_timeout_add_seconds(STREAM_TIMEOUT,
+								stream_timeout,
+								stream);
+		break;
+	case AVDTP_STATE_STREAMING:
+	case AVDTP_STATE_CLOSING:
+	case AVDTP_STATE_ABORTING:
+		if (stream->idle_timer) {
+			g_source_remove(stream->idle_timer);
+			stream->idle_timer = 0;
+		}
+		break;
+	case AVDTP_STATE_IDLE:
+		if (stream->idle_timer) {
+			g_source_remove(stream->idle_timer);
+			stream->idle_timer = 0;
+		}
+		session->streams = g_slist_remove(session->streams, stream);
+		if (session->pending_open == stream)
+			handle_transport_connect(session, NULL, 0, 0);
+		if (session->req && session->req->stream == stream)
+			session->req->stream = NULL;
+		/* Remove pending commands for this stream from the queue */
+		cleanup_queue(session, stream);
+		stream_free(stream);
+		if (session->ref == 1 && !session->streams)
+			set_disconnect_timer(session);
+		break;
+	default:
+		break;
+	}
+}
+
+static void finalize_discovery(struct avdtp *session, int err)
+{
+	struct avdtp_error avdtp_err;
+
+	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, err);
+
+	if (!session->discov_cb)
+		return;
+
+	session->discov_cb(session, session->seps,
+				err ? &avdtp_err : NULL,
+				session->user_data);
+
+	session->discov_cb = NULL;
+	session->user_data = NULL;
+}
+
+static void release_stream(struct avdtp_stream *stream, struct avdtp *session)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (sep->cfm && sep->cfm->abort &&
+				(sep->state != AVDTP_STATE_ABORTING ||
+							stream->abort_int))
+		sep->cfm->abort(session, sep, stream, NULL, sep->user_data);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+}
+
+static void connection_lost(struct avdtp *session, int err)
+{
+	char address[18];
+
+	ba2str(&session->dst, address);
+	debug("Disconnected from %s", address);
+
+	if (session->state == AVDTP_SESSION_STATE_CONNECTING && err != EACCES)
+		btd_cancel_authorization(&session->server->src, &session->dst);
+
+	session->free_lock = 1;
+
+	finalize_discovery(session, err);
+
+	g_slist_foreach(session->streams, (GFunc) release_stream, session);
+	session->streams = NULL;
+
+	session->free_lock = 0;
+
+	if (session->io) {
+		g_io_channel_shutdown(session->io, FALSE, NULL);
+		g_io_channel_unref(session->io);
+		session->io = NULL;
+	}
+
+	avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED);
+
+	if (session->io_id) {
+		g_source_remove(session->io_id);
+		session->io_id = 0;
+	}
+
+	if (session->dc_timer)
+		remove_disconnect_timer(session);
+
+	session->auto_dc = TRUE;
+
+	if (session->ref != 1)
+		error("connection_lost: ref count not 1 after all callbacks");
+	else
+		avdtp_unref(session);
+}
+
+void avdtp_unref(struct avdtp *session)
+{
+	struct avdtp_server *server;
+
+	if (!session)
+		return;
+
+	session->ref--;
+
+	debug("avdtp_unref(%p): ref=%d", session, session->ref);
+
+	if (session->ref == 1) {
+		if (session->state == AVDTP_SESSION_STATE_CONNECTING &&
+								session->io) {
+			btd_cancel_authorization(&session->server->src,
+							&session->dst);
+			g_io_channel_shutdown(session->io, TRUE, NULL);
+			g_io_channel_unref(session->io);
+			session->io = NULL;
+		}
+
+		if (session->io)
+			set_disconnect_timer(session);
+		else if (!session->free_lock) /* Drop the local ref if we
+						 aren't connected */
+			session->ref--;
+	}
+
+	if (session->ref > 0)
+		return;
+
+	server = session->server;
+
+	debug("avdtp_unref(%p): freeing session and removing from list",
+			session);
+
+	if (session->dc_timer)
+		remove_disconnect_timer(session);
+
+	server->sessions = g_slist_remove(server->sessions, session);
+
+	if (session->req)
+		pending_req_free(session->req);
+
+	g_slist_foreach(session->seps, (GFunc) g_free, NULL);
+	g_slist_free(session->seps);
+
+	g_free(session->buf);
+
+	g_free(session);
+}
+
+struct avdtp *avdtp_ref(struct avdtp *session)
+{
+	session->ref++;
+	debug("avdtp_ref(%p): ref=%d", session, session->ref);
+	if (session->dc_timer)
+		remove_disconnect_timer(session);
+	return session;
+}
+
+static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp_server *server,
+							uint8_t seid)
+{
+	GSList *l;
+
+	for (l = server->seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_local_sep *sep = l->data;
+
+		if (sep->info.seid == seid)
+			return sep;
+	}
+
+	return NULL;
+}
+
+static struct avdtp_local_sep *find_local_sep(struct avdtp_server *server,
+						uint8_t type,
+						uint8_t media_type,
+						uint8_t codec)
+{
+	GSList *l;
+
+	for (l = server->seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_local_sep *sep = l->data;
+
+		if (sep->info.inuse)
+			continue;
+
+		if (sep->info.type == type &&
+				sep->info.media_type == media_type &&
+				sep->codec == codec)
+			return sep;
+	}
+
+	return NULL;
+}
+
+static GSList *caps_to_list(uint8_t *data, int size,
+				struct avdtp_service_capability **codec)
+{
+	GSList *caps;
+	int processed;
+
+	for (processed = 0, caps = NULL; processed + 2 < size;) {
+		struct avdtp_service_capability *cap;
+		uint8_t length, category;
+
+		category = data[0];
+		length = data[1];
+
+		if (processed + 2 + length > size) {
+			error("Invalid capability data in getcap resp");
+			break;
+		}
+
+		cap = g_malloc(sizeof(struct avdtp_service_capability) +
+					length);
+		memcpy(cap, data, 2 + length);
+
+		processed += 2 + length;
+		data += 2 + length;
+
+		caps = g_slist_append(caps, cap);
+
+		if (category == AVDTP_MEDIA_CODEC &&
+				length >=
+				sizeof(struct avdtp_media_codec_capability))
+			*codec = cap;
+	}
+
+	return caps;
+}
+
+static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction,
+							void *buf, int size)
+{
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+								0, NULL, 0);
+}
+
+static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction,
+							void *buf, int size)
+{
+	GSList *l;
+	unsigned int rsp_size, sep_count, i;
+	struct seid_info *seps;
+	gboolean ret;
+
+	sep_count = g_slist_length(session->server->seps);
+
+	if (sep_count == 0) {
+		uint8_t err = AVDTP_NOT_SUPPORTED_COMMAND;
+		return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+					AVDTP_DISCOVER, &err, sizeof(err));
+	}
+
+	rsp_size = sep_count * sizeof(struct seid_info);
+
+	seps = g_new0(struct seid_info, sep_count);
+
+	for (l = session->server->seps, i = 0; l != NULL; l = l->next, i++) {
+		struct avdtp_local_sep *sep = l->data;
+
+		memcpy(&seps[i], &sep->info, sizeof(struct seid_info));
+	}
+
+	ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+				AVDTP_DISCOVER, seps, rsp_size);
+	g_free(seps);
+
+	return ret;
+}
+
+static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction,
+					struct seid_req *req, unsigned int size)
+{
+	GSList *l, *caps;
+	struct avdtp_local_sep *sep = NULL;
+	unsigned int rsp_size;
+	uint8_t err, buf[1024], *ptr = buf;
+
+	if (size < sizeof(struct seid_req)) {
+		err = AVDTP_BAD_LENGTH;
+		goto failed;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (!sep->ind->get_capability(session, sep, &caps, &err,
+					sep->user_data))
+		goto failed;
+
+	for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (rsp_size + cap->length + 2 > sizeof(buf))
+			break;
+
+		memcpy(ptr, cap, cap->length + 2);
+		rsp_size += cap->length + 2;
+		ptr += cap->length + 2;
+
+		g_free(cap);
+	}
+
+	g_slist_free(caps);
+
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+				AVDTP_GET_CAPABILITIES, buf, rsp_size);
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_GET_CAPABILITIES, &err, sizeof(err));
+}
+
+static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction,
+				struct setconf_req *req, unsigned int size)
+{
+	struct conf_rej rej;
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	uint8_t err, category = 0x00;
+	struct audio_device *dev;
+	bdaddr_t src, dst;
+	GSList *l;
+
+	if (size < sizeof(struct setconf_req)) {
+		error("Too short getcap request");
+		return FALSE;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (sep->stream) {
+		err = AVDTP_SEP_IN_USE;
+		goto failed;
+	}
+
+	avdtp_get_peers(session, &src, &dst);
+	dev = manager_get_device(&src, &dst, FALSE);
+	if (!dev) {
+		error("Unable to get a audio device object");
+		goto failed;
+	}
+
+	switch (sep->info.type) {
+	case AVDTP_SEP_TYPE_SOURCE:
+		if (!dev->sink) {
+			btd_device_add_uuid(dev->btd_dev, A2DP_SINK_UUID);
+			if (!dev->sink) {
+				error("Unable to get a audio sink object");
+				goto failed;
+			}
+		}
+		break;
+	case AVDTP_SEP_TYPE_SINK:
+		/* Do source_init() here when it's implemented */
+		break;
+	}
+
+	stream = g_new0(struct avdtp_stream, 1);
+	stream->session = session;
+	stream->lsep = sep;
+	stream->rseid = req->int_seid;
+	stream->caps = caps_to_list(req->caps,
+					size - sizeof(struct setconf_req),
+					&stream->codec);
+
+	/* Verify that the Media Transport capability's length = 0. Reject otherwise */
+	for (l = stream->caps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (cap->category == AVDTP_MEDIA_TRANSPORT && cap->length != 0) {
+			err = AVDTP_BAD_MEDIA_TRANSPORT_FORMAT;
+			goto failed_stream;
+		}
+	}
+
+	if (sep->ind && sep->ind->set_configuration) {
+		if (!sep->ind->set_configuration(session, sep, stream,
+							stream->caps, &err,
+							&category,
+							sep->user_data))
+			goto failed_stream;
+	}
+
+	if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+					AVDTP_SET_CONFIGURATION, NULL, 0)) {
+		stream_free(stream);
+		return FALSE;
+	}
+
+	sep->stream = stream;
+	session->streams = g_slist_append(session->streams, stream);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+
+	return TRUE;
+
+failed_stream:
+	stream_free(stream);
+failed:
+	rej.error = err;
+	rej.category = category;
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_SET_CONFIGURATION, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_getconf_cmd(struct avdtp *session, uint8_t transaction,
+					struct seid_req *req, int size)
+{
+	GSList *l;
+	struct avdtp_local_sep *sep = NULL;
+	int rsp_size;
+	uint8_t err;
+	uint8_t buf[1024];
+	uint8_t *ptr = buf;
+
+	if (size < (int) sizeof(struct seid_req)) {
+		error("Too short getconf request");
+		return FALSE;
+	}
+
+	memset(buf, 0, sizeof(buf));
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+	if (!sep->stream || !sep->stream->caps) {
+		err = AVDTP_UNSUPPORTED_CONFIGURATION;
+		goto failed;
+	}
+
+	for (l = sep->stream->caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (rsp_size + cap->length + 2 > (int) sizeof(buf))
+			break;
+
+		memcpy(ptr, cap, cap->length + 2);
+		rsp_size += cap->length + 2;
+		ptr += cap->length + 2;
+	}
+
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+				AVDTP_GET_CONFIGURATION, buf, rsp_size);
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_GET_CONFIGURATION, &err, sizeof(err));
+}
+
+static gboolean avdtp_reconf_cmd(struct avdtp *session, uint8_t transaction,
+					struct seid_req *req, int size)
+{
+	return avdtp_unknown_cmd(session, transaction, (void *) req, size);
+}
+
+static gboolean avdtp_open_cmd(struct avdtp *session, uint8_t transaction,
+				struct seid_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	uint8_t err;
+
+	if (size < sizeof(struct seid_req)) {
+		error("Too short abort request");
+		return FALSE;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (sep->state != AVDTP_STATE_CONFIGURED) {
+		err = AVDTP_BAD_STATE;
+		goto failed;
+	}
+
+	stream = sep->stream;
+
+	if (sep->ind && sep->ind->open) {
+		if (!sep->ind->open(session, sep, stream, &err,
+					sep->user_data))
+			goto failed;
+	}
+
+	if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_OPEN, NULL, 0))
+		return FALSE;
+
+	stream->open_acp = TRUE;
+	session->pending_open = stream;
+	stream->timer = g_timeout_add_seconds(REQ_TIMEOUT,
+						stream_open_timeout,
+						stream);
+
+	return TRUE;
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_OPEN, &err, sizeof(err));
+}
+
+static gboolean avdtp_start_cmd(struct avdtp *session, uint8_t transaction,
+				struct start_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	struct stream_rej rej;
+	struct seid *seid;
+	uint8_t err, failed_seid;
+	int seid_count, i;
+
+	if (size < sizeof(struct start_req)) {
+		error("Too short start request");
+		return FALSE;
+	}
+
+	seid_count = 1 + size - sizeof(struct start_req);
+
+	seid = &req->first_seid;
+
+	for (i = 0; i < seid_count; i++, seid++) {
+		failed_seid = seid->seid;
+
+		sep = find_local_sep_by_seid(session->server,
+					req->first_seid.seid);
+		if (!sep || !sep->stream) {
+			err = AVDTP_BAD_ACP_SEID;
+			goto failed;
+		}
+
+		stream = sep->stream;
+
+		if (sep->state != AVDTP_STATE_OPEN) {
+			err = AVDTP_BAD_STATE;
+			goto failed;
+		}
+
+		if (sep->ind && sep->ind->start) {
+			if (!sep->ind->start(session, sep, stream, &err,
+						sep->user_data))
+				goto failed;
+		}
+
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
+	}
+
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_START, NULL, 0);
+
+failed:
+	memset(&rej, 0, sizeof(rej));
+	rej.acp_seid = failed_seid;
+	rej.error = err;
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_START, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_close_cmd(struct avdtp *session, uint8_t transaction,
+				struct seid_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	uint8_t err;
+
+	if (size < sizeof(struct seid_req)) {
+		error("Too short close request");
+		return FALSE;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep || !sep->stream) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (sep->state != AVDTP_STATE_OPEN &&
+			sep->state != AVDTP_STATE_STREAMING) {
+		err = AVDTP_BAD_STATE;
+		goto failed;
+	}
+
+	stream = sep->stream;
+
+	if (sep->ind && sep->ind->close) {
+		if (!sep->ind->close(session, sep, stream, &err,
+					sep->user_data))
+			goto failed;
+	}
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING);
+
+	if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_CLOSE, NULL, 0))
+		return FALSE;
+
+	stream->timer = g_timeout_add_seconds(REQ_TIMEOUT,
+					stream_close_timeout,
+					stream);
+
+	return TRUE;
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+					AVDTP_CLOSE, &err, sizeof(err));
+}
+
+static gboolean avdtp_suspend_cmd(struct avdtp *session, uint8_t transaction,
+				struct suspend_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	struct stream_rej rej;
+	struct seid *seid;
+	uint8_t err, failed_seid;
+	int seid_count, i;
+
+	if (size < sizeof(struct suspend_req)) {
+		error("Too short suspend request");
+		return FALSE;
+	}
+
+	seid_count = 1 + size - sizeof(struct suspend_req);
+
+	seid = &req->first_seid;
+
+	for (i = 0; i < seid_count; i++, seid++) {
+		failed_seid = seid->seid;
+
+		sep = find_local_sep_by_seid(session->server,
+					req->first_seid.seid);
+		if (!sep || !sep->stream) {
+			err = AVDTP_BAD_ACP_SEID;
+			goto failed;
+		}
+
+		stream = sep->stream;
+
+		if (sep->state != AVDTP_STATE_STREAMING) {
+			err = AVDTP_BAD_STATE;
+			goto failed;
+		}
+
+		if (sep->ind && sep->ind->suspend) {
+			if (!sep->ind->suspend(session, sep, stream, &err,
+						sep->user_data))
+				goto failed;
+		}
+
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+	}
+
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_SUSPEND, NULL, 0);
+
+failed:
+	memset(&rej, 0, sizeof(rej));
+	rej.acp_seid = failed_seid;
+	rej.error = err;
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_SUSPEND, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_abort_cmd(struct avdtp *session, uint8_t transaction,
+				struct seid_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	uint8_t err;
+	gboolean ret;
+
+	if (size < sizeof(struct seid_req)) {
+		error("Too short abort request");
+		return FALSE;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep || !sep->stream) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (sep->ind && sep->ind->abort) {
+		if (!sep->ind->abort(session, sep, sep->stream, &err,
+					sep->user_data))
+			goto failed;
+	}
+
+	ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_ABORT, NULL, 0);
+	if (ret)
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+	return ret;
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+					AVDTP_ABORT, &err, sizeof(err));
+}
+
+static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction,
+					struct seid_req *req, int size)
+{
+	return avdtp_unknown_cmd(session, transaction, (void *) req, size);
+}
+
+static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
+				uint8_t signal_id, void *buf, int size)
+{
+	switch (signal_id) {
+	case AVDTP_DISCOVER:
+		debug("Received DISCOVER_CMD");
+		return avdtp_discover_cmd(session, transaction, buf, size);
+	case AVDTP_GET_CAPABILITIES:
+		debug("Received  GET_CAPABILITIES_CMD");
+		return avdtp_getcap_cmd(session, transaction, buf, size);
+	case AVDTP_SET_CONFIGURATION:
+		debug("Received SET_CONFIGURATION_CMD");
+		return avdtp_setconf_cmd(session, transaction, buf, size);
+	case AVDTP_GET_CONFIGURATION:
+		debug("Received GET_CONFIGURATION_CMD");
+		return avdtp_getconf_cmd(session, transaction, buf, size);
+	case AVDTP_RECONFIGURE:
+		debug("Received RECONFIGURE_CMD");
+		return avdtp_reconf_cmd(session, transaction, buf, size);
+	case AVDTP_OPEN:
+		debug("Received OPEN_CMD");
+		return avdtp_open_cmd(session, transaction, buf, size);
+	case AVDTP_START:
+		debug("Received START_CMD");
+		return avdtp_start_cmd(session, transaction, buf, size);
+	case AVDTP_CLOSE:
+		debug("Received CLOSE_CMD");
+		return avdtp_close_cmd(session, transaction, buf, size);
+	case AVDTP_SUSPEND:
+		debug("Received SUSPEND_CMD");
+		return avdtp_suspend_cmd(session, transaction, buf, size);
+	case AVDTP_ABORT:
+		debug("Received ABORT_CMD");
+		return avdtp_abort_cmd(session, transaction, buf, size);
+	case AVDTP_SECURITY_CONTROL:
+		debug("Received SECURITY_CONTROL_CMD");
+		return avdtp_secctl_cmd(session, transaction, buf, size);
+	default:
+		debug("Received unknown request id %u", signal_id);
+		return avdtp_unknown_cmd(session, transaction, buf, size);
+	}
+}
+
+enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS };
+
+static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session,
+							void *buf, size_t size)
+{
+	struct avdtp_common_header *header = buf;
+	struct avdtp_single_header *single = (void *) session->buf;
+	struct avdtp_start_header *start = (void *) session->buf;
+	void *payload;
+	gsize payload_size;
+
+	switch (header->packet_type) {
+	case AVDTP_PKT_TYPE_SINGLE:
+		if (size < sizeof(*single)) {
+			error("Received too small single packet (%zu bytes)", size);
+			return PARSE_ERROR;
+		}
+		if (session->in.active) {
+			error("SINGLE: Invalid AVDTP packet fragmentation");
+			return PARSE_ERROR;
+		}
+
+		payload = session->buf + sizeof(*single);
+		payload_size = size - sizeof(*single);
+
+		session->in.active = TRUE;
+		session->in.data_size = 0;
+		session->in.no_of_packets = 1;
+		session->in.transaction = header->transaction;
+		session->in.message_type = header->message_type;
+		session->in.signal_id = single->signal_id;
+
+		break;
+	case AVDTP_PKT_TYPE_START:
+		if (size < sizeof(*start)) {
+			error("Received too small start packet (%zu bytes)", size);
+			return PARSE_ERROR;
+		}
+		if (session->in.active) {
+			error("START: Invalid AVDTP packet fragmentation");
+			return PARSE_ERROR;
+		}
+
+		session->in.active = TRUE;
+		session->in.data_size = 0;
+		session->in.transaction = header->transaction;
+		session->in.message_type = header->message_type;
+		session->in.no_of_packets = start->no_of_packets;
+		session->in.signal_id = start->signal_id;
+
+		payload = session->buf + sizeof(*start);
+		payload_size = size - sizeof(*start);
+
+		break;
+	case AVDTP_PKT_TYPE_CONTINUE:
+		if (size < sizeof(struct avdtp_continue_header)) {
+			error("Received too small continue packet (%zu bytes)",
+									size);
+			return PARSE_ERROR;
+		}
+		if (!session->in.active) {
+			error("CONTINUE: Invalid AVDTP packet fragmentation");
+			return PARSE_ERROR;
+		}
+		if (session->in.transaction != header->transaction) {
+			error("Continue transaction id doesn't match");
+			return PARSE_ERROR;
+		}
+		if (session->in.no_of_packets <= 1) {
+			error("Too few continue packets");
+			return PARSE_ERROR;
+		}
+
+		payload = session->buf + sizeof(struct avdtp_continue_header);
+		payload_size = size - sizeof(struct avdtp_continue_header);
+
+		break;
+	case AVDTP_PKT_TYPE_END:
+		if (size < sizeof(struct avdtp_continue_header)) {
+			error("Received too small end packet (%zu bytes)", size);
+			return PARSE_ERROR;
+		}
+		if (!session->in.active) {
+			error("END: Invalid AVDTP packet fragmentation");
+			return PARSE_ERROR;
+		}
+		if (session->in.transaction != header->transaction) {
+			error("End transaction id doesn't match");
+			return PARSE_ERROR;
+		}
+		if (session->in.no_of_packets > 1) {
+			error("Got an end packet too early");
+			return PARSE_ERROR;
+		}
+
+		payload = session->buf + sizeof(struct avdtp_continue_header);
+		payload_size = size - sizeof(struct avdtp_continue_header);
+
+		break;
+	default:
+		error("Invalid AVDTP packet type 0x%02X", header->packet_type);
+		return PARSE_ERROR;
+	}
+
+	if (session->in.data_size + payload_size >
+					sizeof(session->in.buf)) {
+		error("Not enough incoming buffer space!");
+		return PARSE_ERROR;
+	}
+
+	memcpy(session->in.buf + session->in.data_size, payload, payload_size);
+	session->in.data_size += payload_size;
+
+	if (session->in.no_of_packets > 1) {
+		session->in.no_of_packets--;
+		debug("Received AVDTP fragment. %d to go",
+						session->in.no_of_packets);
+		return PARSE_FRAGMENT;
+	}
+
+	session->in.active = FALSE;
+
+	return PARSE_SUCCESS;
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct avdtp *session = data;
+	struct avdtp_common_header *header;
+	gsize size;
+
+	debug("session_cb");
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	header = (void *) session->buf;
+
+	if (cond & (G_IO_HUP | G_IO_ERR))
+		goto failed;
+
+	if (g_io_channel_read(chan, session->buf, session->imtu, &size)
+							!= G_IO_ERROR_NONE) {
+		error("IO Channel read error");
+		goto failed;
+	}
+
+	if (size < sizeof(struct avdtp_common_header)) {
+		error("Received too small packet (%zu bytes)", size);
+		goto failed;
+	}
+
+	switch (avdtp_parse_data(session, session->buf, size)) {
+	case PARSE_ERROR:
+		goto failed;
+	case PARSE_FRAGMENT:
+		return TRUE;
+	case PARSE_SUCCESS:
+		break;
+	}
+
+	if (session->in.message_type == AVDTP_MSG_TYPE_COMMAND) {
+		if (!avdtp_parse_cmd(session, session->in.transaction,
+					session->in.signal_id,
+					session->in.buf,
+					session->in.data_size)) {
+			error("Unable to handle command. Disconnecting");
+			goto failed;
+		}
+
+		if (session->ref == 1 && !session->streams && !session->req)
+			set_disconnect_timer(session);
+
+		if (session->streams && session->dc_timer)
+			remove_disconnect_timer(session);
+
+		return TRUE;
+	}
+
+	if (session->req == NULL) {
+		error("No pending request, ignoring message");
+		return TRUE;
+	}
+
+	if (header->transaction != session->req->transaction) {
+		error("Transaction label doesn't match");
+		return TRUE;
+	}
+
+	if (session->in.signal_id != session->req->signal_id) {
+		error("Reponse signal doesn't match");
+		return TRUE;
+	}
+
+	g_source_remove(session->req->timeout);
+	session->req->timeout = 0;
+
+	switch (header->message_type) {
+	case AVDTP_MSG_TYPE_ACCEPT:
+		if (!avdtp_parse_resp(session, session->req->stream,
+						session->in.transaction,
+						session->in.signal_id,
+						session->in.buf,
+						session->in.data_size)) {
+			error("Unable to parse accept response");
+			goto failed;
+		}
+		break;
+	case AVDTP_MSG_TYPE_REJECT:
+		if (!avdtp_parse_rej(session, session->req->stream,
+						session->in.transaction,
+						session->in.signal_id,
+						session->in.buf,
+						session->in.data_size)) {
+			error("Unable to parse reject response");
+			goto failed;
+		}
+		break;
+	default:
+		error("Unknown message type 0x%02X", header->message_type);
+		break;
+	}
+
+	pending_req_free(session->req);
+	session->req = NULL;
+
+	process_queue(session);
+
+	return TRUE;
+
+failed:
+	connection_lost(session, EIO);
+
+	return FALSE;
+}
+
+static struct avdtp *find_session(GSList *list, const bdaddr_t *dst)
+{
+	GSList *l;
+
+	for (l = list; l != NULL; l = g_slist_next(l)) {
+		struct avdtp *s = l->data;
+
+		if (bacmp(dst, &s->dst))
+			continue;
+
+		return s;
+	}
+
+	return NULL;
+}
+
+static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	struct avdtp_server *server;
+	struct avdtp *session;
+
+	assert(src != NULL);
+	assert(dst != NULL);
+
+	server = find_server(servers, src);
+	if (server == NULL)
+		return NULL;
+
+	session = find_session(server->sessions, dst);
+	if (session) {
+		if (session->pending_auth)
+			return NULL;
+		else
+			return session;
+	}
+
+	session = g_new0(struct avdtp, 1);
+
+	session->server = server;
+	bacpy(&session->dst, dst);
+	session->ref = 1;
+	/* We don't use avdtp_set_state() here since this isn't a state change
+	 * but just setting of the initial state */
+	session->state = AVDTP_SESSION_STATE_DISCONNECTED;
+	session->auto_dc = TRUE;
+
+	server->sessions = g_slist_append(server->sessions, session);
+
+	return session;
+}
+
+struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst)
+{
+	struct avdtp *session;
+
+	session = avdtp_get_internal(src, dst);
+
+	if (!session)
+		return NULL;
+
+	return avdtp_ref(session);
+}
+
+static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+	struct avdtp *session = user_data;
+	char address[18];
+	GError *gerr = NULL;
+
+	if (err) {
+		error("%s", err->message);
+		goto failed;
+	}
+
+	if (!session->io)
+		session->io = g_io_channel_ref(chan);
+
+	bt_io_get(chan, BT_IO_L2CAP, &gerr,
+			BT_IO_OPT_OMTU, &session->omtu,
+			BT_IO_OPT_IMTU, &session->imtu,
+			BT_IO_OPT_INVALID);
+	if (gerr) {
+		error("%s", gerr->message);
+		g_error_free(gerr);
+		goto failed;
+	}
+
+	ba2str(&session->dst, address);
+	debug("AVDTP: connected %s channel to %s",
+			session->pending_open ? "transport" : "signaling",
+			address);
+
+	if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+		debug("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu);
+
+		session->buf = g_malloc0(session->imtu);
+		avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED);
+
+		if (session->io_id)
+			g_source_remove(session->io_id);
+
+		/* This watch should be low priority since otherwise the
+		 * connect callback might be dispatched before the session
+		 * callback if the kernel wakes us up at the same time for
+		 * them. This could happen if a headset is very quick in
+		 * sending the Start command after connecting the stream
+		 * transport channel.
+		 */
+		session->io_id = g_io_add_watch_full(chan,
+						G_PRIORITY_LOW,
+						G_IO_IN | G_IO_ERR | G_IO_HUP
+						| G_IO_NVAL,
+						(GIOFunc) session_cb, session,
+						NULL);
+
+		if (session->stream_setup) {
+			set_disconnect_timer(session);
+			avdtp_set_auto_disconnect(session, FALSE);
+		}
+	} else if (session->pending_open)
+		handle_transport_connect(session, chan, session->imtu,
+								session->omtu);
+	else
+		goto failed;
+
+	process_queue(session);
+
+	return;
+
+failed:
+	if (session->pending_open) {
+		struct avdtp_stream *stream = session->pending_open;
+
+		handle_transport_connect(session, NULL, 0, 0);
+
+		if (avdtp_abort(session, stream) < 0)
+			avdtp_sep_set_state(session, stream->lsep,
+						AVDTP_STATE_IDLE);
+	} else
+		connection_lost(session, EIO);
+
+	return;
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+	struct avdtp *session = user_data;
+	GError *err = NULL;
+
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+		connection_lost(session, EACCES);
+		return;
+	}
+
+	if (!bt_io_accept(session->io, avdtp_connect_cb, session, NULL,
+								&err)) {
+		error("bt_io_accept: %s", err->message);
+		connection_lost(session, EACCES);
+		g_error_free(err);
+		return;
+	}
+
+	/* This is so that avdtp_connect_cb will know to do the right thing
+	 * with respect to the disconnect timer */
+	session->stream_setup = TRUE;
+}
+
+static void avdtp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+	struct avdtp *session;
+	struct audio_device *dev;
+	char address[18];
+	bdaddr_t src, dst;
+	int perr;
+	GError *err = NULL;
+
+	bt_io_get(chan, BT_IO_L2CAP, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_DEST, address,
+			BT_IO_OPT_INVALID);
+	if (err) {
+		error("%s", err->message);
+		g_error_free(err);
+		goto drop;
+	}
+
+	debug("AVDTP: incoming connect from %s", address);
+
+	session = avdtp_get_internal(&src, &dst);
+	if (!session)
+		goto drop;
+
+	/* This state (ie, session is already *connecting*) happens when the
+	 * device initiates a connect (really a config'd L2CAP channel) even
+	 * though there is a connect we initiated in progress. In sink.c &
+	 * source.c, this state is referred to as XCASE connect:connect.
+	 * Abort the device's channel in favor of our own.
+	 */
+	if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+		debug("avdtp_confirm_cb: connect already in progress"
+						" (XCASE connect:connect)");
+		goto drop;
+	}
+
+	if (session->pending_open && session->pending_open->open_acp) {
+		if (!bt_io_accept(chan, avdtp_connect_cb, session, NULL, NULL))
+			goto drop;
+		return;
+	}
+
+	if (session->io) {
+		error("Refusing unexpected connect from %s", address);
+		goto drop;
+	}
+
+	dev = manager_get_device(&src, &dst, FALSE);
+	if (!dev) {
+		dev = manager_get_device(&src, &dst, TRUE);
+		if (!dev) {
+			error("Unable to get audio device object for %s",
+					address);
+			goto drop;
+		}
+		btd_device_add_uuid(dev->btd_dev, ADVANCED_AUDIO_UUID);
+	}
+
+	session->io = g_io_channel_ref(chan);
+	avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING);
+
+	session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+					(GIOFunc) session_cb, session);
+
+	perr = audio_device_request_authorization(dev, ADVANCED_AUDIO_UUID,
+							auth_cb, session);
+	if (perr < 0) {
+		avdtp_unref(session);
+		goto drop;
+	}
+
+	dev->auto_connect = auto_connect;
+
+	return;
+
+drop:
+	g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static int l2cap_connect(struct avdtp *session)
+{
+	GError *err = NULL;
+	GIOChannel *io;
+
+	io = bt_io_connect(BT_IO_L2CAP, avdtp_connect_cb, session,
+				NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &session->server->src,
+				BT_IO_OPT_DEST_BDADDR, &session->dst,
+				BT_IO_OPT_PSM, AVDTP_PSM,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+		return -EIO;
+	}
+
+	g_io_channel_unref(io);
+
+	return 0;
+}
+
+static void queue_request(struct avdtp *session, struct pending_req *req,
+			gboolean priority)
+{
+	if (priority)
+		session->prio_queue = g_slist_append(session->prio_queue, req);
+	else
+		session->req_queue = g_slist_append(session->req_queue, req);
+}
+
+static uint8_t req_get_seid(struct pending_req *req)
+{
+	if (req->signal_id == AVDTP_DISCOVER)
+		return 0;
+
+	return ((struct seid_req *) (req->data))->acp_seid;
+}
+
+static gboolean request_timeout(gpointer user_data)
+{
+	struct avdtp *session = user_data;
+	struct pending_req *req;
+	struct seid_req sreq;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_stream *stream;
+	uint8_t seid;
+	struct avdtp_error err;
+
+	req = session->req;
+	session->req = NULL;
+
+	avdtp_error_init(&err, AVDTP_ERROR_ERRNO, ETIMEDOUT);
+
+	seid = req_get_seid(req);
+	if (seid)
+		stream = find_stream_by_rseid(session, seid);
+	else
+		stream = NULL;
+
+	if (stream)
+		lsep = stream->lsep;
+	else
+		lsep = NULL;
+
+	switch (req->signal_id) {
+	case AVDTP_RECONFIGURE:
+		error("Reconfigure request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+			lsep->cfm->reconfigure(session, lsep, stream, &err,
+						lsep->user_data);
+		break;
+	case AVDTP_OPEN:
+		error("Open request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->open)
+			lsep->cfm->open(session, lsep, stream, &err,
+					lsep->user_data);
+		break;
+	case AVDTP_START:
+		error("Start request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->start)
+			lsep->cfm->start(session, lsep, stream, &err,
+						lsep->user_data);
+		break;
+	case AVDTP_SUSPEND:
+		error("Suspend request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->suspend)
+			lsep->cfm->suspend(session, lsep, stream, &err,
+						lsep->user_data);
+		break;
+	case AVDTP_CLOSE:
+		error("Close request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->close) {
+			lsep->cfm->close(session, lsep, stream, &err,
+						lsep->user_data);
+			if (stream)
+				stream->close_int = FALSE;
+		}
+		break;
+	case AVDTP_SET_CONFIGURATION:
+		error("SetConfiguration request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+			lsep->cfm->set_configuration(session, lsep, stream,
+							&err, lsep->user_data);
+		goto failed;
+	case AVDTP_DISCOVER:
+		error("Discover request timed out");
+		goto failed;
+	case AVDTP_GET_CAPABILITIES:
+		error("GetCapabilities request timed out");
+		goto failed;
+	case AVDTP_ABORT:
+		error("Abort request timed out");
+		goto failed;
+	}
+
+	if (!stream)
+		goto failed;
+
+	memset(&sreq, 0, sizeof(sreq));
+	sreq.acp_seid = seid;
+
+	if (send_request(session, TRUE, stream, AVDTP_ABORT,
+						&sreq, sizeof(sreq)) < 0) {
+		error("Unable to send abort request");
+		goto failed;
+	}
+
+	stream->abort_int = TRUE;
+
+	goto done;
+
+failed:
+	connection_lost(session, ETIMEDOUT);
+done:
+	pending_req_free(req);
+	return FALSE;
+}
+
+static int send_req(struct avdtp *session, gboolean priority,
+			struct pending_req *req)
+{
+	static int transaction = 0;
+	int err;
+
+	if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) {
+		err = l2cap_connect(session);
+		if (err < 0)
+			goto failed;
+		avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING);
+	}
+
+	if (session->state < AVDTP_SESSION_STATE_CONNECTED ||
+			session->req != NULL) {
+		queue_request(session, req, priority);
+		return 0;
+	}
+
+	req->transaction = transaction++;
+	transaction %= 16;
+
+	/* FIXME: Should we retry to send if the buffer
+	was not totally sent or in case of EINTR? */
+	if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND,
+				req->signal_id, req->data, req->data_size)) {
+		err = -EIO;
+		goto failed;
+	}
+
+
+	session->req = req;
+
+	req->timeout = g_timeout_add_seconds(REQ_TIMEOUT,
+					request_timeout,
+					session);
+	return 0;
+
+failed:
+	g_free(req->data);
+	g_free(req);
+	return err;
+}
+
+static int send_request(struct avdtp *session, gboolean priority,
+			struct avdtp_stream *stream, uint8_t signal_id,
+			void *buffer, size_t size)
+{
+	struct pending_req *req;
+
+	req = g_new0(struct pending_req, 1);
+	req->signal_id = signal_id;
+	req->data = g_malloc(size);
+	memcpy(req->data, buffer, size);
+	req->data_size = size;
+	req->stream = stream;
+
+	return send_req(session, priority, req);
+}
+
+static gboolean avdtp_discover_resp(struct avdtp *session,
+					struct discover_resp *resp, int size)
+{
+	int sep_count, i;
+
+	sep_count = size / sizeof(struct seid_info);
+
+	for (i = 0; i < sep_count; i++) {
+		struct avdtp_remote_sep *sep;
+		struct avdtp_stream *stream;
+		struct seid_req req;
+		int ret;
+
+		debug("seid %d type %d media %d in use %d",
+				resp->seps[i].seid, resp->seps[i].type,
+				resp->seps[i].media_type, resp->seps[i].inuse);
+
+		stream = find_stream_by_rseid(session, resp->seps[i].seid);
+
+		sep = find_remote_sep(session->seps, resp->seps[i].seid);
+		if (!sep) {
+			if (resp->seps[i].inuse && !stream)
+				continue;
+			sep = g_new0(struct avdtp_remote_sep, 1);
+			session->seps = g_slist_append(session->seps, sep);
+		}
+
+		sep->stream = stream;
+		sep->seid = resp->seps[i].seid;
+		sep->type = resp->seps[i].type;
+		sep->media_type = resp->seps[i].media_type;
+
+		memset(&req, 0, sizeof(req));
+		req.acp_seid = sep->seid;
+
+		ret = send_request(session, TRUE, NULL,
+					AVDTP_GET_CAPABILITIES,
+					&req, sizeof(req));
+		if (ret < 0) {
+			finalize_discovery(session, -ret);
+			break;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean avdtp_get_capabilities_resp(struct avdtp *session,
+						struct getcap_resp *resp,
+						unsigned int size)
+{
+	struct avdtp_remote_sep *sep;
+	uint8_t seid;
+
+	/* Check for minimum required packet size includes:
+	 *   1. getcap resp header
+	 *   2. media transport capability (2 bytes)
+	 *   3. media codec capability type + length (2 bytes)
+	 *   4. the actual media codec elements
+	 * */
+	if (size < (sizeof(struct getcap_resp) + 4 +
+				sizeof(struct avdtp_media_codec_capability))) {
+		error("Too short getcap resp packet");
+		return FALSE;
+	}
+
+	seid = ((struct seid_req *) session->req->data)->acp_seid;
+
+	sep = find_remote_sep(session->seps, seid);
+
+	debug("seid %d type %d media %d", sep->seid,
+					sep->type, sep->media_type);
+
+	if (sep->caps) {
+		g_slist_foreach(sep->caps, (GFunc) g_free, NULL);
+		g_slist_free(sep->caps);
+		sep->caps = NULL;
+		sep->codec = NULL;
+	}
+
+	sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp),
+					&sep->codec);
+
+	return TRUE;
+}
+
+static gboolean avdtp_set_configuration_resp(struct avdtp *session,
+						struct avdtp_stream *stream,
+						struct avdtp_single_header *resp,
+						int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (sep->cfm && sep->cfm->set_configuration)
+		sep->cfm->set_configuration(session, sep, stream, NULL,
+						sep->user_data);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+
+	return TRUE;
+}
+
+static gboolean avdtp_reconfigure_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					struct avdtp_single_header *resp, int size)
+{
+	return TRUE;
+}
+
+static gboolean avdtp_open_resp(struct avdtp *session, struct avdtp_stream *stream,
+				struct seid_rej *resp, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (l2cap_connect(session) < 0) {
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+		return FALSE;
+	}
+
+	session->pending_open = stream;
+
+	return TRUE;
+}
+
+static gboolean avdtp_start_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					struct seid_rej *resp, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (sep->cfm && sep->cfm->start)
+		sep->cfm->start(session, sep, stream, NULL, sep->user_data);
+
+	/* We might be in STREAMING already if both sides send START_CMD at the
+	 * same time and the one in SNK role doesn't reject it as it should */
+	if (sep->state != AVDTP_STATE_STREAMING)
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
+
+	return TRUE;
+}
+
+static gboolean avdtp_close_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					struct seid_rej *resp, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING);
+
+	close_stream(stream);
+
+	return TRUE;
+}
+
+static gboolean avdtp_suspend_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					void *data, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+	if (sep->cfm && sep->cfm->suspend)
+		sep->cfm->suspend(session, sep, stream, NULL, sep->user_data);
+
+	return TRUE;
+}
+
+static gboolean avdtp_abort_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					struct seid_rej *resp, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+	if (sep->cfm && sep->cfm->abort)
+		sep->cfm->abort(session, sep, stream, NULL, sep->user_data);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+
+	return TRUE;
+}
+
+static gboolean avdtp_parse_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					uint8_t transaction, uint8_t signal_id,
+					void *buf, int size)
+{
+	struct pending_req *next;
+
+	if (session->prio_queue)
+		next = session->prio_queue->data;
+	else if (session->req_queue)
+		next = session->req_queue->data;
+	else
+		next = NULL;
+
+	switch (signal_id) {
+	case AVDTP_DISCOVER:
+		debug("DISCOVER request succeeded");
+		return avdtp_discover_resp(session, buf, size);
+	case AVDTP_GET_CAPABILITIES:
+		debug("GET_CAPABILITIES request succeeded");
+		if (!avdtp_get_capabilities_resp(session, buf, size))
+			return FALSE;
+		if (!(next && next->signal_id == AVDTP_GET_CAPABILITIES))
+			finalize_discovery(session, 0);
+		return TRUE;
+	}
+
+	/* The remaining commands require an existing stream so bail out
+	 * here if the stream got unexpectedly disconnected */
+	if (!stream) {
+		debug("AVDTP: stream was closed while waiting for reply");
+		return TRUE;
+	}
+
+	switch (signal_id) {
+	case AVDTP_SET_CONFIGURATION:
+		debug("SET_CONFIGURATION request succeeded");
+		return avdtp_set_configuration_resp(session, stream,
+								buf, size);
+	case AVDTP_RECONFIGURE:
+		debug("RECONFIGURE request succeeded");
+		return avdtp_reconfigure_resp(session, stream, buf, size);
+	case AVDTP_OPEN:
+		debug("OPEN request succeeded");
+		return avdtp_open_resp(session, stream, buf, size);
+	case AVDTP_SUSPEND:
+		debug("SUSPEND request succeeded");
+		return avdtp_suspend_resp(session, stream, buf, size);
+	case AVDTP_START:
+		debug("START request succeeded");
+		return avdtp_start_resp(session, stream, buf, size);
+	case AVDTP_CLOSE:
+		debug("CLOSE request succeeded");
+		return avdtp_close_resp(session, stream, buf, size);
+	case AVDTP_ABORT:
+		debug("ABORT request succeeded");
+		return avdtp_abort_resp(session, stream, buf, size);
+	}
+
+	error("Unknown signal id in accept response: %u", signal_id);
+	return TRUE;
+}
+
+static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned int size,
+					struct avdtp_error *err)
+{
+	if (size < sizeof(struct seid_rej)) {
+		error("Too small packet for seid_rej");
+		return FALSE;
+	}
+
+	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+	return TRUE;
+}
+
+static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size,
+				struct avdtp_error *err, uint8_t *category)
+{
+	if (size < sizeof(struct conf_rej)) {
+		error("Too small packet for conf_rej");
+		return FALSE;
+	}
+
+	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+	if (category)
+		*category = rej->category;
+
+	return TRUE;
+}
+
+static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size,
+					struct avdtp_error *err,
+					uint8_t *acp_seid)
+{
+	if (size < sizeof(struct stream_rej)) {
+		error("Too small packet for stream_rej");
+		return FALSE;
+	}
+
+	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+	if (acp_seid)
+		*acp_seid = rej->acp_seid;
+
+	return TRUE;
+}
+
+static gboolean avdtp_parse_rej(struct avdtp *session,
+					struct avdtp_stream *stream,
+					uint8_t transaction, uint8_t signal_id,
+					void *buf, int size)
+{
+	struct avdtp_error err;
+	uint8_t acp_seid, category;
+	struct avdtp_local_sep *sep = stream ? stream->lsep : NULL;
+
+	switch (signal_id) {
+	case AVDTP_DISCOVER:
+		if (!seid_rej_to_err(buf, size, &err))
+			return FALSE;
+		error("DISCOVER request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		return TRUE;
+	case AVDTP_GET_CAPABILITIES:
+		if (!seid_rej_to_err(buf, size, &err))
+			return FALSE;
+		error("GET_CAPABILITIES request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		return TRUE;
+	case AVDTP_OPEN:
+		if (!seid_rej_to_err(buf, size, &err))
+			return FALSE;
+		error("OPEN request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->open)
+			sep->cfm->open(session, sep, stream, &err,
+					sep->user_data);
+		return TRUE;
+	case AVDTP_SET_CONFIGURATION:
+		if (!conf_rej_to_err(buf, size, &err, &category))
+			return FALSE;
+		error("SET_CONFIGURATION request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->set_configuration)
+			sep->cfm->set_configuration(session, sep, stream,
+							&err, sep->user_data);
+		return TRUE;
+	case AVDTP_RECONFIGURE:
+		if (!conf_rej_to_err(buf, size, &err, &category))
+			return FALSE;
+		error("RECONFIGURE request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->reconfigure)
+			sep->cfm->reconfigure(session, sep, stream, &err,
+						sep->user_data);
+		return TRUE;
+	case AVDTP_START:
+		if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+			return FALSE;
+		error("START request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->start)
+			sep->cfm->start(session, sep, stream, &err,
+					sep->user_data);
+		return TRUE;
+	case AVDTP_SUSPEND:
+		if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+			return FALSE;
+		error("SUSPEND request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->suspend)
+			sep->cfm->suspend(session, sep, stream, &err,
+						sep->user_data);
+		return TRUE;
+	case AVDTP_CLOSE:
+		if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+			return FALSE;
+		error("CLOSE request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->close) {
+			sep->cfm->close(session, sep, stream, &err,
+					sep->user_data);
+			stream->close_int = FALSE;
+		}
+		return TRUE;
+	case AVDTP_ABORT:
+		if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+			return FALSE;
+		error("ABORT request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->abort)
+			sep->cfm->abort(session, sep, stream, &err,
+					sep->user_data);
+		return TRUE;
+	default:
+		error("Unknown reject response signal id: %u", signal_id);
+		return TRUE;
+	}
+}
+
+gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	struct avdtp_server *server;
+	struct avdtp *session;
+
+	server = find_server(servers, src);
+	if (!server)
+		return FALSE;
+
+	session = find_session(server->sessions, dst);
+	if (!session)
+		return FALSE;
+
+	if (session->state != AVDTP_SESSION_STATE_DISCONNECTED)
+		return TRUE;
+
+	return FALSE;
+}
+
+struct avdtp_service_capability *avdtp_stream_get_codec(
+						struct avdtp_stream *stream)
+{
+	GSList *l;
+
+	for (l = stream->caps; l; l = l->next) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (cap->category == AVDTP_MEDIA_CODEC)
+			return cap;
+	}
+
+	return NULL;
+}
+
+gboolean avdtp_stream_has_capability(struct avdtp_stream *stream,
+				struct avdtp_service_capability *cap)
+{
+	GSList *l;
+	struct avdtp_service_capability *stream_cap;
+
+	for (l = stream->caps; l; l = g_slist_next(l)) {
+		stream_cap = l->data;
+
+		if (stream_cap->category != cap->category ||
+			stream_cap->length != cap->length)
+			continue;
+
+		if (memcmp(stream_cap->data, cap->data, cap->length) == 0)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
+					GSList *caps)
+{
+	GSList *l;
+
+	for (l = caps; l; l = g_slist_next(l)) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (!avdtp_stream_has_capability(stream, cap))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+					uint16_t *imtu, uint16_t *omtu,
+					GSList **caps)
+{
+	if (stream->io == NULL)
+		return FALSE;
+
+	if (sock)
+		*sock = g_io_channel_unix_get_fd(stream->io);
+
+	if (omtu)
+		*omtu = stream->omtu;
+
+	if (imtu)
+		*imtu = stream->imtu;
+
+	if (caps)
+		*caps = stream->caps;
+
+	return TRUE;
+}
+
+static int process_queue(struct avdtp *session)
+{
+	GSList **queue, *l;
+	struct pending_req *req;
+
+	if (session->req)
+		return 0;
+
+	if (session->prio_queue)
+		queue = &session->prio_queue;
+	else
+		queue = &session->req_queue;
+
+	if (!*queue)
+		return 0;
+
+	l = *queue;
+	req = l->data;
+
+	*queue = g_slist_remove(*queue, req);
+
+	return send_req(session, FALSE, req);
+}
+
+struct avdtp_remote_sep *avdtp_get_remote_sep(struct avdtp *session,
+						uint8_t seid)
+{
+	GSList *l;
+
+	for (l = session->seps; l; l = l->next) {
+		struct avdtp_remote_sep *sep = l->data;
+
+		if (sep->seid == seid)
+			return sep;
+	}
+
+	return NULL;
+}
+
+uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep)
+{
+	return sep->seid;
+}
+
+uint8_t avdtp_get_type(struct avdtp_remote_sep *sep)
+{
+	return sep->type;
+}
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep)
+{
+	return sep->codec;
+}
+
+struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep)
+{
+	return sep->stream;
+}
+
+struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
+							void *data, int length)
+{
+	struct avdtp_service_capability *cap;
+
+	if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_MEDIA_CODEC)
+		return NULL;
+
+	cap = g_malloc(sizeof(struct avdtp_service_capability) + length);
+	cap->category = category;
+	cap->length = length;
+	memcpy(cap->data, data, length);
+
+	return cap;
+}
+
+static gboolean process_discover(gpointer data)
+{
+	struct avdtp *session = data;
+
+	finalize_discovery(session, 0);
+
+	return FALSE;
+}
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+			void *user_data)
+{
+	int ret;
+
+	if (session->discov_cb)
+		return -EBUSY;
+
+	if (session->seps) {
+		session->discov_cb = cb;
+		session->user_data = user_data;
+		g_idle_add(process_discover, session);
+		return 0;
+	}
+
+	ret = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0);
+	if (ret == 0) {
+		session->discov_cb = cb;
+		session->user_data = user_data;
+	}
+
+	return ret;
+}
+
+int avdtp_get_seps(struct avdtp *session, uint8_t acp_type, uint8_t media_type,
+			uint8_t codec, struct avdtp_local_sep **lsep,
+			struct avdtp_remote_sep **rsep)
+{
+	GSList *l;
+	uint8_t int_type;
+
+	int_type = acp_type == AVDTP_SEP_TYPE_SINK ?
+				AVDTP_SEP_TYPE_SOURCE : AVDTP_SEP_TYPE_SINK;
+
+	*lsep = find_local_sep(session->server, int_type, media_type, codec);
+	if (!*lsep)
+		return -EINVAL;
+
+	for (l = session->seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_remote_sep *sep = l->data;
+		struct avdtp_service_capability *cap;
+		struct avdtp_media_codec_capability *codec_data;
+
+		if (sep->type != acp_type)
+			continue;
+
+		if (sep->media_type != media_type)
+			continue;
+
+		if (!sep->codec)
+			continue;
+
+		cap = sep->codec;
+		codec_data = (void *) cap->data;
+
+		if (codec_data->media_codec_type != codec)
+			continue;
+
+		if (!sep->stream) {
+			*rsep = sep;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+				struct avdtp_stream *stream,
+				unsigned int id)
+{
+	GSList *l;
+	struct stream_callback *cb;
+
+	if (!stream)
+		return FALSE;
+
+	for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) {
+		struct stream_callback *tmp = l->data;
+		if (tmp && tmp->id == id) {
+			cb = tmp;
+			break;
+		}
+	}
+
+	if (!cb)
+		return FALSE;
+
+	stream->callbacks = g_slist_remove(stream->callbacks, cb);
+	g_free(cb);
+
+	return TRUE;
+}
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+					struct avdtp_stream *stream,
+					avdtp_stream_state_cb cb, void *data)
+{
+	struct stream_callback *stream_cb;
+	static unsigned int id = 0;
+
+	stream_cb = g_new(struct stream_callback, 1);
+	stream_cb->cb = cb;
+	stream_cb->user_data = data;
+	stream_cb->id = ++id;
+
+	stream->callbacks = g_slist_append(stream->callbacks, stream_cb);;
+
+	return stream_cb->id;
+}
+
+int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+
+	if (session->state < AVDTP_SESSION_STATE_CONNECTED)
+		return -EINVAL;
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION,
+							&req, sizeof(req));
+}
+
+static void copy_capabilities(gpointer data, gpointer user_data)
+{
+	struct avdtp_service_capability *src_cap = data;
+	struct avdtp_service_capability *dst_cap;
+	GSList **l = user_data;
+
+	dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data,
+					src_cap->length);
+
+	*l = g_slist_append(*l, dst_cap);
+}
+
+int avdtp_set_configuration(struct avdtp *session,
+				struct avdtp_remote_sep *rsep,
+				struct avdtp_local_sep *lsep,
+				GSList *caps,
+				struct avdtp_stream **stream)
+{
+	struct setconf_req *req;
+	struct avdtp_stream *new_stream;
+	unsigned char *ptr;
+	int ret, caps_len;
+	struct avdtp_service_capability *cap;
+	GSList *l;
+
+	if (session->state != AVDTP_SESSION_STATE_CONNECTED)
+		return -ENOTCONN;
+
+	if (!(lsep && rsep))
+		return -EINVAL;
+
+	debug("avdtp_set_configuration(%p): int_seid=%u, acp_seid=%u",
+			session, lsep->info.seid, rsep->seid);
+
+	new_stream = g_new0(struct avdtp_stream, 1);
+	new_stream->session = session;
+	new_stream->lsep = lsep;
+	new_stream->rseid = rsep->seid;
+
+	g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
+
+	/* Calculate total size of request */
+	for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
+		cap = l->data;
+		caps_len += cap->length + 2;
+	}
+
+	req = g_malloc0(sizeof(struct setconf_req) + caps_len);
+
+	req->int_seid = lsep->info.seid;
+	req->acp_seid = rsep->seid;
+
+	/* Copy the capabilities into the request */
+	for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
+		cap = l->data;
+		memcpy(ptr, cap, cap->length + 2);
+		ptr += cap->length + 2;
+	}
+
+	ret = send_request(session, FALSE, new_stream,
+				AVDTP_SET_CONFIGURATION, req,
+				sizeof(struct setconf_req) + caps_len);
+	if (ret < 0)
+		stream_free(new_stream);
+	else {
+		lsep->info.inuse = 1;
+		lsep->stream = new_stream;
+		rsep->stream = new_stream;
+		session->streams = g_slist_append(session->streams, new_stream);
+		if (stream)
+			*stream = new_stream;
+	}
+
+	g_free(req);
+
+	return ret;
+}
+
+int avdtp_reconfigure(struct avdtp *session, GSList *caps,
+			struct avdtp_stream *stream)
+{
+	struct reconf_req *req;
+	unsigned char *ptr;
+	int caps_len, err;
+	GSList *l;
+	struct avdtp_service_capability *cap;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state != AVDTP_STATE_OPEN)
+		return -EINVAL;
+
+	/* Calculate total size of request */
+	for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
+		cap = l->data;
+		caps_len += cap->length + 2;
+	}
+
+	req = g_malloc0(sizeof(struct reconf_req) + caps_len);
+
+	req->acp_seid = stream->rseid;
+
+	/* Copy the capabilities into the request */
+	for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
+		cap = l->data;
+		memcpy(ptr, cap, cap->length + 2);
+		ptr += cap->length + 2;
+	}
+
+	err = send_request(session, FALSE, stream, AVDTP_RECONFIGURE, req,
+						sizeof(*req) + caps_len);
+	g_free(req);
+
+	return err;
+}
+
+int avdtp_open(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state > AVDTP_STATE_CONFIGURED)
+		return -EINVAL;
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	return send_request(session, FALSE, stream, AVDTP_OPEN,
+							&req, sizeof(req));
+}
+
+int avdtp_start(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct start_req req;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state != AVDTP_STATE_OPEN)
+		return -EINVAL;
+
+	if (stream->close_int == TRUE) {
+		error("avdtp_start: rejecting start since close is initiated");
+		return -EINVAL;
+	}
+
+	memset(&req, 0, sizeof(req));
+	req.first_seid.seid = stream->rseid;
+
+	return send_request(session, FALSE, stream, AVDTP_START,
+							&req, sizeof(req));
+}
+
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+	int ret;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state < AVDTP_STATE_OPEN)
+		return -EINVAL;
+
+	if (stream->close_int == TRUE) {
+		error("avdtp_close: rejecting since close is already initiated");
+		return -EINVAL;
+	}
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	ret = send_request(session, FALSE, stream, AVDTP_CLOSE,
+							&req, sizeof(req));
+	if (ret == 0)
+		stream->close_int = TRUE;
+
+	return ret;
+}
+
+int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state <= AVDTP_STATE_OPEN || stream->close_int)
+		return -EINVAL;
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	return send_request(session, FALSE, stream, AVDTP_SUSPEND,
+							&req, sizeof(req));
+}
+
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+	int ret;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state <= AVDTP_STATE_OPEN)
+		return -EINVAL;
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	ret = send_request(session, TRUE, stream, AVDTP_ABORT,
+							&req, sizeof(req));
+	if (ret == 0)
+		stream->abort_int = TRUE;
+
+	return ret;
+}
+
+struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
+						uint8_t media_type,
+						uint8_t codec_type,
+						struct avdtp_sep_ind *ind,
+						struct avdtp_sep_cfm *cfm,
+						void *user_data)
+{
+	struct avdtp_server *server;
+	struct avdtp_local_sep *sep;
+
+	server = find_server(servers, src);
+	if (!server)
+		return NULL;
+
+	if (g_slist_length(server->seps) > MAX_SEID)
+		return NULL;
+
+	sep = g_new0(struct avdtp_local_sep, 1);
+
+	sep->state = AVDTP_STATE_IDLE;
+	sep->info.seid = g_slist_length(server->seps) + 1;
+	sep->info.type = type;
+	sep->info.media_type = media_type;
+	sep->codec = codec_type;
+	sep->ind = ind;
+	sep->cfm = cfm;
+	sep->user_data = user_data;
+	sep->server = server;
+
+	debug("SEP %p registered: type:%d codec:%d seid:%d", sep,
+			sep->info.type, sep->codec, sep->info.seid);
+	server->seps = g_slist_append(server->seps, sep);
+
+	return sep;
+}
+
+int avdtp_unregister_sep(struct avdtp_local_sep *sep)
+{
+	struct avdtp_server *server;
+
+	if (!sep)
+		return -EINVAL;
+
+	server = sep->server;
+	server->seps = g_slist_remove(server->seps, sep);
+
+	if (sep->stream)
+		release_stream(sep->stream, sep->stream->session);
+
+	g_free(sep);
+
+	return 0;
+}
+
+static GIOChannel *avdtp_server_socket(const bdaddr_t *src, gboolean master)
+{
+	GError *err = NULL;
+	GIOChannel *io;
+
+	io = bt_io_listen(BT_IO_L2CAP, NULL, avdtp_confirm_cb,
+				NULL, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, src,
+				BT_IO_OPT_PSM, AVDTP_PSM,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+				BT_IO_OPT_MASTER, master,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+	}
+
+	return io;
+}
+
+const char *avdtp_strerror(struct avdtp_error *err)
+{
+	if (err->type == AVDTP_ERROR_ERRNO)
+		return strerror(err->err.posix_errno);
+
+	switch(err->err.error_code) {
+	case AVDTP_BAD_HEADER_FORMAT:
+		return "Bad Header Format";
+	case AVDTP_BAD_LENGTH:
+		return "Bad Packet Lenght";
+	case AVDTP_BAD_ACP_SEID:
+		return "Bad Acceptor SEID";
+	case AVDTP_SEP_IN_USE:
+		return "Stream End Point in Use";
+	case AVDTP_SEP_NOT_IN_USE:
+		return "Stream End Point Not in Use";
+	case AVDTP_BAD_SERV_CATEGORY:
+		return "Bad Service Category";
+	case AVDTP_BAD_PAYLOAD_FORMAT:
+		return "Bad Payload format";
+	case AVDTP_NOT_SUPPORTED_COMMAND:
+		return "Command Not Supported";
+	case AVDTP_INVALID_CAPABILITIES:
+		return "Invalid Capabilities";
+	case AVDTP_BAD_RECOVERY_TYPE:
+		return "Bad Recovery Type";
+	case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT:
+		return "Bad Media Transport Format";
+	case AVDTP_BAD_RECOVERY_FORMAT:
+		return "Bad Recovery Format";
+	case AVDTP_BAD_ROHC_FORMAT:
+		return "Bad Header Compression Format";
+	case AVDTP_BAD_CP_FORMAT:
+		return "Bad Content Protetion Format";
+	case AVDTP_BAD_MULTIPLEXING_FORMAT:
+		return "Bad Multiplexing Format";
+	case AVDTP_UNSUPPORTED_CONFIGURATION:
+		return "Configuration not supported";
+	case AVDTP_BAD_STATE:
+		return "Bad State";
+	default:
+		return "Unknow error";
+	}
+}
+
+avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep)
+{
+	return sep->state;
+}
+
+void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst)
+{
+	if (src)
+		bacpy(src, &session->server->src);
+	if (dst)
+		bacpy(dst, &session->dst);
+}
+
+int avdtp_init(const bdaddr_t *src, GKeyFile *config)
+{
+	GError *err = NULL;
+	gboolean tmp, master = TRUE;
+	struct avdtp_server *server;
+
+	if (config) {
+		tmp = g_key_file_get_boolean(config, "General",
+							"Master", &err);
+		if (err) {
+			debug("audio.conf: %s", err->message);
+			g_clear_error(&err);
+		} else
+			master = tmp;
+
+		tmp = g_key_file_get_boolean(config, "General", "AutoConnect",
+									&err);
+		if (err)
+			g_clear_error(&err);
+		else
+			auto_connect = tmp;
+	}
+
+	server = g_new0(struct avdtp_server, 1);
+	if (!server)
+		return -ENOMEM;
+
+	server->io = avdtp_server_socket(src, master);
+	if (!server->io) {
+		g_free(server);
+		return -1;
+	}
+
+	bacpy(&server->src, src);
+
+	servers = g_slist_append(servers, server);
+
+	return 0;
+}
+
+void avdtp_exit(const bdaddr_t *src)
+{
+	struct avdtp_server *server;
+	GSList *l;
+
+	server = find_server(servers, src);
+	if (!server)
+		return;
+
+	for (l = server->sessions; l; l = l->next) {
+		struct avdtp *session = l->data;
+
+		connection_lost(session, -ECONNABORTED);
+	}
+
+	servers = g_slist_remove(servers, server);
+
+	g_io_channel_shutdown(server->io, TRUE, NULL);
+	g_io_channel_unref(server->io);
+	g_free(server);
+}
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream)
+{
+	return g_slist_find(session->streams, stream) ? TRUE : FALSE;
+}
+
+void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc)
+{
+	session->auto_dc = auto_dc;
+}
+
+gboolean avdtp_stream_setup_active(struct avdtp *session)
+{
+	return session->stream_setup;
+}
+
+unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data)
+{
+	struct avdtp_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct avdtp_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	avdtp_callbacks = g_slist_append(avdtp_callbacks, state_cb);;
+
+	return state_cb->id;
+}
+
+gboolean avdtp_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = avdtp_callbacks; l != NULL; l = l->next) {
+		struct avdtp_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			avdtp_callbacks = g_slist_remove(avdtp_callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff --git a/audio/avdtp.h b/audio/avdtp.h
new file mode 100644
index 0000000..81b8f58
--- /dev/null
+++ b/audio/avdtp.h
@@ -0,0 +1,298 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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_ERROR_ERRNO,
+	AVDTP_ERROR_ERROR_CODE
+} avdtp_error_type_t;
+
+typedef enum {
+	AVDTP_SESSION_STATE_DISCONNECTED,
+	AVDTP_SESSION_STATE_CONNECTING,
+	AVDTP_SESSION_STATE_CONNECTED
+} avdtp_session_state_t;
+
+struct avdtp;
+struct avdtp_stream;
+struct avdtp_local_sep;
+struct avdtp_remote_sep;
+struct avdtp_error {
+	avdtp_error_type_t type;
+	union {
+		uint8_t error_code;
+		int posix_errno;
+	} err;
+};
+
+/* SEP capability categories */
+#define AVDTP_MEDIA_TRANSPORT			0x01
+#define AVDTP_REPORTING				0x02
+#define AVDTP_RECOVERY				0x03
+#define AVDTP_CONTENT_PROTECTION		0x04
+#define AVDTP_HEADER_COMPRESSION		0x05
+#define AVDTP_MULTIPLEXING			0x06
+#define AVDTP_MEDIA_CODEC			0x07
+
+/* AVDTP error definitions */
+#define AVDTP_BAD_HEADER_FORMAT			0x01
+#define AVDTP_BAD_LENGTH			0x11
+#define AVDTP_BAD_ACP_SEID			0x12
+#define AVDTP_SEP_IN_USE			0x13
+#define AVDTP_SEP_NOT_IN_USE			0x14
+#define AVDTP_BAD_SERV_CATEGORY			0x17
+#define AVDTP_BAD_PAYLOAD_FORMAT		0x18
+#define AVDTP_NOT_SUPPORTED_COMMAND		0x19
+#define AVDTP_INVALID_CAPABILITIES		0x1A
+#define AVDTP_BAD_RECOVERY_TYPE			0x22
+#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT	0x23
+#define AVDTP_BAD_RECOVERY_FORMAT		0x25
+#define AVDTP_BAD_ROHC_FORMAT			0x26
+#define AVDTP_BAD_CP_FORMAT			0x27
+#define AVDTP_BAD_MULTIPLEXING_FORMAT		0x28
+#define AVDTP_UNSUPPORTED_CONFIGURATION		0x29
+#define AVDTP_BAD_STATE				0x31
+
+/* SEP types definitions */
+#define AVDTP_SEP_TYPE_SOURCE			0x00
+#define AVDTP_SEP_TYPE_SINK			0x01
+
+/* Media types definitions */
+#define AVDTP_MEDIA_TYPE_AUDIO			0x00
+#define AVDTP_MEDIA_TYPE_VIDEO			0x01
+#define AVDTP_MEDIA_TYPE_MULTIMEDIA		0x02
+
+typedef enum {
+	AVDTP_STATE_IDLE,
+	AVDTP_STATE_CONFIGURED,
+	AVDTP_STATE_OPEN,
+	AVDTP_STATE_STREAMING,
+	AVDTP_STATE_CLOSING,
+	AVDTP_STATE_ABORTING,
+} avdtp_state_t;
+
+struct avdtp_service_capability {
+	uint8_t category;
+	uint8_t length;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_media_codec_capability {
+	uint8_t rfa0:4;
+	uint8_t media_type:4;
+	uint8_t media_codec_type;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_media_codec_capability {
+	uint8_t media_type:4;
+	uint8_t rfa0:4;
+	uint8_t media_codec_type;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+typedef void (*avdtp_session_state_cb) (struct audio_device *dev,
+					struct avdtp *session,
+					avdtp_session_state_t old_state,
+					avdtp_session_state_t new_state,
+					void *user_data);
+
+typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream,
+					avdtp_state_t old_state,
+					avdtp_state_t new_state,
+					struct avdtp_error *err,
+					void *user_data);
+
+/* Callbacks for when a reply is received to a command that we sent */
+struct avdtp_sep_cfm {
+	void (*set_configuration) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err,
+					void *user_data);
+	void (*get_configuration) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err,
+					void *user_data);
+	void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data);
+	void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data);
+	void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data);
+	void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data);
+	void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data);
+	void (*reconfigure) (struct avdtp *session,
+				struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data);
+};
+
+/* Callbacks for indicating when we received a new command. The return value
+ * indicates whether the command should be rejected or accepted */
+struct avdtp_sep_ind {
+	gboolean (*get_capability) (struct avdtp *session,
+					struct avdtp_local_sep *sep,
+					GSList **caps, uint8_t *err,
+					void *user_data);
+	gboolean (*set_configuration) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					struct avdtp_stream *stream,
+					GSList *caps, uint8_t *err,
+					uint8_t *category, void *user_data);
+	gboolean (*get_configuration) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					uint8_t *err, void *user_data);
+	gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*suspend) (struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*abort) (struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*reconfigure) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					uint8_t *err, void *user_data);
+};
+
+typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps,
+					struct avdtp_error *err, void *user_data);
+
+struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst);
+
+void avdtp_unref(struct avdtp *session);
+struct avdtp *avdtp_ref(struct avdtp *session);
+
+gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst);
+
+struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
+							void *data, int size);
+
+struct avdtp_remote_sep *avdtp_get_remote_sep(struct avdtp *session,
+						uint8_t seid);
+
+uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep);
+
+uint8_t avdtp_get_type(struct avdtp_remote_sep *sep);
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep);
+
+struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep);
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+			void *user_data);
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream);
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+					struct avdtp_stream *stream,
+					avdtp_stream_state_cb cb, void *data);
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+				struct avdtp_stream *stream,
+				unsigned int id);
+
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+					uint16_t *imtu, uint16_t *omtu,
+					GSList **caps);
+struct avdtp_service_capability *avdtp_stream_get_codec(
+						struct avdtp_stream *stream);
+gboolean avdtp_stream_has_capability(struct avdtp_stream *stream,
+				struct avdtp_service_capability *cap);
+gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
+					GSList *caps);
+
+unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data);
+
+gboolean avdtp_remove_state_cb(unsigned int id);
+
+int avdtp_set_configuration(struct avdtp *session,
+				struct avdtp_remote_sep *rsep,
+				struct avdtp_local_sep *lsep,
+				GSList *caps,
+				struct avdtp_stream **stream);
+
+int avdtp_get_configuration(struct avdtp *session,
+				struct avdtp_stream *stream);
+
+int avdtp_open(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_reconfigure(struct avdtp *session, GSList *caps,
+			struct avdtp_stream *stream);
+int avdtp_start(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream);
+
+struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
+						uint8_t media_type,
+						uint8_t codec_type,
+						struct avdtp_sep_ind *ind,
+						struct avdtp_sep_cfm *cfm,
+						void *user_data);
+
+/* Find a matching pair of local and remote SEP ID's */
+int avdtp_get_seps(struct avdtp *session, uint8_t type, uint8_t media,
+			uint8_t codec, struct avdtp_local_sep **lsep,
+			struct avdtp_remote_sep **rsep);
+
+int avdtp_unregister_sep(struct avdtp_local_sep *sep);
+
+avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep);
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id);
+const char *avdtp_strerror(struct avdtp_error *err);
+avdtp_error_type_t avdtp_error_type(struct avdtp_error *err);
+int avdtp_error_error_code(struct avdtp_error *err);
+int avdtp_error_posix_errno(struct avdtp_error *err);
+
+void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst);
+
+void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc);
+gboolean avdtp_stream_setup_active(struct avdtp *session);
+
+int avdtp_init(const bdaddr_t *src, GKeyFile *config);
+void avdtp_exit(const bdaddr_t *src);
diff --git a/audio/bluetooth.conf b/audio/bluetooth.conf
new file mode 100644
index 0000000..55b51e4
--- /dev/null
+++ b/audio/bluetooth.conf
@@ -0,0 +1,36 @@
+# Please note that this ALSA configuration file fragment needs be enabled in
+# /etc/asound.conf or a similar configuration file with directives similar to
+# the following:
+#
+#@hooks [
+#	{
+#		func load
+#		files [
+#			"/etc/alsa/bluetooth.conf"
+#		]
+#		errors false
+#	}
+#]
+
+pcm.rawbluetooth {
+	@args [ ADDRESS ]
+	@args.ADDRESS {
+		type string
+	}
+	type bluetooth
+	device $ADDRESS
+}
+
+pcm.bluetooth {
+	@args [ ADDRESS ]
+	@args.ADDRESS {
+		type string
+	}
+	type plug
+	slave {
+		pcm {
+			type bluetooth
+			device $ADDRESS
+		}
+	}
+}
diff --git a/audio/control.c b/audio/control.c
new file mode 100644
index 0000000..e55d1f4
--- /dev/null
+++ b/audio/control.c
@@ -0,0 +1,1160 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 <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 <bluetooth/sdp_lib.h>
+#include <bluetooth/l2cap.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "error.h"
+#include "uinput.h"
+#include "adapter.h"
+#include "../src/device.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "control.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "dbus-common.h"
+
+#define AVCTP_PSM 23
+
+/* Message types */
+#define AVCTP_COMMAND		0
+#define AVCTP_RESPONSE		1
+
+/* Packet types */
+#define AVCTP_PACKET_SINGLE	0
+#define AVCTP_PACKET_START	1
+#define AVCTP_PACKET_CONTINUE	2
+#define AVCTP_PACKET_END	3
+
+/* ctype entries */
+#define CTYPE_CONTROL		0x0
+#define CTYPE_STATUS		0x1
+#define CTYPE_NOT_IMPLEMENTED	0x8
+#define CTYPE_ACCEPTED		0x9
+#define CTYPE_REJECTED		0xA
+#define CTYPE_STABLE		0xC
+
+/* opcodes */
+#define OP_UNITINFO		0x30
+#define OP_SUBUNITINFO		0x31
+#define OP_PASSTHROUGH		0x7c
+
+/* subunits of interest */
+#define SUBUNIT_PANEL		0x09
+
+/* operands in passthrough commands */
+#define VOL_UP_OP		0x41
+#define VOL_DOWN_OP		0x42
+#define MUTE_OP			0x43
+#define PLAY_OP			0x44
+#define STOP_OP			0x45
+#define PAUSE_OP		0x46
+#define RECORD_OP		0x47
+#define REWIND_OP		0x48
+#define FAST_FORWARD_OP		0x49
+#define EJECT_OP		0x4a
+#define FORWARD_OP		0x4b
+#define BACKWARD_OP		0x4c
+
+static DBusConnection *connection = NULL;
+static gchar *input_device_name = NULL;
+static GSList *servers = NULL;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avctp_header {
+	uint8_t ipid:1;
+	uint8_t cr:1;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+	uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avrcp_header {
+	uint8_t code:4;
+	uint8_t _hdr0:4;
+	uint8_t subunit_id:3;
+	uint8_t subunit_type:5;
+	uint8_t opcode;
+} __attribute__ ((packed));
+#define AVRCP_HEADER_LENGTH 3
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avctp_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t cr:1;
+	uint8_t ipid:1;
+	uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avrcp_header {
+	uint8_t _hdr0:4;
+	uint8_t code:4;
+	uint8_t subunit_type:5;
+	uint8_t subunit_id:3;
+	uint8_t opcode;
+} __attribute__ ((packed));
+#define AVRCP_HEADER_LENGTH 3
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp_state_callback {
+	avctp_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct avctp_server {
+	bdaddr_t src;
+	GIOChannel *io;
+	uint32_t tg_record_id;
+#ifndef ANDROID
+	uint32_t ct_record_id;
+#endif
+};
+
+struct control {
+	struct audio_device *dev;
+
+	avctp_state_t state;
+
+	int uinput;
+
+	GIOChannel *io;
+	guint io_id;
+
+	uint16_t mtu;
+
+	gboolean target;
+};
+
+static struct {
+	const char *name;
+	uint8_t avrcp;
+	uint16_t uinput;
+} key_map[] = {
+	{ "PLAY",		PLAY_OP,		KEY_PLAYCD },
+	{ "STOP",		STOP_OP,		KEY_STOPCD },
+	{ "PAUSE",		PAUSE_OP,		KEY_PAUSECD },
+	{ "FORWARD",		FORWARD_OP,		KEY_NEXTSONG },
+	{ "BACKWARD",		BACKWARD_OP,		KEY_PREVIOUSSONG },
+	{ "REWIND",		REWIND_OP,		KEY_REWIND },
+	{ "FAST FORWARD",	FAST_FORWARD_OP,	KEY_FASTFORWARD },
+	{ NULL }
+};
+
+static GSList *avctp_callbacks = NULL;
+
+static sdp_record_t *avrcp_ct_record()
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, l2cap, avctp, avrct;
+	sdp_profile_desc_t profile[1];
+	sdp_list_t *aproto, *proto[2];
+	sdp_record_t *record;
+	sdp_data_t *psm, *version, *features;
+	uint16_t lp = AVCTP_PSM, ver = 0x0100, feat = 0x000f;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	/* Service Class ID List */
+	sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &avrct);
+	sdp_set_service_classes(record, svclass_id);
+
+	/* Protocol Descriptor List */
+	sdp_uuid16_create(&l2cap, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap);
+	psm = sdp_data_alloc(SDP_UINT16, &lp);
+	proto[0] = sdp_list_append(proto[0], psm);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&avctp, AVCTP_UUID);
+	proto[1] = sdp_list_append(0, &avctp);
+	version = sdp_data_alloc(SDP_UINT16, &ver);
+	proto[1] = sdp_list_append(proto[1], version);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	/* Bluetooth Profile Descriptor List */
+	sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+	profile[0].version = ver;
+	pfseq = sdp_list_append(0, &profile[0]);
+	sdp_set_profile_descs(record, pfseq);
+
+	features = sdp_data_alloc(SDP_UINT16, &feat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	sdp_set_info_attr(record, "AVRCP CT", 0, 0);
+
+	free(psm);
+	free(version);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static sdp_record_t *avrcp_tg_record()
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, l2cap, avctp, avrtg;
+	sdp_profile_desc_t profile[1];
+	sdp_list_t *aproto, *proto[2];
+	sdp_record_t *record;
+	sdp_data_t *psm, *version, *features;
+	uint16_t lp = AVCTP_PSM, ver = 0x0100, feat = 0x000f;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	/* Service Class ID List */
+	sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &avrtg);
+	sdp_set_service_classes(record, svclass_id);
+
+	/* Protocol Descriptor List */
+	sdp_uuid16_create(&l2cap, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap);
+	psm = sdp_data_alloc(SDP_UINT16, &lp);
+	proto[0] = sdp_list_append(proto[0], psm);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&avctp, AVCTP_UUID);
+	proto[1] = sdp_list_append(0, &avctp);
+	version = sdp_data_alloc(SDP_UINT16, &ver);
+	proto[1] = sdp_list_append(proto[1], version);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	/* Bluetooth Profile Descriptor List */
+	sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+	profile[0].version = ver;
+	pfseq = sdp_list_append(0, &profile[0]);
+	sdp_set_profile_descs(record, pfseq);
+
+	features = sdp_data_alloc(SDP_UINT16, &feat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	sdp_set_info_attr(record, "AVRCP TG", 0, 0);
+
+	free(psm);
+	free(version);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+	struct uinput_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.type	= type;
+	event.code	= code;
+	event.value	= value;
+
+	return write(fd, &event, sizeof(event));
+}
+
+static void send_key(int fd, uint16_t key, int pressed)
+{
+	if (fd < 0)
+		return;
+
+	send_event(fd, EV_KEY, key, pressed);
+	send_event(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static void handle_panel_passthrough(struct control *control,
+					const unsigned char *operands,
+					int operand_count)
+{
+	const char *status;
+	int pressed, i;
+
+	if (operand_count == 0)
+		return;
+
+	if (operands[0] & 0x80) {
+		status = "released";
+		pressed = 0;
+	} else {
+		status = "pressed";
+		pressed = 1;
+	}
+
+	for (i = 0; key_map[i].name != NULL; i++) {
+		if ((operands[0] & 0x7F) == key_map[i].avrcp) {
+			debug("AVRCP: %s %s", key_map[i].name, status);
+			send_key(control->uinput, key_map[i].uinput, pressed);
+			break;
+		}
+	}
+
+	if (key_map[i].name == NULL)
+		debug("AVRCP: unknown button 0x%02X %s",
+						operands[0] & 0x7F, status);
+}
+
+static void avctp_disconnected(struct audio_device *dev)
+{
+	struct control *control = dev->control;
+
+	if (!control)
+		return;
+
+	if (control->io) {
+		g_io_channel_shutdown(control->io, TRUE, NULL);
+		g_io_channel_unref(control->io);
+		control->io = NULL;
+	}
+
+	if (control->io_id) {
+		g_source_remove(control->io_id);
+		control->io_id = 0;
+	}
+
+	if (control->uinput >= 0) {
+		ioctl(control->uinput, UI_DEV_DESTROY);
+		close(control->uinput);
+		control->uinput = -1;
+	}
+}
+
+static void avctp_set_state(struct control *control, avctp_state_t new_state)
+{
+	GSList *l;
+	struct audio_device *dev = control->dev;
+	avdtp_session_state_t old_state = control->state;
+	gboolean value;
+
+	switch (new_state) {
+	case AVCTP_STATE_DISCONNECTED:
+		avctp_disconnected(control->dev);
+
+		if (old_state != AVCTP_STATE_CONNECTED)
+			break;
+
+		value = FALSE;
+		g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_CONTROL_INTERFACE,
+					"Disconnected", DBUS_TYPE_INVALID);
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_CONTROL_INTERFACE, "Connected",
+					DBUS_TYPE_BOOLEAN, &value);
+		break;
+	case AVCTP_STATE_CONNECTING:
+		break;
+	case AVCTP_STATE_CONNECTED:
+		value = TRUE;
+		g_dbus_emit_signal(control->dev->conn, control->dev->path,
+				AUDIO_CONTROL_INTERFACE, "Connected",
+				DBUS_TYPE_INVALID);
+		emit_property_changed(control->dev->conn, control->dev->path,
+				AUDIO_CONTROL_INTERFACE, "Connected",
+				DBUS_TYPE_BOOLEAN, &value);
+		break;
+	default:
+		error("Invalid AVCTP state %d", new_state);
+		return;
+	}
+
+	control->state = new_state;
+
+	for (l = avctp_callbacks; l != NULL; l = l->next) {
+		struct avctp_state_callback *cb = l->data;
+		cb->cb(control->dev, old_state, new_state, cb->user_data);
+	}
+}
+
+static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct control *control = data;
+	unsigned char buf[1024], *operands;
+	struct avctp_header *avctp;
+	struct avrcp_header *avrcp;
+	int ret, packet_size, operand_count, sock;
+
+	if (!(cond | G_IO_IN))
+		goto failed;
+
+	sock = g_io_channel_unix_get_fd(control->io);
+
+	ret = read(sock, buf, sizeof(buf));
+	if (ret <= 0)
+		goto failed;
+
+	debug("Got %d bytes of data for AVCTP session %p", ret, control);
+
+	if ((unsigned int) ret < sizeof(struct avctp_header)) {
+		error("Too small AVCTP packet");
+		goto failed;
+	}
+
+	packet_size = ret;
+
+	avctp = (struct avctp_header *) buf;
+
+	debug("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
+			"PID 0x%04X",
+			avctp->transaction, avctp->packet_type,
+			avctp->cr, avctp->ipid, ntohs(avctp->pid));
+
+	ret -= sizeof(struct avctp_header);
+	if ((unsigned int) ret < sizeof(struct avrcp_header)) {
+		error("Too small AVRCP packet");
+		goto failed;
+	}
+
+	avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header));
+
+	ret -= sizeof(struct avrcp_header);
+
+	operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header);
+	operand_count = ret;
+
+	debug("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
+			"opcode 0x%02X, %d operands",
+			avctp->cr ? "response" : "command",
+			avrcp->code, avrcp->subunit_type, avrcp->subunit_id,
+			avrcp->opcode, operand_count);
+
+	if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_NOT_IMPLEMENTED;
+	} else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+		avctp->ipid = 1;
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_REJECTED;
+	} else if (avctp->cr == AVCTP_COMMAND &&
+			avrcp->code == CTYPE_CONTROL &&
+			avrcp->subunit_type == SUBUNIT_PANEL &&
+			avrcp->opcode == OP_PASSTHROUGH) {
+		handle_panel_passthrough(control, operands, operand_count);
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_ACCEPTED;
+	} else if (avctp->cr == AVCTP_COMMAND &&
+			avrcp->code == CTYPE_STATUS &&
+			(avrcp->opcode == OP_UNITINFO
+			|| avrcp->opcode == OP_SUBUNITINFO)) {
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_STABLE;
+		/* The first operand should be 0x07 for the UNITINFO response.
+		 * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+		 * Interface Command Set (section 9.2.1, page 45) specs
+		 * explain this value but both use it */
+		if (operand_count >= 1 && avrcp->opcode == OP_UNITINFO)
+			operands[0] = 0x07;
+		if (operand_count >= 2)
+			operands[1] = SUBUNIT_PANEL << 3;
+		debug("reply to %s", avrcp->opcode == OP_UNITINFO ?
+				"OP_UNITINFO" : "OP_SUBUNITINFO");
+	} else {
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_REJECTED;
+	}
+	ret = write(sock, buf, packet_size);
+
+	return TRUE;
+
+failed:
+	debug("AVCTP session %p got disconnected", control);
+	avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+	return FALSE;
+}
+
+static int uinput_create(char *name)
+{
+	struct uinput_dev dev;
+	int fd, err, i;
+
+	fd = open("/dev/uinput", O_RDWR);
+	if (fd < 0) {
+		fd = open("/dev/input/uinput", O_RDWR);
+		if (fd < 0) {
+			fd = open("/dev/misc/uinput", O_RDWR);
+			if (fd < 0) {
+				err = errno;
+				error("Can't open input device: %s (%d)",
+							strerror(err), err);
+				return -err;
+			}
+		}
+	}
+
+	memset(&dev, 0, sizeof(dev));
+	if (name)
+		strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1);
+
+	dev.id.bustype = BUS_BLUETOOTH;
+	dev.id.vendor  = 0x0000;
+	dev.id.product = 0x0000;
+	dev.id.version = 0x0000;
+
+	if (write(fd, &dev, sizeof(dev)) < 0) {
+		err = errno;
+		error("Can't write device information: %s (%d)",
+						strerror(err), err);
+		close(fd);
+		errno = err;
+		return -err;
+	}
+
+	ioctl(fd, UI_SET_EVBIT, EV_KEY);
+	ioctl(fd, UI_SET_EVBIT, EV_REL);
+	ioctl(fd, UI_SET_EVBIT, EV_REP);
+	ioctl(fd, UI_SET_EVBIT, EV_SYN);
+
+	for (i = 0; key_map[i].name != NULL; i++)
+		ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
+
+	if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
+		err = errno;
+		error("Can't create uinput device: %s (%d)",
+						strerror(err), err);
+		close(fd);
+		errno = err;
+		return -err;
+	}
+
+	return fd;
+}
+
+static void init_uinput(struct control *control)
+{
+	char address[18], *name;
+
+	ba2str(&control->dev->dst, address);
+
+	/* Use device name from config file if specified */
+	name = input_device_name;
+	if (!name)
+		name = address;
+
+	control->uinput = uinput_create(name);
+	if (control->uinput < 0)
+		error("AVRCP: failed to init uinput for %s", address);
+	else
+		debug("AVRCP: uinput initialized for %s", address);
+}
+
+static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+	struct control *control = data;
+	char address[18];
+	uint16_t imtu;
+	GError *gerr = NULL;
+
+	if (err) {
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+		error("%s", err->message);
+		return;
+	}
+
+	bt_io_get(chan, BT_IO_L2CAP, &gerr,
+			BT_IO_OPT_DEST, &address,
+			BT_IO_OPT_IMTU, &imtu,
+			BT_IO_OPT_INVALID);
+	if (gerr) {
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+		error("%s", gerr->message);
+		g_error_free(gerr);
+		return;
+	}
+
+	debug("AVCTP: connected to %s", address);
+
+	if (!control->io)
+		control->io = g_io_channel_ref(chan);
+
+	init_uinput(control);
+
+	avctp_set_state(control, AVCTP_STATE_CONNECTED);
+	control->mtu = imtu;
+	control->io_id = g_io_add_watch(chan,
+				G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+				(GIOFunc) control_cb, control);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+	struct control *control = user_data;
+	GError *err = NULL;
+
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+		return;
+	}
+
+	if (!bt_io_accept(control->io, avctp_connect_cb, control,
+								NULL, &err)) {
+		error("bt_io_accept: %s", err->message);
+		g_error_free(err);
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+	}
+}
+
+static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+	struct control *control = NULL;
+	struct audio_device *dev;
+	char address[18];
+	bdaddr_t src, dst;
+	GError *err = NULL;
+
+	bt_io_get(chan, BT_IO_L2CAP, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_DEST, address,
+			BT_IO_OPT_INVALID);
+	if (err) {
+		error("%s", err->message);
+		g_error_free(err);
+		g_io_channel_shutdown(chan, TRUE, NULL);
+		return;
+	}
+
+	dev = manager_get_device(&src, &dst, TRUE);
+	if (!dev) {
+		error("Unable to get audio device object for %s", address);
+		goto drop;
+	}
+
+	if (!dev->control) {
+		btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
+		if (!dev->control)
+			goto drop;
+	}
+
+	control = dev->control;
+
+	if (control->io) {
+		error("Refusing unexpected connect from %s", address);
+		goto drop;
+	}
+
+	avctp_set_state(control, AVCTP_STATE_CONNECTING);
+	control->io = g_io_channel_ref(chan);
+
+	if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
+						auth_cb, dev->control) < 0)
+		goto drop;
+
+	return;
+
+drop:
+	if (!control || !control->io)
+		g_io_channel_shutdown(chan, TRUE, NULL);
+	if (control)
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+}
+
+static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master)
+{
+	GError *err = NULL;
+	GIOChannel *io;
+
+	io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL,
+				NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, src,
+				BT_IO_OPT_PSM, AVCTP_PSM,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+				BT_IO_OPT_MASTER, master,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+	}
+
+	return io;
+}
+
+gboolean avrcp_connect(struct audio_device *dev)
+{
+	struct control *control = dev->control;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	if (control->state > AVCTP_STATE_DISCONNECTED)
+		return TRUE;
+
+	avctp_set_state(control, AVCTP_STATE_CONNECTING);
+
+	io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+				BT_IO_OPT_DEST_BDADDR, &dev->dst,
+				BT_IO_OPT_PSM, AVCTP_PSM,
+				BT_IO_OPT_INVALID);
+	if (err) {
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+		error("%s", err->message);
+		g_error_free(err);
+		return FALSE;
+	}
+
+	control->io = io;
+
+	return TRUE;
+}
+
+void avrcp_disconnect(struct audio_device *dev)
+{
+	struct control *control = dev->control;
+
+	if (!(control && control->io))
+		return;
+
+	avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+}
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
+{
+	sdp_record_t *record;
+	gboolean tmp, master = TRUE;
+	GError *err = NULL;
+	struct avctp_server *server;
+
+	if (config) {
+		tmp = g_key_file_get_boolean(config, "General",
+							"Master", &err);
+		if (err) {
+			debug("audio.conf: %s", err->message);
+			g_error_free(err);
+		} else
+			master = tmp;
+		err = NULL;
+		input_device_name = g_key_file_get_string(config,
+			"AVRCP", "InputDeviceName", &err);
+		if (err) {
+			debug("audio.conf: %s", err->message);
+			input_device_name = NULL;
+			g_error_free(err);
+		}
+	}
+
+	server = g_new0(struct avctp_server, 1);
+	if (!server)
+		return -ENOMEM;
+
+	if (!connection)
+		connection = dbus_connection_ref(conn);
+
+	record = avrcp_tg_record();
+	if (!record) {
+		error("Unable to allocate new service record");
+		return -1;
+	}
+
+	if (add_record_to_server(src, record) < 0) {
+		error("Unable to register AVRCP target service record");
+		sdp_record_free(record);
+		return -1;
+	}
+	server->tg_record_id = record->handle;
+
+#ifndef ANDROID
+	record = avrcp_ct_record();
+	if (!record) {
+		error("Unable to allocate new service record");
+		return -1;
+	}
+
+	if (add_record_to_server(src, record) < 0) {
+		error("Unable to register AVRCP controller service record");
+		sdp_record_free(record);
+		return -1;
+	}
+	server->ct_record_id = record->handle;
+#endif
+
+	server->io = avctp_server_socket(src, master);
+	if (!server->io) {
+#ifndef ANDROID
+		remove_record_from_server(server->ct_record_id);
+#endif
+		remove_record_from_server(server->tg_record_id);
+		g_free(server);
+		return -1;
+	}
+
+	bacpy(&server->src, src);
+
+	servers = g_slist_append(servers, server);
+
+	return 0;
+}
+
+static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		struct avctp_server *server = l->data;
+
+		if (bacmp(&server->src, src) == 0)
+			return server;
+	}
+
+	return NULL;
+}
+
+void avrcp_unregister(const bdaddr_t *src)
+{
+	struct avctp_server *server;
+
+	server = find_server(servers, src);
+	if (!server)
+		return;
+
+	servers = g_slist_remove(servers, server);
+
+#ifndef ANDROID
+	remove_record_from_server(server->ct_record_id);
+#endif
+	remove_record_from_server(server->tg_record_id);
+
+	g_io_channel_shutdown(server->io, TRUE, NULL);
+	g_io_channel_unref(server->io);
+	g_free(server);
+
+	if (servers)
+		return;
+
+	dbus_connection_unref(connection);
+	connection = NULL;
+}
+
+static DBusMessage *control_is_connected(DBusConnection *conn,
+						DBusMessage *msg,
+						void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	DBusMessage *reply;
+	dbus_bool_t connected;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	connected = (control->state == AVCTP_STATE_CONNECTED);
+
+	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+					DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static int avctp_send_passthrough(struct control *control, uint8_t op)
+{
+	unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2];
+	struct avctp_header *avctp = (void *) buf;
+	struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
+	uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH];
+	int err, sk = g_io_channel_unix_get_fd(control->io);
+	static uint8_t transaction = 0;
+
+	memset(buf, 0, sizeof(buf));
+
+	avctp->transaction = transaction++;
+	avctp->packet_type = AVCTP_PACKET_SINGLE;
+	avctp->cr = AVCTP_COMMAND;
+	avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+	avrcp->code = CTYPE_CONTROL;
+	avrcp->subunit_type = SUBUNIT_PANEL;
+	avrcp->opcode = OP_PASSTHROUGH;
+
+	operands[0] = op & 0x7f;
+	operands[1] = 0;
+
+	err = write(sk, buf, sizeof(buf));
+	if (err < 0)
+		return err;
+
+	/* Button release */
+	avctp->transaction = transaction++;
+	operands[0] |= 0x80;
+
+	return write(sk, buf, sizeof(buf));
+}
+
+static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	DBusMessage *reply;
+	int err;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (control->state != AVCTP_STATE_CONNECTED)
+		return g_dbus_create_error(msg,
+					ERROR_INTERFACE ".NotConnected",
+					"Device not Connected");
+
+	if (!control->target)
+		return g_dbus_create_error(msg,
+					ERROR_INTERFACE ".NotSupported",
+					"AVRCP Target role not supported");
+
+	err = avctp_send_passthrough(control, VOL_UP_OP);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+							strerror(-err));
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	DBusMessage *reply;
+	int err;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (control->state != AVCTP_STATE_CONNECTED)
+		return g_dbus_create_error(msg,
+					ERROR_INTERFACE ".NotConnected",
+					"Device not Connected");
+
+	if (!control->target)
+		return g_dbus_create_error(msg,
+					ERROR_INTERFACE ".NotSupported",
+					"AVRCP Target role not supported");
+
+	err = avctp_send_passthrough(control, VOL_DOWN_OP);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+							strerror(-err));
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *control_get_properties(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	gboolean value;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* Connected */
+	value = (device->control->state == AVCTP_STATE_CONNECTED);
+	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static GDBusMethodTable control_methods[] = {
+	{ "IsConnected",	"",	"b",	control_is_connected,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "GetProperties",	"",	"a{sv}",control_get_properties },
+	{ "VolumeUp",		"",	"",	volume_up },
+	{ "VolumeDown",		"",	"",	volume_down },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable control_signals[] = {
+	{ "Connected",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED},
+	{ "Disconnected",		"",	G_DBUS_SIGNAL_FLAG_DEPRECATED},
+	{ "PropertyChanged",		"sv"	},
+	{ NULL, NULL }
+};
+
+static void path_unregister(void *data)
+{
+	struct audio_device *dev = data;
+	struct control *control = dev->control;
+
+	debug("Unregistered interface %s on path %s",
+		AUDIO_CONTROL_INTERFACE, dev->path);
+
+	if (control->state != AVCTP_STATE_DISCONNECTED)
+		avctp_disconnected(dev);
+
+	g_free(control);
+	dev->control = NULL;
+}
+
+void control_unregister(struct audio_device *dev)
+{
+	g_dbus_unregister_interface(dev->conn, dev->path,
+		AUDIO_CONTROL_INTERFACE);
+}
+
+void control_update(struct audio_device *dev, uint16_t uuid16)
+{
+	struct control *control = dev->control;
+
+	if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
+		control->target = TRUE;
+}
+
+struct control *control_init(struct audio_device *dev, uint16_t uuid16)
+{
+	struct control *control;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_CONTROL_INTERFACE,
+					control_methods, control_signals, NULL,
+					dev, path_unregister))
+		return NULL;
+
+	debug("Registered interface %s on path %s",
+		AUDIO_CONTROL_INTERFACE, dev->path);
+
+	control = g_new0(struct control, 1);
+	control->dev = dev;
+	control->state = AVCTP_STATE_DISCONNECTED;
+	control->uinput = -1;
+
+	if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
+		control->target = TRUE;
+
+	return control;
+}
+
+gboolean control_is_active(struct audio_device *dev)
+{
+	struct control *control = dev->control;
+
+	if (control && control->state != AVCTP_STATE_DISCONNECTED)
+		return TRUE;
+
+	return FALSE;
+}
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
+{
+	struct avctp_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct avctp_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	avctp_callbacks = g_slist_append(avctp_callbacks, state_cb);
+
+	return state_cb->id;
+}
+
+gboolean avctp_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = avctp_callbacks; l != NULL; l = l->next) {
+		struct avctp_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			avctp_callbacks = g_slist_remove(avctp_callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff --git a/audio/control.h b/audio/control.h
new file mode 100644
index 0000000..ad0aca5
--- /dev/null
+++ b/audio/control.h
@@ -0,0 +1,50 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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"
+
+typedef enum {
+	AVCTP_STATE_DISCONNECTED = 0,
+	AVCTP_STATE_CONNECTING,
+	AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
+typedef void (*avctp_state_cb) (struct audio_device *dev,
+				avctp_state_t old_state,
+				avctp_state_t new_state,
+				void *user_data);
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
+gboolean avctp_remove_state_cb(unsigned int id);
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
+void avrcp_unregister(const bdaddr_t *src);
+
+gboolean avrcp_connect(struct audio_device *dev);
+void avrcp_disconnect(struct audio_device *dev);
+
+struct control *control_init(struct audio_device *dev, uint16_t uuid16);
+void control_update(struct audio_device *dev, uint16_t uuid16);
+void control_unregister(struct audio_device *dev);
+gboolean control_is_active(struct audio_device *dev);
diff --git a/audio/ctl_bluetooth.c b/audio/ctl_bluetooth.c
new file mode 100644
index 0000000..2e12f65
--- /dev/null
+++ b/audio/ctl_bluetooth.c
@@ -0,0 +1,384 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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)
+		goto done;
+
+	*value = rsp->key;
+done:
+	return ret;
+}
+
+static int bluetooth_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+								long *value)
+{
+	struct bluetooth_data *data = ext->private_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_control_rsp *rsp = (void *) buf;
+	long current;
+	int ret, keyvalue;
+
+	DBG("ext %p key %ld", ext, key);
+
+	ret = bluetooth_read_integer(ext, key, &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
index 0000000..9662dec
--- /dev/null
+++ b/audio/device.c
@@ -0,0 +1,799 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "error.h"
+#include "ipc.h"
+#include "dbus-common.h"
+#include "device.h"
+#include "unix.h"
+#include "avdtp.h"
+#include "control.h"
+#include "headset.h"
+#include "gateway.h"
+#include "sink.h"
+#include "source.h"
+
+#define AUDIO_INTERFACE "org.bluez.Audio"
+
+#define CONTROL_CONNECT_TIMEOUT 2
+#define AVDTP_CONNECT_TIMEOUT 1
+#define HEADSET_CONNECT_TIMEOUT 1
+
+typedef enum {
+	AUDIO_STATE_DISCONNECTED,
+	AUDIO_STATE_CONNECTING,
+	AUDIO_STATE_CONNECTED,
+} audio_state_t;
+
+struct service_auth {
+	service_auth_cb cb;
+	void *user_data;
+};
+
+struct dev_priv {
+	audio_state_t state;
+
+	headset_state_t hs_state;
+	sink_state_t sink_state;
+	avctp_state_t avctp_state;
+	GSList *auths;
+
+	DBusMessage *conn_req;
+	DBusMessage *dc_req;
+
+	guint control_timer;
+	guint avdtp_timer;
+	guint headset_timer;
+
+	gboolean authorized;
+	guint auth_idle_id;
+};
+
+static unsigned int sink_callback_id = 0;
+static unsigned int avctp_callback_id = 0;
+static unsigned int avdtp_callback_id = 0;
+static unsigned int headset_callback_id = 0;
+
+static void device_free(struct audio_device *dev)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (dev->conn)
+		dbus_connection_unref(dev->conn);
+
+	btd_device_unref(dev->btd_dev);
+
+	if (priv) {
+		if (priv->control_timer)
+			g_source_remove(priv->control_timer);
+		if (priv->avdtp_timer)
+			g_source_remove(priv->avdtp_timer);
+		if (priv->headset_timer)
+			g_source_remove(priv->headset_timer);
+		if (priv->dc_req)
+			dbus_message_unref(priv->dc_req);
+		if (priv->conn_req)
+			dbus_message_unref(priv->conn_req);
+		g_free(priv);
+	}
+
+	g_free(dev->path);
+	g_free(dev);
+}
+
+static const char *state2str(audio_state_t state)
+{
+	switch (state) {
+	case AUDIO_STATE_DISCONNECTED:
+		return "disconnected";
+	case AUDIO_STATE_CONNECTING:
+		return "connecting";
+	case AUDIO_STATE_CONNECTED:
+		return "connected";
+	default:
+		error("Invalid audio state %d", state);
+		return NULL;
+	}
+}
+
+static void device_set_state(struct audio_device *dev, audio_state_t new_state)
+{
+	struct dev_priv *priv = dev->priv;
+	const char *state_str;
+	DBusMessage *reply = NULL;
+
+	state_str = state2str(new_state);
+	if (!state_str)
+		return;
+
+	if (new_state == AUDIO_STATE_DISCONNECTED)
+		priv->authorized = FALSE;
+
+	if (dev->priv->state == new_state) {
+		debug("state change attempted from %s to %s",
+							state_str, state_str);
+		return;
+	}
+
+	dev->priv->state = new_state;
+
+	if (priv->dc_req && new_state == AUDIO_STATE_DISCONNECTED) {
+		reply = dbus_message_new_method_return(priv->dc_req);
+		dbus_message_unref(priv->dc_req);
+		priv->dc_req = NULL;
+		g_dbus_send_message(dev->conn, reply);
+	}
+
+	if (priv->conn_req && new_state != AUDIO_STATE_CONNECTING) {
+		if (new_state == AUDIO_STATE_CONNECTED)
+			reply = dbus_message_new_method_return(priv->conn_req);
+		else
+			reply = g_dbus_create_error(priv->conn_req,
+							ERROR_INTERFACE
+							".ConnectFailed",
+							"Connecting failed");
+		dbus_message_unref(priv->conn_req);
+		priv->conn_req = NULL;
+		g_dbus_send_message(dev->conn, reply);
+	}
+
+	emit_property_changed(dev->conn, dev->path,
+				AUDIO_INTERFACE, "State",
+				DBUS_TYPE_STRING, &state_str);
+}
+
+static gboolean control_connect_timeout(gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+
+	dev->priv->control_timer = 0;
+
+	if (dev->control)
+		avrcp_connect(dev);
+
+	return FALSE;
+}
+
+static gboolean device_set_control_timer(struct audio_device *dev)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->control)
+		return FALSE;
+
+	if (priv->control_timer)
+		return FALSE;
+
+	priv->control_timer = g_timeout_add_seconds(CONTROL_CONNECT_TIMEOUT,
+							control_connect_timeout,
+							dev);
+
+	return TRUE;
+}
+
+static void device_remove_control_timer(struct audio_device *dev)
+{
+	if (dev->priv->control_timer)
+		g_source_remove(dev->priv->control_timer);
+	dev->priv->control_timer = 0;
+}
+
+static gboolean avdtp_connect_timeout(gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+
+	dev->priv->avdtp_timer = 0;
+
+	if (dev->sink) {
+		struct avdtp *session = avdtp_get(&dev->src, &dev->dst);
+
+		if (!session)
+			return FALSE;
+
+		sink_setup_stream(dev->sink, session);
+		avdtp_unref(session);
+	}
+
+	return FALSE;
+}
+
+static gboolean device_set_avdtp_timer(struct audio_device *dev)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->sink)
+		return FALSE;
+
+	if (priv->avdtp_timer)
+		return FALSE;
+
+	priv->avdtp_timer = g_timeout_add_seconds(AVDTP_CONNECT_TIMEOUT,
+							avdtp_connect_timeout,
+							dev);
+
+	return TRUE;
+}
+
+static void device_remove_avdtp_timer(struct audio_device *dev)
+{
+	if (dev->priv->avdtp_timer)
+		g_source_remove(dev->priv->avdtp_timer);
+	dev->priv->avdtp_timer = 0;
+}
+
+static gboolean headset_connect_timeout(gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct dev_priv *priv = dev->priv;
+
+	dev->priv->headset_timer = 0;
+
+	if (dev->headset == NULL)
+		return FALSE;
+
+	if (headset_config_stream(dev, FALSE, NULL, NULL) == 0) {
+		if (priv->state != AUDIO_STATE_CONNECTED &&
+				(priv->sink_state == SINK_STATE_CONNECTED ||
+				priv->sink_state == SINK_STATE_PLAYING))
+			device_set_state(dev, AUDIO_STATE_CONNECTED);
+	}
+
+	return FALSE;
+}
+
+static gboolean device_set_headset_timer(struct audio_device *dev)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->headset)
+		return FALSE;
+
+	if (priv->headset_timer)
+		return FALSE;
+
+	priv->headset_timer = g_timeout_add_seconds(HEADSET_CONNECT_TIMEOUT,
+						headset_connect_timeout, dev);
+
+	return TRUE;
+}
+
+static void device_remove_headset_timer(struct audio_device *dev)
+{
+	if (dev->priv->headset_timer)
+		g_source_remove(dev->priv->headset_timer);
+	dev->priv->headset_timer = 0;
+}
+
+static void device_avdtp_cb(struct audio_device *dev, struct avdtp *session,
+				avdtp_session_state_t old_state,
+				avdtp_session_state_t new_state,
+				void *user_data)
+{
+	if (!dev->sink || !dev->control)
+		return;
+
+	if (new_state == AVDTP_SESSION_STATE_CONNECTED) {
+		if (avdtp_stream_setup_active(session))
+			device_set_control_timer(dev);
+		else
+			avrcp_connect(dev);
+	}
+}
+
+static void device_sink_cb(struct audio_device *dev,
+				sink_state_t old_state,
+				sink_state_t new_state,
+				void *user_data)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->sink)
+		return;
+
+	priv->sink_state = new_state;
+
+	switch (new_state) {
+	case SINK_STATE_DISCONNECTED:
+		if (dev->control) {
+			device_remove_control_timer(dev);
+			avrcp_disconnect(dev);
+		}
+		if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+			device_set_state(dev, AUDIO_STATE_DISCONNECTED);
+		else if (old_state == SINK_STATE_CONNECTING) {
+			switch (priv->hs_state) {
+			case HEADSET_STATE_CONNECTED:
+			case HEADSET_STATE_PLAY_IN_PROGRESS:
+			case HEADSET_STATE_PLAYING:
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+			default:
+				break;
+			}
+		}
+		break;
+	case SINK_STATE_CONNECTING:
+		device_remove_avdtp_timer(dev);
+		if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+			device_set_state(dev, AUDIO_STATE_CONNECTING);
+		break;
+	case SINK_STATE_CONNECTED:
+		if (old_state == SINK_STATE_PLAYING)
+			break;
+#ifdef ANDROID
+		android_set_high_priority(&dev->dst);
+#endif
+		if (dev->auto_connect) {
+			if (!dev->headset)
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+			if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+				device_set_headset_timer(dev);
+			else if (priv->hs_state == HEADSET_STATE_CONNECTED)
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+		} else if (priv->hs_state != HEADSET_STATE_CONNECTED)
+			device_set_state(dev, AUDIO_STATE_CONNECTED);
+		break;
+	case SINK_STATE_PLAYING:
+		break;
+	}
+}
+
+static void device_avctp_cb(struct audio_device *dev,
+				avctp_state_t old_state,
+				avctp_state_t new_state,
+				void *user_data)
+{
+	if (!dev->control)
+		return;
+
+	dev->priv->avctp_state = new_state;
+
+	switch (new_state) {
+	case AVCTP_STATE_DISCONNECTED:
+		break;
+	case AVCTP_STATE_CONNECTING:
+		device_remove_control_timer(dev);
+		break;
+	case AVCTP_STATE_CONNECTED:
+		break;
+	}
+}
+
+static void device_headset_cb(struct audio_device *dev,
+				headset_state_t old_state,
+				headset_state_t new_state,
+				void *user_data)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->headset)
+		return;
+
+	priv->hs_state = new_state;
+
+	switch (new_state) {
+	case HEADSET_STATE_DISCONNECTED:
+		device_remove_avdtp_timer(dev);
+		if (priv->sink_state != SINK_STATE_DISCONNECTED &&
+						dev->sink && priv->dc_req) {
+			sink_shutdown(dev->sink);
+			break;
+		}
+		if (priv->sink_state == SINK_STATE_DISCONNECTED)
+			device_set_state(dev, AUDIO_STATE_DISCONNECTED);
+		else if (old_state == HEADSET_STATE_CONNECT_IN_PROGRESS &&
+				(priv->sink_state == SINK_STATE_CONNECTED ||
+				priv->sink_state == SINK_STATE_PLAYING))
+			device_set_state(dev, AUDIO_STATE_CONNECTED);
+		break;
+	case HEADSET_STATE_CONNECT_IN_PROGRESS:
+		device_remove_headset_timer(dev);
+		if (priv->sink_state == SINK_STATE_DISCONNECTED)
+			device_set_state(dev, AUDIO_STATE_CONNECTING);
+		break;
+	case HEADSET_STATE_CONNECTED:
+		if (old_state == HEADSET_STATE_CONNECTED ||
+				old_state == HEADSET_STATE_PLAY_IN_PROGRESS ||
+				old_state == HEADSET_STATE_PLAYING)
+			break;
+		if (dev->auto_connect) {
+			if (!dev->sink)
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+			else if (priv->sink_state == SINK_STATE_DISCONNECTED)
+				device_set_avdtp_timer(dev);
+			else if (priv->sink_state == SINK_STATE_CONNECTED)
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+		} else if (priv->sink_state != SINK_STATE_CONNECTED)
+			device_set_state(dev, AUDIO_STATE_CONNECTED);
+		break;
+	case HEADSET_STATE_PLAY_IN_PROGRESS:
+		break;
+	case HEADSET_STATE_PLAYING:
+		break;
+	}
+}
+
+static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *dev = data;
+	struct dev_priv *priv = dev->priv;
+
+	if (priv->state == AUDIO_STATE_CONNECTING)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+						"Connect in Progress");
+	else if (priv->state == AUDIO_STATE_CONNECTED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".AlreadyConnected",
+						"Already Connected");
+
+	dev->auto_connect = TRUE;
+
+	if (dev->headset)
+		headset_config_stream(dev, FALSE, NULL, NULL);
+
+	if (priv->state != AUDIO_STATE_CONNECTING && dev->sink) {
+		struct avdtp *session = avdtp_get(&dev->src, &dev->dst);
+
+		if (!session)
+			return g_dbus_create_error(msg, ERROR_INTERFACE
+					".Failed",
+					"Failed to get AVDTP session");
+
+		sink_setup_stream(dev->sink, session);
+		avdtp_unref(session);
+	}
+
+	/* The previous calls should cause a call to the state callback to
+	 * indicate AUDIO_STATE_CONNECTING */
+	if (priv->state != AUDIO_STATE_CONNECTING)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+				".ConnectFailed",
+				"Headset connect failed");
+
+	priv->conn_req = dbus_message_ref(msg);
+
+	return NULL;
+}
+
+static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *dev = data;
+	struct dev_priv *priv = dev->priv;
+
+	if (priv->state == AUDIO_STATE_DISCONNECTED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected",
+						"Not connected");
+
+	if (priv->dc_req)
+		return dbus_message_new_method_return(msg);
+
+	priv->dc_req = dbus_message_ref(msg);
+
+	if (priv->hs_state != HEADSET_STATE_DISCONNECTED)
+		headset_shutdown(dev);
+	else if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED)
+		sink_shutdown(dev->sink);
+	else {
+		dbus_message_unref(priv->dc_req);
+		priv->dc_req = NULL;
+		return dbus_message_new_method_return(msg);
+	}
+
+	return NULL;
+}
+
+static DBusMessage *dev_get_properties(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *device = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	const char *state;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* State */
+	state = state2str(device->priv->state);
+	if (state)
+		dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static GDBusMethodTable dev_methods[] = {
+	{ "Connect",		"",	"",	dev_connect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Disconnect",		"",	"",	dev_disconnect },
+	{ "GetProperties",	"",	"a{sv}",dev_get_properties },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable dev_signals[] = {
+	{ "PropertyChanged",		"sv"	},
+	{ NULL, NULL }
+};
+
+struct audio_device *audio_device_register(DBusConnection *conn,
+					struct btd_device *device,
+					const char *path, const bdaddr_t *src,
+					const bdaddr_t *dst)
+{
+	struct audio_device *dev;
+
+	if (!conn || !path)
+		return NULL;
+
+	dev = g_new0(struct audio_device, 1);
+
+	dev->btd_dev = btd_device_ref(device);
+	dev->path = g_strdup(path);
+	bacpy(&dev->dst, dst);
+	bacpy(&dev->src, src);
+	dev->conn = dbus_connection_ref(conn);
+	dev->priv = g_new0(struct dev_priv, 1);
+	dev->priv->state = AUDIO_STATE_DISCONNECTED;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_INTERFACE,
+					dev_methods, dev_signals, NULL,
+					dev, NULL)) {
+		error("Unable to register %s on %s", AUDIO_INTERFACE,
+								dev->path);
+		device_free(dev);
+		return NULL;
+	}
+
+	debug("Registered interface %s on path %s", AUDIO_INTERFACE,
+								dev->path);
+
+	if (sink_callback_id == 0)
+		sink_callback_id = sink_add_state_cb(device_sink_cb, NULL);
+
+	if (avdtp_callback_id == 0)
+		avdtp_callback_id = avdtp_add_state_cb(device_avdtp_cb, NULL);
+	if (avctp_callback_id == 0)
+		avctp_callback_id = avctp_add_state_cb(device_avctp_cb, NULL);
+
+	if (headset_callback_id == 0)
+		headset_callback_id = headset_add_state_cb(device_headset_cb,
+									NULL);
+
+	return dev;
+}
+
+gboolean audio_device_is_active(struct audio_device *dev,
+						const char *interface)
+{
+	if (!interface) {
+		if ((dev->sink || dev->source) &&
+			avdtp_is_connected(&dev->src, &dev->dst))
+			return TRUE;
+
+		if (dev->headset && headset_is_active(dev))
+			return TRUE;
+	}
+	else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink &&
+			avdtp_is_connected(&dev->src, &dev->dst))
+		return TRUE;
+	else if (!strcmp(interface, AUDIO_SOURCE_INTERFACE) && dev->source &&
+			avdtp_is_connected(&dev->src, &dev->dst))
+		return TRUE;
+	else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset &&
+			headset_is_active(dev))
+		return TRUE;
+	else if (!strcmp(interface, AUDIO_CONTROL_INTERFACE) && dev->control &&
+			control_is_active(dev))
+		return TRUE;
+	else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway &&
+			gateway_is_connected(dev))
+		return TRUE;
+
+	return FALSE;
+}
+
+void audio_device_unregister(struct audio_device *device)
+{
+	unix_device_removed(device);
+
+	if (device->headset)
+		headset_unregister(device);
+
+	if (device->sink)
+		sink_unregister(device);
+
+	if (device->source)
+		source_unregister(device);
+
+	if (device->control)
+		control_unregister(device);
+
+	g_dbus_unregister_interface(device->conn, device->path,
+						AUDIO_INTERFACE);
+
+	device_free(device);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+	struct audio_device *dev = user_data;
+	struct dev_priv *priv = dev->priv;
+
+	if (derr == NULL)
+		priv->authorized = TRUE;
+
+	while (priv->auths) {
+		struct service_auth *auth = priv->auths->data;
+
+		auth->cb(derr, auth->user_data);
+		priv->auths = g_slist_remove(priv->auths, auth);
+		g_free(auth);
+	}
+}
+
+static gboolean auth_idle_cb(gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct dev_priv *priv = dev->priv;
+
+	priv->auth_idle_id = 0;
+
+	auth_cb(NULL, dev);
+
+	return FALSE;
+}
+
+static gboolean audio_device_is_connected(struct audio_device *dev)
+{
+	if (dev->headset) {
+		headset_state_t state = headset_get_state(dev);
+
+		if (state == HEADSET_STATE_CONNECTED ||
+				state == HEADSET_STATE_PLAY_IN_PROGRESS ||
+				state == HEADSET_STATE_PLAYING)
+			return TRUE;
+	}
+
+	if (dev->sink) {
+		sink_state_t state = sink_get_state(dev);
+
+		if (state == SINK_STATE_CONNECTED ||
+				state == SINK_STATE_PLAYING)
+			return TRUE;
+	}
+
+	if (dev->source) {
+		source_state_t state = source_get_state(dev);
+
+		if (state == SOURCE_STATE_CONNECTED ||
+				state == SOURCE_STATE_PLAYING)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+int audio_device_request_authorization(struct audio_device *dev,
+					const char *uuid, service_auth_cb cb,
+					void *user_data)
+{
+	struct dev_priv *priv = dev->priv;
+	struct service_auth *auth;
+	int err;
+
+	auth = g_try_new0(struct service_auth, 1);
+	if (!auth)
+		return -ENOMEM;
+
+	auth->cb = cb;
+	auth->user_data = user_data;
+
+	priv->auths = g_slist_append(priv->auths, auth);
+	if (g_slist_length(priv->auths) > 1)
+		return 0;
+
+	if (priv->authorized || audio_device_is_connected(dev)) {
+		priv->auth_idle_id = g_idle_add(auth_idle_cb, dev);
+		return 0;
+	}
+
+	err = btd_request_authorization(&dev->src, &dev->dst, uuid, auth_cb,
+					dev);
+	if (err < 0) {
+		priv->auths = g_slist_remove(priv->auths, auth);
+		g_free(auth);
+	}
+
+	return err;
+}
+
+int audio_device_cancel_authorization(struct audio_device *dev,
+					authorization_cb cb, void *user_data)
+{
+	struct dev_priv *priv = dev->priv;
+	GSList *l, *next;
+
+	for (l = priv->auths; l != NULL; l = next) {
+		struct service_auth *auth = priv->auths->data;
+
+		next = g_slist_next(l);
+
+		if (cb && auth->cb != cb)
+			continue;
+
+		if (user_data && auth->user_data != user_data)
+			continue;
+
+		priv->auths = g_slist_remove(priv->auths, auth);
+		g_free(auth);
+	}
+
+	if (g_slist_length(priv->auths) == 0) {
+		if (priv->auth_idle_id > 0) {
+			g_source_remove(priv->auth_idle_id);
+			priv->auth_idle_id = 0;
+		} else
+			btd_cancel_authorization(&dev->src, &dev->dst);
+	}
+
+	return 0;
+}
diff --git a/audio/device.h b/audio/device.h
new file mode 100644
index 0000000..061c3da
--- /dev/null
+++ b/audio/device.h
@@ -0,0 +1,87 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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"
+
+/* Move these to respective .h files once they exist */
+#define AUDIO_SOURCE_INTERFACE		"org.bluez.AudioSource"
+#define AUDIO_CONTROL_INTERFACE		"org.bluez.Control"
+
+struct source;
+struct control;
+struct target;
+struct sink;
+struct headset;
+struct gateway;
+struct dev_priv;
+
+struct audio_device {
+	struct btd_device *btd_dev;
+
+	DBusConnection *conn;
+	char *path;
+	bdaddr_t src;
+	bdaddr_t dst;
+
+	gboolean auto_connect;
+
+	struct headset *headset;
+	struct gateway *gateway;
+	struct sink *sink;
+	struct source *source;
+	struct control *control;
+	struct target *target;
+
+	struct dev_priv *priv;
+};
+
+struct audio_device *audio_device_register(DBusConnection *conn,
+					struct btd_device *device,
+					const char *path, const bdaddr_t *src,
+					const bdaddr_t *dst);
+
+void audio_device_unregister(struct audio_device *device);
+
+gboolean audio_device_is_active(struct audio_device *dev,
+						const char *interface);
+
+typedef void (*authorization_cb) (DBusError *derr, void *user_data);
+
+int audio_device_request_authorization(struct audio_device *dev,
+					const char *uuid, authorization_cb cb,
+					void *user_data);
diff --git a/audio/gateway.c b/audio/gateway.c
new file mode 100644
index 0000000..09a401c
--- /dev/null
+++ b/audio/gateway.c
@@ -0,0 +1,1223 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2008-2009  Leonid Movshovich <event.riga@gmail.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 <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/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "glib-helper.h"
+#include "device.h"
+#include "gateway.h"
+#include "logging.h"
+#include "error.h"
+#include "btio.h"
+#include "dbus-common.h"
+
+#define RFCOMM_BUF_SIZE 256
+/* not-more-then-16 defined by GSM + 1 for NULL + padding */
+#define AG_INDICATOR_DESCR_SIZE 20
+#define AG_CALLER_NUM_SIZE 64	/* size of number + type */
+
+/* commands */
+#define AG_FEATURES "AT+BRSF=26\r"     /* = 0x7F = All features supported */
+#define AG_INDICATORS_SUPP "AT+CIND=?\r"
+#define AG_INDICATORS_VAL "AT+CIND?\r"
+#define AG_INDICATORS_ENABLE "AT+CMER=3,0,0,1\r"
+#define AG_HOLD_MPTY_SUPP "AT+CHLD=?\r"
+#define AG_CALLER_IDENT_ENABLE "AT+CLIP=1\r"
+#define AG_CARRIER_FORMAT "AT+COPS=3,0\r"
+#define AG_EXTENDED_RESULT_CODE "AT+CMEE=1\r"
+
+#define AG_FEATURE_3WAY 0x1
+#define AG_FEATURE_EXTENDED_RES_CODE 0x100
+/* Hold and multipary AG features.
+ * Comments below are copied from hands-free spec for reference */
+/* Releases all held calls or sets User Determined User Busy (UDUB)
+ * for a waiting call */
+#define AG_CHLD_0 0x01
+/* Releases all active calls (if any exist) and accepts the other
+ * (held or waiting) call */
+#define AG_CHLD_1 0x02
+/* Releases specified active call only <x> */
+#define AG_CHLD_1x 0x04
+/* Places all active calls (if any exist) on hold and accepts the other
+ * (held or waiting) call */
+#define AG_CHLD_2 0x08
+/* Request private consultation mode with specified call <x> (Place all
+ * calls on hold EXCEPT the call <x>) */
+#define AG_CHLD_2x 0x10
+/* Adds a held call to the conversation */
+#define AG_CHLD_3 0x20
+/* Connects the two calls and disconnects the subscriber from both calls
+ * (Explicit Call Transfer). Support for this value and its associated
+ * functionality is optional for the HF. */
+#define AG_CHLD_4 0x40
+
+#define OK_RESPONSE "\r\nOK\r\n"
+#define ERROR_RESPONSE "\r\nERROR\r\n"
+
+struct indicator {
+	gchar descr[AG_INDICATOR_DESCR_SIZE];
+	gint value;
+};
+
+struct gateway {
+	gateway_state_t state;
+	GIOChannel *rfcomm;
+	guint rfcomm_watch_id;
+	GIOChannel *sco;
+	gateway_stream_cb_t sco_start_cb;
+	void *sco_start_cb_data;
+	DBusMessage *connect_message;
+	guint ag_features;
+	guint hold_multiparty_features;
+	GSList *indies;
+	gboolean is_dialing;
+	gboolean call_active;
+
+	int sp_gain;
+	int mic_gain;
+};
+
+static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond,
+					struct audio_device *device);
+
+int gateway_close(struct audio_device *device);
+
+static void rfcomm_start_watch(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	gw->rfcomm_watch_id = g_io_add_watch(gw->rfcomm,
+			G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+			(GIOFunc) rfcomm_ag_data_cb, dev);
+}
+
+static void rfcomm_stop_watch(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	g_source_remove(gw->rfcomm_watch_id);
+}
+
+static gboolean io_channel_write_all(GIOChannel *io, gchar *data,
+					gsize count)
+{
+	gsize written = 0;
+	GIOStatus status;
+
+	while (count > 0) {
+		status = g_io_channel_write_chars(io, data, count, &written,
+						NULL);
+		if (status != G_IO_STATUS_NORMAL)
+			return FALSE;
+
+		data += written;
+		count -= written;
+	}
+	return TRUE;
+}
+
+/* it's worth to mention that data and response could be the same pointers */
+static gboolean rfcomm_send_and_read(struct gateway *gw, gchar *data,
+                                    gchar *response, gsize count)
+{
+	GIOChannel *rfcomm = gw->rfcomm;
+	gsize read = 0;
+	gboolean got_ok = FALSE;
+	gboolean got_error = FALSE;
+	gchar *resp_buf = response;
+	gsize toread = RFCOMM_BUF_SIZE - 1;
+	GIOStatus status;
+
+	if (!io_channel_write_all(rfcomm, data, count))
+		return FALSE;
+
+	while (!(got_ok || got_error)) {
+		status = g_io_channel_read_chars(rfcomm, resp_buf, toread,
+						&read, NULL);
+		if (status == G_IO_STATUS_NORMAL)
+			resp_buf[read] = '\0';
+		else {
+			debug("rfcomm_send_and_read(): %m");
+			return FALSE;
+		}
+		got_ok = NULL != strstr(resp_buf, OK_RESPONSE);
+		got_error = NULL != strstr(resp_buf, ERROR_RESPONSE);
+		resp_buf += read;
+		toread -= read;
+	}
+	return TRUE;
+}
+
+/* get <descr> from the names: (<descr>, (<values>)), (<descr>, (<values>))
+ * ... */
+static GSList *parse_indicator_names(gchar *names, GSList *indies)
+{
+	gchar *current = names - 1;
+	GSList *result = indies;
+	gchar *next;
+	struct indicator *ind;
+
+	while (current != NULL) {
+		current += 2;
+		next = strstr(current, ",(");
+		ind = g_slice_new(struct indicator);
+		strncpy(ind->descr, current, 20);
+		ind->descr[(intptr_t) next - (intptr_t) current] = '\0';
+		result = g_slist_append(result, (gpointer) ind);
+		current = strstr(next + 1, ",(");
+	}
+	return result;
+}
+
+/* get values from <val0>,<val1>,... */
+static GSList *parse_indicator_values(gchar *values, GSList *indies)
+{
+	gint val;
+	gchar *current = values - 1;
+	GSList *runner = indies;
+	struct indicator *ind;
+
+	while (current != NULL) {
+		current += 1;
+		sscanf(current, "%d", &val);
+		current = strchr(current, ',');
+		ind = g_slist_nth_data(runner, 0);
+		ind->value = val;
+		runner = g_slist_next(runner);
+	}
+	return indies;
+}
+
+/* get values from <val0>,<val1>,... */
+static guint get_hold_mpty_features(gchar *features)
+{
+	guint result = 0;
+
+	if (strstr(features, "0"))
+		result |= AG_CHLD_0;
+
+	if (strstr(features, "1"))
+		result |= AG_CHLD_1;
+
+	if (strstr(features, "1x"))
+		result |= AG_CHLD_1x;
+
+	if (strstr(features, "2"))
+		result |= AG_CHLD_2;
+
+	if (strstr(features, "2x"))
+		result |= AG_CHLD_2x;
+
+	if (strstr(features, "3"))
+		result |= AG_CHLD_3;
+
+	if (strstr(features, "4"))
+		result |= AG_CHLD_4;
+
+	return result;
+}
+
+static gboolean establish_service_level_conn(struct gateway *gw)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	gboolean res;
+
+	debug("at the begin of establish_service_level_conn()");
+	res = rfcomm_send_and_read(gw, AG_FEATURES, buf,
+				sizeof(AG_FEATURES) - 1);
+	if (!res || sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features) != 1)
+		return FALSE;
+
+	debug("features are 0x%X", gw->ag_features);
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_SUPP, buf,
+				sizeof(AG_INDICATORS_SUPP) - 1);
+	if (!res || !strstr(buf, "+CIND:"))
+		return FALSE;
+
+	gw->indies = parse_indicator_names(strchr(buf, '('), NULL);
+
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_VAL, buf,
+		sizeof(AG_INDICATORS_VAL) - 1);
+	if (!res || !strstr(buf, "+CIND:"))
+		return FALSE;
+
+	gw->indies = parse_indicator_values(strchr(buf, ':') + 1, gw->indies);
+
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_ENABLE, buf,
+				sizeof(AG_INDICATORS_ENABLE) - 1);
+	if (!res || !strstr(buf, "OK"))
+		return FALSE;
+
+	if ((gw->ag_features & AG_FEATURE_3WAY) != 0) {
+		res = rfcomm_send_and_read(gw, AG_HOLD_MPTY_SUPP, buf,
+				sizeof(AG_HOLD_MPTY_SUPP) - 1);
+		if (!res || !strstr(buf, "+CHLD:")) {
+			g_slice_free1(RFCOMM_BUF_SIZE, buf);
+			return FALSE;
+		}
+		gw->hold_multiparty_features = get_hold_mpty_features(
+							strchr(buf, '('));
+
+	} else
+		gw->hold_multiparty_features = 0;
+
+	debug("Service layer connection successfully established!");
+	rfcomm_send_and_read(gw, AG_CALLER_IDENT_ENABLE, buf,
+			sizeof(AG_CALLER_IDENT_ENABLE) - 1);
+	rfcomm_send_and_read(gw, AG_CARRIER_FORMAT, buf,
+			sizeof(AG_CARRIER_FORMAT) - 1);
+	if ((gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) != 0)
+		rfcomm_send_and_read(gw, AG_EXTENDED_RESULT_CODE, buf,
+			sizeof(AG_EXTENDED_RESULT_CODE) - 1);
+
+	return TRUE;
+}
+
+static void process_ind_change(struct audio_device *dev, guint index,
+							gint value)
+{
+	struct gateway *gw = dev->gateway;
+	struct indicator *ind = g_slist_nth_data(gw->indies, index - 1);
+	gchar *name = ind->descr;
+
+	ind->value = value;
+
+	debug("at the begin of process_ind_change, name is %s", name);
+	if (!strcmp(name, "\"call\"")) {
+		if (value > 0) {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"CallStarted", DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+			gw->call_active = TRUE;
+		} else {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"CallEnded", DBUS_TYPE_INVALID);
+			gw->call_active = FALSE;
+		}
+
+	} else if (!strcmp(name, "\"callsetup\"")) {
+		if (value == 0 && gw->is_dialing) {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"CallTerminated",
+					DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+		} else if (!gw->is_dialing && value > 0)
+			gw->is_dialing = TRUE;
+
+	} else if (!strcmp(name, "\"callheld\"")) {
+		/* FIXME: The following code is based on assumptions only.
+		 * Has to be tested for interoperability
+		 * I assume that callheld=2 would be sent when dial from HF
+		 * failed in case of 3-way call
+		 * Unfortunately this path is not covered by the HF spec so
+		 * the code has to be tested for interop
+		*/
+		/* '2' means: all calls held, no active calls */
+		if (value == 2) {
+			if (gw->is_dialing) {
+				g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"CallTerminated",
+					DBUS_TYPE_INVALID);
+				gw->is_dialing = FALSE;
+			}
+		}
+	} else if (!strcmp(name, "\"service\""))
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE, "RegistrationStatus",
+				DBUS_TYPE_UINT16, &value);
+	else if (!strcmp(name, "\"signal\""))
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE, "SignalStrength",
+				DBUS_TYPE_UINT16, &value);
+	else if (!strcmp(name, "\"roam\""))
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE, "RoamingStatus",
+				DBUS_TYPE_UINT16, &value);
+	else if (!strcmp(name, "\"battchg\""))
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE, "BatteryCharge",
+				DBUS_TYPE_UINT16, &value);
+}
+
+static void process_ring(struct audio_device *device, GIOChannel *chan,
+			gchar *buf)
+{
+	gchar number[AG_CALLER_NUM_SIZE];
+	gchar *cli;
+	gchar *sep;
+	gsize read;
+	GIOStatus status;
+
+	rfcomm_stop_watch(device);
+	status = g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read, NULL);
+	if (status != G_IO_STATUS_NORMAL)
+		return;
+
+	debug("at the begin of process_ring");
+	if (strlen(buf) > AG_CALLER_NUM_SIZE + 10)
+		error("process_ring(): buf is too long '%s'", buf);
+	else if ((cli = strstr(buf, "\r\n+CLIP"))) {
+		if (sscanf(cli, "\r\n+CLIP: \"%s", number) == 1) {
+			sep = strchr(number, '"');
+			sep[0] = '\0';
+
+			/* FIXME:signal will be emitted on each RING+CLIP.
+			 * That's bad */
+			cli = number;
+			g_dbus_emit_signal(device->conn, device->path,
+					AUDIO_GATEWAY_INTERFACE, "Ring",
+					DBUS_TYPE_STRING, &cli,
+					DBUS_TYPE_INVALID);
+			device->gateway->is_dialing = TRUE;
+		} else
+			error("process_ring(): '%s' in place of +CLIP after RING", buf);
+
+	}
+
+	rfcomm_start_watch(device);
+}
+
+static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond,
+					struct audio_device *device)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	struct gateway *gw;
+	gsize read;
+	/* some space for value */
+	gchar indicator[AG_INDICATOR_DESCR_SIZE + 4];
+	gint value;
+	guint index;
+	gchar *sep;
+
+	debug("at the begin of rfcomm_ag_data_cb()");
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	gw = device->gateway;
+
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		debug("connection with remote BT is closed");
+		gateway_close(device);
+		return FALSE;
+	}
+
+	if (g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read, NULL)
+			!= G_IO_STATUS_NORMAL)
+		return TRUE;
+	buf[read] = '\0';
+
+	if (strlen(buf) > AG_INDICATOR_DESCR_SIZE + 14)
+		error("rfcomm_ag_data_cb(): buf is too long '%s'", buf);
+	else if (sscanf(buf, "\r\n+CIEV:%s\r\n", indicator) == 1) {
+		sep = strchr(indicator, ',');
+		sep[0] = '\0';
+		sep += 1;
+		index = atoi(indicator);
+		value = atoi(sep);
+		process_ind_change(device, index, value);
+	} else if (strstr(buf, "RING"))
+		process_ring(device, chan, buf);
+	else if (sscanf(buf, "\r\n+BVRA:%d\r\n", &value) == 1) {
+		if (value == 0)
+			g_dbus_emit_signal(device->conn, device->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"VoiceRecognitionActive",
+					DBUS_TYPE_INVALID);
+		else
+			g_dbus_emit_signal(device->conn, device->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"VoiceRecognitionInactive",
+					DBUS_TYPE_INVALID);
+	} else if (sscanf(buf, "\r\n+VGS:%d\r\n", &value) == 1) {
+		gw->sp_gain = value;
+		emit_property_changed(device->conn, device->path,
+				AUDIO_GATEWAY_INTERFACE, "SpeakerGain",
+				DBUS_TYPE_UINT16, &value);
+	} else if (sscanf(buf, "\r\n+VGM:%d\r\n", &value) == 1) {
+		gw->mic_gain = value;
+		emit_property_changed(device->conn, device->path,
+				AUDIO_GATEWAY_INTERFACE, "MicrophoneGain",
+				DBUS_TYPE_UINT16, &value);
+	} else
+		error("rfcomm_ag_data_cb(): read wrong data '%s'", buf);
+
+	return TRUE;
+}
+
+static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
+			struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		debug("sco connection is released");
+		g_io_channel_shutdown(gw->sco, TRUE, NULL);
+		g_io_channel_unref(gw->sco);
+		gw->sco = NULL;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+	struct audio_device *dev = (struct audio_device *) user_data;
+	struct gateway *gw = dev->gateway;
+
+	debug("at the begin of sco_connect_cb() in gateway.c");
+
+	if (err) {
+		error("sco_connect_cb(): %s", err->message);
+		/* not sure, but from other point of view,
+		 * what is the reason to have headset which
+		 * cannot play audio? */
+		if (gw->sco_start_cb)
+			gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+		gateway_close(dev);
+		return;
+	}
+
+	gw->sco = g_io_channel_ref(chan);
+	if (gw->sco_start_cb)
+		gw->sco_start_cb(dev, gw->sco_start_cb_data);
+
+	/* why is this here? */
+	fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
+	g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+				(GIOFunc) sco_io_cb, dev);
+}
+
+static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
+				gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct gateway *gw = dev->gateway;
+	DBusMessage *conn_mes = gw->connect_message;
+	gchar gw_addr[18];
+	GIOFlags flags;
+
+	if (err) {
+		error("connect(): %s", err->message);
+		if (gw->sco_start_cb)
+			gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+		return;
+	}
+
+	ba2str(&dev->dst, gw_addr);
+	/* Blocking mode should be default, but just in case: */
+	flags = g_io_channel_get_flags(chan);
+	flags &= ~G_IO_FLAG_NONBLOCK;
+	flags &= G_IO_FLAG_MASK;
+	g_io_channel_set_flags(chan, flags, NULL);
+	g_io_channel_set_encoding(chan, NULL, NULL);
+	g_io_channel_set_buffered(chan, FALSE);
+	if (!gw->rfcomm)
+		gw->rfcomm = g_io_channel_ref(chan);
+
+	if (establish_service_level_conn(dev->gateway)) {
+		gboolean value = TRUE;
+
+		debug("%s: Connected to %s", dev->path, gw_addr);
+		rfcomm_start_watch(dev);
+		if (conn_mes) {
+			DBusMessage *reply =
+				dbus_message_new_method_return(conn_mes);
+			dbus_connection_send(dev->conn, reply, NULL);
+			dbus_message_unref(reply);
+			dbus_message_unref(conn_mes);
+			gw->connect_message = NULL;
+		}
+
+		gw->state = GATEWAY_STATE_CONNECTED;
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE,
+				"Connected", DBUS_TYPE_BOOLEAN,	&value);
+		return;
+	} else
+		error("%s: Failed to establish service layer connection to %s",
+			dev->path, gw_addr);
+
+	if (NULL != gw->sco_start_cb)
+		gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+
+	gateway_close(dev);
+}
+
+static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	DBusMessage *msg = dev->gateway->connect_message;
+	int ch = -1;
+	sdp_list_t *protos, *classes;
+	uuid_t uuid;
+	gateway_stream_cb_t sco_cb;
+	GIOChannel *io;
+	GError *err = NULL;
+
+	if (perr < 0) {
+		error("Unable to get service record: %s (%d)", strerror(-perr),
+					-perr);
+		goto fail;
+	}
+
+	if (!recs || !recs->data) {
+		error("No records found");
+		goto fail;
+	}
+
+	if (sdp_get_service_classes(recs->data, &classes) < 0) {
+		error("Unable to get service classes from record");
+		goto fail;
+	}
+
+	if (sdp_get_access_protos(recs->data, &protos) < 0) {
+		error("Unable to get access protocols from record");
+		goto fail;
+	}
+
+	memcpy(&uuid, classes->data, sizeof(uuid));
+	sdp_list_free(classes, free);
+
+	if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
+			uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
+		sdp_list_free(protos, NULL);
+		error("Invalid service record or not HFP");
+		goto fail;
+	}
+
+	ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+	sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+	sdp_list_free(protos, NULL);
+	if (ch <= 0) {
+		error("Unable to extract RFCOMM channel from service record");
+		goto fail;
+	}
+
+	io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+				BT_IO_OPT_DEST_BDADDR, &dev->dst,
+				BT_IO_OPT_CHANNEL, ch,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("Unable to connect: %s", err->message);
+		if (msg) {
+			error_common_reply(dev->conn, msg, ERROR_INTERFACE
+						".ConnectionAttemptFailed",
+						err->message);
+			msg = NULL;
+		}
+		g_error_free(err);
+		gateway_close(dev);
+	}
+
+	g_io_channel_unref(io);
+	return;
+
+fail:
+	if (msg)
+		error_common_reply(dev->conn, msg, ERROR_INTERFACE
+					".NotSupported", "Not supported");
+
+	dev->gateway->connect_message = NULL;
+
+	sco_cb = dev->gateway->sco_start_cb;
+	if (sco_cb)
+		sco_cb(NULL, dev->gateway->sco_start_cb_data);
+}
+
+static int get_records(struct audio_device *device)
+{
+	uuid_t uuid;
+
+	sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID);
+	return bt_search_service(&device->src, &device->dst, &uuid,
+				get_record_cb, device, NULL);
+}
+
+static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *au_dev = (struct audio_device *) data;
+	struct gateway *gw = au_dev->gateway;
+
+	debug("at the begin of ag_connect()");
+	if (gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".AlreadyConnected",
+					"Already Connected");
+
+	gw->connect_message = dbus_message_ref(msg);
+	if (get_records(au_dev) < 0) {
+		dbus_message_unref(gw->connect_message);
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".ConnectAttemptFailed",
+					"Connect Attempt Failed");
+	}
+	debug("at the end of ag_connect()");
+	return NULL;
+}
+
+static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	DBusMessage *reply = NULL;
+	char gw_addr[18];
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	gateway_close(device);
+	ba2str(&device->dst, gw_addr);
+	debug("Disconnected from %s, %s", gw_addr, device->path);
+
+	return reply;
+}
+
+static DBusMessage *process_ag_reponse(DBusMessage *msg, gchar *response)
+{
+	DBusMessage *reply;
+
+
+	debug("in process_ag_reponse, response is %s", response);
+	if (strstr(response, OK_RESPONSE))
+		reply = dbus_message_new_method_return(msg);
+	else {
+		/* FIXME: some code should be here to processes errors
+		 *  in better fasion */
+		debug("AG responded with '%s' to %s method call", response,
+				dbus_message_get_member(msg));
+		reply = dbus_message_new_error(msg, ERROR_INTERFACE
+					".OperationFailed",
+					"Operation failed.See log for details");
+	}
+	return reply;
+}
+
+static DBusMessage *process_simple(DBusMessage *msg, struct audio_device *dev,
+					gchar *data)
+{
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+
+	rfcomm_stop_watch(dev);
+	rfcomm_send_and_read(gw, data, buf, strlen(data));
+	rfcomm_start_watch(dev);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_ANSWER "ATA\r"
+
+static DBusMessage *ag_answer(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *dev = data;
+	struct gateway *gw = dev->gateway;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	if (gw->call_active)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".CallAlreadyAnswered",
+					"Call AlreadyAnswered");
+
+	return process_simple(msg, dev, AG_ANSWER);
+}
+
+#define AG_HANGUP "AT+CHUP\r"
+
+static DBusMessage *ag_terminate_call(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *dev = data;
+	struct gateway *gw = dev->gateway;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	return process_simple(msg, dev, AG_HANGUP);
+}
+
+/* according to GSM spec */
+#define ALLOWED_NUMBER_SYMBOLS "1234567890*#ABCD"
+#define AG_PLACE_CALL "ATD%s;\r"
+/* dialing from memory is not supported as headset spec doesn't define a way
+ * to retreive phone memory entries.
+ */
+static DBusMessage *ag_call(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number;
+	gint atd_len;
+	DBusMessage *result;
+
+	debug("at the begin of ag_call()");
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+				DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS))
+		return dbus_message_new_error(msg,
+			ERROR_INTERFACE ".BadNumber",
+			"Number contains characters which are not allowed");
+
+	atd_len = sprintf(buf, AG_PLACE_CALL, number);
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, buf, buf, atd_len);
+	rfcomm_start_watch(device);
+
+	result = process_ag_reponse(msg, buf);
+	return result;
+}
+
+#define AG_GET_CARRIER "AT+COPS?\r"
+
+static DBusMessage *ag_get_operator(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *dev = (struct audio_device *) data;
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	GIOChannel *rfcomm = gw->rfcomm;
+	gsize read;
+	gchar *result, *sep;
+	DBusMessage *reply;
+	GIOStatus status;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	rfcomm_stop_watch(dev);
+	io_channel_write_all(rfcomm, AG_GET_CARRIER, strlen(AG_GET_CARRIER));
+
+	status = g_io_channel_read_chars(rfcomm, buf, RFCOMM_BUF_SIZE - 1,
+						&read, NULL);
+	rfcomm_start_watch(dev);
+	if (G_IO_STATUS_NORMAL == status) {
+		buf[read] = '\0';
+		if (strstr(buf, "+COPS")) {
+			if (!strrchr(buf, ','))
+				result = "0";
+			else {
+				result = strchr(buf, '\"') + 1;
+				sep = strchr(result, '\"');
+				sep[0] = '\0';
+			}
+
+			reply = dbus_message_new_method_return(msg);
+			dbus_message_append_args(reply, DBUS_TYPE_STRING,
+						&result, DBUS_TYPE_INVALID);
+		} else {
+			info("ag_get_operator(): '+COPS' expected but"
+				" '%s' received", buf);
+			reply = dbus_message_new_error(msg, ERROR_INTERFACE
+						".Failed",
+						"Unexpected response from AG");
+		}
+	} else {
+		error("ag_get_operator(): %m");
+		reply = dbus_message_new_error(msg, ERROR_INTERFACE
+					".ConnectionFailed",
+					"Failed to receive response from AG");
+	}
+
+	return reply;
+}
+
+#define AG_SEND_DTMF "AT+VTS=%c\r"
+static DBusMessage *ag_send_dtmf(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number;
+	gint com_len;
+	gboolean got_ok = TRUE;
+	gint num_len;
+	gint i = 0;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+				DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS))
+		return dbus_message_new_error(msg,
+			ERROR_INTERFACE ".BadNumber",
+			"Number contains characters which are not allowed");
+
+	num_len = strlen(number);
+	rfcomm_stop_watch(device);
+	while (i < num_len && got_ok) {
+		com_len = sprintf(buf, AG_SEND_DTMF, number[i]);
+		rfcomm_send_and_read(gw, buf, buf, com_len);
+		got_ok = NULL != strstr(buf, OK_RESPONSE);
+		i += 1;
+	}
+	rfcomm_start_watch(device);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
+#define CNUM_LEN 5             /* length of "+CNUM" string */
+#define MAX_NUMBER_CNT 16
+static DBusMessage *ag_get_subscriber_num(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number, *end;
+	DBusMessage *reply = dbus_message_new_method_return(msg);
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf,
+			strlen(AG_GET_SUBSCRIBER_NUMS));
+	rfcomm_start_watch(device);
+
+	if (strlen(buf) > AG_CALLER_NUM_SIZE + 21)
+		error("ag_get_subscriber_num(): buf is too long '%s'", buf);
+	else if (strstr(buf, "+CNUM")) {
+		number = strchr(buf, ',');
+		number++;
+		end = strchr(number, ',');
+		if (end) {
+			*end = '\0';
+			dbus_message_append_args(reply, DBUS_TYPE_STRING,
+						&number, DBUS_TYPE_INVALID);
+		}
+	} else
+		error("ag_get_subscriber_num(): read wrong data '%s'", buf);
+
+	return reply;
+}
+
+static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	gboolean value;
+	guint index = 0;
+	struct indicator *ind;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* Connected */
+	value = gateway_is_connected(device);
+	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+	if (!value)
+		goto done;
+
+	while ((ind = g_slist_nth_data(gw->indies, index))) {
+		if(!strcmp(ind->descr, "\"service\""))
+			dict_append_entry(&dict, "RegistrationStatus",
+					DBUS_TYPE_UINT16, &ind->value);
+		else if (!strcmp(ind->descr, "\"signal\""))
+			dict_append_entry(&dict, "SignalStrength",
+					DBUS_TYPE_UINT16, &ind->value);
+		else if (!strcmp(ind->descr, "\"roam\""))
+			dict_append_entry(&dict, "RoamingStatus",
+					DBUS_TYPE_UINT16, &ind->value);
+		else if (!strcmp(ind->descr, "\"battchg\""))
+			dict_append_entry(&dict, "BatteryCharge",
+					DBUS_TYPE_UINT16, &ind->value);
+		index++;
+	}
+
+	/* SpeakerGain */
+	dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16,
+				&device->gateway->sp_gain);
+
+	/* MicrophoneGain */
+	dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16,
+				&device->gateway->mic_gain);
+done:
+	dbus_message_iter_close_container(&iter, &dict);
+	return reply;
+}
+
+static GDBusMethodTable gateway_methods[] = {
+	{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Disconnect", "", "", ag_disconnect },
+	{ "AnswerCall", "", "", ag_answer },
+	{ "TerminateCall", "", "", ag_terminate_call },
+	{ "Call", "s", "", ag_call },
+	{ "GetOperatorName", "", "s", ag_get_operator },
+	{ "SendDTMF", "s", "", ag_send_dtmf },
+	{ "GetSubscriberNumber", "", "s", ag_get_subscriber_num },
+	{ "GetProperties", "", "a{sv}", ag_get_properties },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable gateway_signals[] = {
+	{ "Ring", "s" },
+	{ "CallTerminated", "" },
+	{ "CallStarted", "" },
+	{ "CallEnded", "" },
+	{ "PropertyChanged", "sv" },
+	{ NULL, NULL }
+};
+
+struct gateway *gateway_init(struct audio_device *dev)
+{
+	struct gateway *gw;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					gateway_methods, gateway_signals,
+					NULL, dev, NULL))
+		return NULL;
+
+	debug("in gateway_init, dev is %p", dev);
+	gw = g_new0(struct gateway, 1);
+	gw->indies = NULL;
+	gw->is_dialing = FALSE;
+	gw->call_active = FALSE;
+	gw->state = GATEWAY_STATE_DISCONNECTED;
+	return gw;
+
+}
+
+gboolean gateway_is_connected(struct audio_device *dev)
+{
+	return (dev && dev->gateway &&
+			dev->gateway->state == GATEWAY_STATE_CONNECTED);
+}
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
+{
+	if (!io)
+		return -EINVAL;
+
+	g_io_channel_ref(io);
+	dev->gateway->rfcomm = io;
+
+	return 0;
+}
+
+int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (gw->sco)
+		return -EISCONN;
+
+	gw->sco = g_io_channel_ref(io);
+
+	g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                (GIOFunc) sco_io_cb, dev);
+	return 0;
+}
+
+void gateway_start_service(struct audio_device *device)
+{
+	rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+}
+
+static void indicator_slice_free(gpointer mem)
+{
+	g_slice_free(struct indicator, mem);
+}
+
+int gateway_close(struct audio_device *device)
+{
+	struct gateway *gw = device->gateway;
+	GIOChannel *rfcomm = gw->rfcomm;
+	GIOChannel *sco = gw->sco;
+	gboolean value = FALSE;
+
+	g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL);
+	g_slist_free(gw->indies);
+	if (rfcomm) {
+		g_io_channel_shutdown(rfcomm, TRUE, NULL);
+		g_io_channel_unref(rfcomm);
+		gw->rfcomm = NULL;
+	}
+
+	if (sco) {
+		g_io_channel_shutdown(sco, TRUE, NULL);
+		g_io_channel_unref(sco);
+		gw->sco = NULL;
+		gw->sco_start_cb = NULL;
+		gw->sco_start_cb_data = NULL;
+	}
+
+	gw->state = GATEWAY_STATE_DISCONNECTED;
+
+	emit_property_changed(device->conn, device->path,
+				AUDIO_GATEWAY_INTERFACE,
+				"Connected", DBUS_TYPE_BOOLEAN, &value);
+	return 0;
+}
+
+/* These are functions to be called from unix.c for audio system
+ * ifaces (alsa, gstreamer, etc.) */
+gboolean gateway_request_stream(struct audio_device *dev,
+				gateway_stream_cb_t cb, void *user_data)
+{
+	struct gateway *gw = dev->gateway;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	if (!gw->rfcomm) {
+		gw->sco_start_cb = cb;
+		gw->sco_start_cb_data = user_data;
+		get_records(dev);
+	} else if (!gw->sco) {
+		gw->sco_start_cb = cb;
+		gw->sco_start_cb_data = user_data;
+		io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+				BT_IO_OPT_DEST_BDADDR, &dev->dst,
+				BT_IO_OPT_INVALID);
+		if (!io) {
+			error("%s", err->message);
+			g_error_free(err);
+			return FALSE;
+		}
+	} else {
+		if (cb)
+			cb(dev, user_data);
+	}
+
+	return TRUE;
+}
+
+int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t sco_cb,
+				void *user_data)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (!gw->rfcomm) {
+		gw->sco_start_cb = sco_cb;
+		gw->sco_start_cb_data = user_data;
+		return get_records(dev);
+	}
+
+	if (sco_cb)
+		sco_cb(dev, user_data);
+
+	return 0;
+}
+
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id)
+{
+	gateway_close(dev);
+	return TRUE;
+}
+
+int gateway_get_sco_fd(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (!gw || !gw->sco)
+		return -1;
+
+	return g_io_channel_unix_get_fd(gw->sco);
+}
+
+void gateway_suspend_stream(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (!gw || !gw->sco)
+		return;
+
+	g_io_channel_shutdown(gw->sco, TRUE, NULL);
+	g_io_channel_unref(gw->sco);
+	gw->sco = NULL;
+	gw->sco_start_cb = NULL;
+	gw->sco_start_cb_data = NULL;
+}
+
diff --git a/audio/gateway.h b/audio/gateway.h
new file mode 100644
index 0000000..e539469
--- /dev/null
+++ b/audio/gateway.h
@@ -0,0 +1,47 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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.HeadsetGateway"
+
+#define DEFAULT_HSP_HS_CHANNEL 6
+#define DEFAULT_HFP_HS_CHANNEL 7
+
+typedef enum {
+	GATEWAY_STATE_DISCONNECTED,
+	GATEWAY_STATE_CONNECTED
+} gateway_state_t;
+
+typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+struct gateway *gateway_init(struct audio_device *device);
+gboolean gateway_is_connected(struct audio_device *dev);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
+void gateway_start_service(struct audio_device *device);
+gboolean gateway_request_stream(struct audio_device *dev,
+			gateway_stream_cb_t cb, void *user_data);
+int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t cb,
+			void *user_data);
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id);
+int gateway_get_sco_fd(struct audio_device *dev);
+void gateway_suspend_stream(struct audio_device *dev);
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c
new file mode 100644
index 0000000..0753f38
--- /dev/null
+++ b/audio/gsta2dpsink.c
@@ -0,0 +1,703 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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 "gsta2dpsink.h"
+
+GType gst_avdtp_sink_get_type(void);
+
+GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug);
+#define GST_CAT_DEFAULT gst_a2dp_sink_debug
+
+#define A2DP_SBC_RTP_PAYLOAD_TYPE 1
+#define TEMPLATE_MAX_BITPOOL_STR "64"
+
+#define DEFAULT_AUTOCONNECT TRUE
+
+enum {
+	PROP_0,
+	PROP_DEVICE,
+	PROP_AUTOCONNECT
+};
+
+GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN);
+
+static const GstElementDetails gst_a2dp_sink_details =
+	GST_ELEMENT_DETAILS("Bluetooth A2DP sink",
+				"Sink/Audio",
+				"Plays audio to an A2DP device",
+				"Marcel Holtmann <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:
+	if (element != NULL)
+		g_object_unref(G_OBJECT(element));
+
+	return NULL;
+}
+
+static void gst_a2dp_sink_base_init(gpointer g_class)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+	gst_element_class_set_details(element_class,
+		&gst_a2dp_sink_details);
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&gst_a2dp_sink_factory));
+}
+
+static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,
+					const GValue *value, GParamSpec *pspec)
+{
+	GstA2dpSink *self = GST_A2DP_SINK(object);
+
+	switch (prop_id) {
+	case PROP_DEVICE:
+		if (self->sink != NULL)
+			gst_avdtp_sink_set_device(self->sink,
+				g_value_get_string(value));
+
+		if (self->device != NULL)
+			g_free(self->device);
+		self->device = g_value_dup_string(value);
+		break;
+
+	case PROP_AUTOCONNECT:
+		self->autoconnect = g_value_get_boolean(value);
+
+		if (self->sink != NULL)
+			g_object_set(G_OBJECT(self->sink), "auto-connect",
+					self->autoconnect, NULL);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,
+					GValue *value, GParamSpec *pspec)
+{
+	GstA2dpSink *self = GST_A2DP_SINK(object);
+	gchar *device;
+
+	switch (prop_id) {
+	case PROP_DEVICE:
+		if (self->sink != NULL) {
+			device = gst_avdtp_sink_get_device(self->sink);
+			if (device != NULL)
+				g_value_take_string(value, device);
+		}
+		break;
+	case PROP_AUTOCONNECT:
+		if (self->sink != NULL)
+			g_object_get(G_OBJECT(self->sink), "auto-connect",
+				&self->autoconnect, NULL);
+
+		g_value_set_boolean(value, self->autoconnect);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static gboolean gst_a2dp_sink_init_ghost_pad(GstA2dpSink *self)
+{
+	GstPad *capsfilter_pad;
+
+	/* we search for the capsfilter sinkpad */
+	capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink");
+
+	/* now we add a ghostpad */
+	self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink",
+		capsfilter_pad));
+	g_object_unref(capsfilter_pad);
+
+	/* the getcaps of our ghostpad must reflect the device caps */
+	gst_pad_set_getcaps_function(GST_PAD(self->ghostpad),
+				gst_a2dp_sink_get_caps);
+	self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad);
+	gst_pad_set_setcaps_function(GST_PAD(self->ghostpad),
+			GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps));
+
+	/* we need to handle events on our own and we also need the eventfunc
+	 * of the ghostpad for forwarding calls */
+	self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad));
+	gst_pad_set_event_function(GST_PAD(self->ghostpad),
+			gst_a2dp_sink_handle_event);
+
+	if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad)))
+		GST_ERROR_OBJECT(self, "failed to add ghostpad");
+
+	return TRUE;
+}
+
+static void gst_a2dp_sink_remove_dynamic_elements(GstA2dpSink *self)
+{
+	if (self->rtp) {
+		GST_LOG_OBJECT(self, "removing rtp element from the bin");
+		if (!gst_bin_remove(GST_BIN(self), GST_ELEMENT(self->rtp)))
+			GST_WARNING_OBJECT(self, "failed to remove rtp "
+					"element from bin");
+		else
+			self->rtp = NULL;
+	}
+}
+
+static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element,
+			GstStateChange transition)
+{
+	GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+	GstA2dpSink *self = GST_A2DP_SINK(element);
+
+	switch (transition) {
+	case GST_STATE_CHANGE_READY_TO_PAUSED:
+		self->taglist = gst_tag_list_new();
+
+		gst_a2dp_sink_init_fakesink(self);
+		break;
+
+	case GST_STATE_CHANGE_NULL_TO_READY:
+		self->sink_is_in_bin = FALSE;
+		self->sink = GST_AVDTP_SINK(gst_element_factory_make(
+				"avdtpsink", "avdtpsink"));
+		if (self->sink == NULL) {
+			GST_WARNING_OBJECT(self, "failed to create avdtpsink");
+			return GST_STATE_CHANGE_FAILURE;
+		}
+
+		if (self->device != NULL)
+			gst_avdtp_sink_set_device(self->sink,
+					self->device);
+
+		g_object_set(G_OBJECT(self->sink), "auto-connect",
+					self->autoconnect, NULL);
+
+		ret = gst_element_set_state(GST_ELEMENT(self->sink),
+			GST_STATE_READY);
+		break;
+	default:
+		break;
+	}
+
+	if (ret == GST_STATE_CHANGE_FAILURE)
+		return ret;
+
+	ret = GST_ELEMENT_CLASS(parent_class)->change_state(element,
+								transition);
+
+	switch (transition) {
+	case GST_STATE_CHANGE_PAUSED_TO_READY:
+		if (self->taglist) {
+			gst_tag_list_free(self->taglist);
+			self->taglist = NULL;
+		}
+		if (self->newseg_event != NULL) {
+			gst_event_unref(self->newseg_event);
+			self->newseg_event = NULL;
+		}
+		gst_a2dp_sink_remove_fakesink(self);
+		break;
+
+	case GST_STATE_CHANGE_READY_TO_NULL:
+		if (self->sink_is_in_bin) {
+			if (!gst_bin_remove(GST_BIN(self),
+						GST_ELEMENT(self->sink)))
+				GST_WARNING_OBJECT(self, "Failed to remove "
+						"avdtpsink from bin");
+		} else if (self->sink != NULL) {
+			gst_element_set_state(GST_ELEMENT(self->sink),
+					GST_STATE_NULL);
+			g_object_unref(G_OBJECT(self->sink));
+		}
+
+		self->sink = NULL;
+
+		gst_a2dp_sink_remove_dynamic_elements(self);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS(klass);
+	GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	object_class->set_property = GST_DEBUG_FUNCPTR(
+					gst_a2dp_sink_set_property);
+	object_class->get_property = GST_DEBUG_FUNCPTR(
+					gst_a2dp_sink_get_property);
+
+	object_class->finalize = GST_DEBUG_FUNCPTR(
+					gst_a2dp_sink_finalize);
+
+	element_class->change_state = GST_DEBUG_FUNCPTR(
+					gst_a2dp_sink_change_state);
+
+	g_object_class_install_property(object_class, PROP_DEVICE,
+			g_param_spec_string("device", "Device",
+			"Bluetooth remote device address",
+			NULL, G_PARAM_READWRITE));
+
+	g_object_class_install_property(object_class, PROP_AUTOCONNECT,
+			g_param_spec_boolean("auto-connect", "Auto-connect",
+			"Automatically attempt to connect to device",
+			DEFAULT_AUTOCONNECT, G_PARAM_READWRITE));
+
+	GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0,
+				"A2DP sink element");
+}
+
+GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self)
+{
+	return gst_avdtp_sink_get_device_caps(self->sink);
+}
+
+static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad)
+{
+	GstCaps *caps;
+	GstCaps *caps_aux;
+	GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+
+	if (self->sink == NULL) {
+		GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized "
+			"returning template caps");
+		caps = gst_static_pad_template_get_caps(
+				&gst_a2dp_sink_factory);
+	} else {
+		GST_LOG_OBJECT(self, "Getting device caps");
+		caps = gst_a2dp_sink_get_device_caps(self);
+		if (caps == NULL)
+			caps = gst_static_pad_template_get_caps(
+					&gst_a2dp_sink_factory);
+	}
+	caps_aux = gst_caps_copy(caps);
+	g_object_set(self->capsfilter, "caps", caps_aux, NULL);
+	gst_caps_unref(caps_aux);
+	return caps;
+}
+
+static gboolean gst_a2dp_sink_init_avdtp_sink(GstA2dpSink *self)
+{
+	GstElement *sink;
+
+	/* check if we don't need a new sink */
+	if (self->sink_is_in_bin)
+		return TRUE;
+
+	if (self->sink == NULL)
+		sink = gst_element_factory_make("avdtpsink", "avdtpsink");
+	else
+		sink = GST_ELEMENT(self->sink);
+
+	if (sink == NULL) {
+		GST_ERROR_OBJECT(self, "Couldn't create avdtpsink");
+		return FALSE;
+	}
+
+	if (!gst_bin_add(GST_BIN(self), sink)) {
+		GST_ERROR_OBJECT(self, "failed to add avdtpsink "
+			"to the bin");
+		goto cleanup_and_fail;
+	}
+
+	if (gst_element_set_state(sink, GST_STATE_READY) ==
+			GST_STATE_CHANGE_FAILURE) {
+		GST_ERROR_OBJECT(self, "avdtpsink failed to go to ready");
+		goto remove_element_and_fail;
+	}
+
+	if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) {
+		GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay "
+			"to avdtpsink");
+		goto remove_element_and_fail;
+	}
+
+	self->sink = GST_AVDTP_SINK(sink);
+	self->sink_is_in_bin = TRUE;
+	g_object_set(G_OBJECT(self->sink), "device", self->device, NULL);
+
+	gst_element_set_state(sink, GST_STATE_PAUSED);
+
+	return TRUE;
+
+remove_element_and_fail:
+	gst_element_set_state(sink, GST_STATE_NULL);
+	gst_bin_remove(GST_BIN(self), sink);
+	return FALSE;
+
+cleanup_and_fail:
+	if (sink != NULL)
+		g_object_unref(G_OBJECT(sink));
+
+	return FALSE;
+}
+
+static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self)
+{
+	GstElement *rtppay;
+
+	/* if we already have a rtp, we don't need a new one */
+	if (self->rtp != NULL)
+		return TRUE;
+
+	rtppay = gst_a2dp_sink_init_element(self, "rtpsbcpay", "rtp",
+						self->capsfilter);
+	if (rtppay == NULL)
+		return FALSE;
+
+	self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
+	g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL);
+
+	gst_element_set_state(rtppay, GST_STATE_PAUSED);
+
+	return TRUE;
+}
+
+static gboolean gst_a2dp_sink_init_rtp_mpeg_element(GstA2dpSink *self)
+{
+	GstElement *rtppay;
+
+	/* check if we don't need a new rtp */
+	if (self->rtp)
+		return TRUE;
+
+	GST_LOG_OBJECT(self, "Initializing rtp mpeg element");
+	/* if capsfilter is not created then we can't have our rtp element */
+	if (self->capsfilter == NULL)
+		return FALSE;
+
+	rtppay = gst_a2dp_sink_init_element(self, "rtpmpapay", "rtp",
+					self->capsfilter);
+	if (rtppay == NULL)
+		return FALSE;
+
+	self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
+
+	gst_element_set_state(rtppay, GST_STATE_PAUSED);
+
+	return TRUE;
+}
+
+static gboolean gst_a2dp_sink_init_dynamic_elements(GstA2dpSink *self,
+						GstCaps *caps)
+{
+	GstStructure *structure;
+	GstEvent *event;
+	GstPad *capsfilterpad;
+	gboolean crc;
+	gchar *mode = NULL;
+
+	structure = gst_caps_get_structure(caps, 0);
+
+	/* before everything we need to remove fakesink */
+	gst_a2dp_sink_remove_fakesink(self);
+
+	/* first, we need to create our rtp payloader */
+	if (gst_structure_has_name(structure, "audio/x-sbc")) {
+		GST_LOG_OBJECT(self, "sbc media received");
+		if (!gst_a2dp_sink_init_rtp_sbc_element(self))
+			return FALSE;
+	} else if (gst_structure_has_name(structure, "audio/mpeg")) {
+		GST_LOG_OBJECT(self, "mp3 media received");
+		if (!gst_a2dp_sink_init_rtp_mpeg_element(self))
+			return FALSE;
+	} else {
+		GST_ERROR_OBJECT(self, "Unexpected media type");
+		return FALSE;
+	}
+
+	if (!gst_a2dp_sink_init_avdtp_sink(self))
+		return FALSE;
+
+	/* check if we should push the taglist FIXME should we push this?
+	 * we can send the tags directly if needed */
+	if (self->taglist != NULL &&
+			gst_structure_has_name(structure, "audio/mpeg")) {
+
+		event = gst_event_new_tag(self->taglist);
+
+		/* send directly the crc */
+		if (gst_tag_list_get_boolean(self->taglist, "has-crc", &crc))
+			gst_avdtp_sink_set_crc(self->sink, crc);
+
+		if (gst_tag_list_get_string(self->taglist, "channel-mode",
+				&mode))
+			gst_avdtp_sink_set_channel_mode(self->sink, mode);
+
+		capsfilterpad = gst_ghost_pad_get_target(self->ghostpad);
+		gst_pad_send_event(capsfilterpad, event);
+		self->taglist = NULL;
+		g_free(mode);
+	}
+
+	if (!gst_avdtp_sink_set_device_caps(self->sink, caps))
+		return FALSE;
+
+	g_object_set(G_OBJECT(self->rtp), "mtu",
+		gst_avdtp_sink_get_link_mtu(self->sink), NULL);
+
+	/* we forward our new segment here if we have one */
+	if (self->newseg_event) {
+		gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp),
+					self->newseg_event);
+		self->newseg_event = NULL;
+	}
+
+	return TRUE;
+}
+
+static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)
+{
+	GstA2dpSink *self;
+
+	self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+	GST_INFO_OBJECT(self, "setting caps");
+
+	/* now we know the caps */
+	gst_a2dp_sink_init_dynamic_elements(self, caps);
+
+	return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps);
+}
+
+/* used for catching newsegment events while we don't have a sink, for
+ * later forwarding it to the sink */
+static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event)
+{
+	GstA2dpSink *self;
+	GstTagList *taglist = NULL;
+	GstObject *parent;
+
+	self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+	parent = gst_element_get_parent(GST_ELEMENT(self->sink));
+
+	if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT &&
+			parent != GST_OBJECT_CAST(self)) {
+		if (self->newseg_event != NULL)
+			gst_event_unref(self->newseg_event);
+		self->newseg_event = gst_event_ref(event);
+
+	} else if (GST_EVENT_TYPE(event) == GST_EVENT_TAG &&
+			parent != GST_OBJECT_CAST(self)) {
+		if (self->taglist == NULL)
+			gst_event_parse_tag(event, &self->taglist);
+		else {
+			gst_event_parse_tag(event, &taglist);
+			gst_tag_list_insert(self->taglist, taglist,
+					GST_TAG_MERGE_REPLACE);
+		}
+	}
+
+	if (parent != NULL)
+		gst_object_unref(GST_OBJECT(parent));
+
+	return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event);
+}
+
+static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self)
+{
+	GstElement *element;
+
+	element = gst_element_factory_make("capsfilter", "filter");
+	if (element == NULL)
+		goto failed;
+
+	if (!gst_bin_add(GST_BIN(self), element))
+		goto failed;
+
+	self->capsfilter = element;
+	return TRUE;
+
+failed:
+	GST_ERROR_OBJECT(self, "Failed to initialize caps filter");
+	return FALSE;
+}
+
+static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self)
+{
+	if (self->fakesink != NULL)
+		return TRUE;
+
+	g_mutex_lock(self->cb_mutex);
+	self->fakesink = gst_a2dp_sink_init_element(self, "fakesink",
+			"fakesink", self->capsfilter);
+	g_mutex_unlock(self->cb_mutex);
+
+	if (!self->fakesink)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self)
+{
+	g_mutex_lock(self->cb_mutex);
+
+	if (self->fakesink != NULL) {
+		gst_element_set_locked_state(self->fakesink, TRUE);
+		gst_element_set_state(self->fakesink, GST_STATE_NULL);
+
+		gst_bin_remove(GST_BIN(self), self->fakesink);
+		self->fakesink = NULL;
+	}
+
+	g_mutex_unlock(self->cb_mutex);
+
+	return TRUE;
+}
+
+static void gst_a2dp_sink_init(GstA2dpSink *self,
+			GstA2dpSinkClass *klass)
+{
+	self->sink = NULL;
+	self->fakesink = NULL;
+	self->rtp = NULL;
+	self->device = NULL;
+	self->autoconnect = DEFAULT_AUTOCONNECT;
+	self->capsfilter = NULL;
+	self->newseg_event = NULL;
+	self->taglist = NULL;
+	self->ghostpad = NULL;
+	self->sink_is_in_bin = FALSE;
+
+	self->cb_mutex = g_mutex_new();
+
+	/* we initialize our capsfilter */
+	gst_a2dp_sink_init_caps_filter(self);
+	g_object_set(self->capsfilter, "caps",
+		gst_static_pad_template_get_caps(&gst_a2dp_sink_factory),
+		NULL);
+
+	gst_a2dp_sink_init_fakesink(self);
+
+	gst_a2dp_sink_init_ghost_pad(self);
+
+}
+
+gboolean gst_a2dp_sink_plugin_init(GstPlugin *plugin)
+{
+	return gst_element_register(plugin, "a2dpsink",
+			GST_RANK_MARGINAL, GST_TYPE_A2DP_SINK);
+}
+
diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h
new file mode 100644
index 0000000..902b678
--- /dev/null
+++ b/audio/gsta2dpsink.h
@@ -0,0 +1,84 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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;
+	gboolean autoconnect;
+	gboolean sink_is_in_bin;
+
+	GstGhostPad *ghostpad;
+	GstPadSetCapsFunction ghostpad_setcapsfunc;
+	GstPadEventFunction ghostpad_eventfunc;
+
+	GstEvent *newseg_event;
+	/* Store the tags received before the a2dpsender sink is created
+	 * when it is created we forward this to it */
+	GstTagList *taglist;
+
+	GMutex *cb_mutex;
+};
+
+struct _GstA2dpSinkClass {
+	GstBinClass parent_class;
+};
+
+//GType gst_a2dp_sink_get_type(void);
+
+gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin);
+
+GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self);
+
+G_END_DECLS
+
+#endif
+
diff --git a/audio/gstavdtpsink.c b/audio/gstavdtpsink.c
new file mode 100644
index 0000000..bc25bd1
--- /dev/null
+++ b/audio/gstavdtpsink.c
@@ -0,0 +1,1434 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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 "ipc.h"
+#include "rtp.h"
+
+#include "gstavdtpsink.h"
+
+GST_DEBUG_CATEGORY_STATIC(avdtp_sink_debug);
+#define GST_CAT_DEFAULT avdtp_sink_debug
+
+#define BUFFER_SIZE 2048
+#define TEMPLATE_MAX_BITPOOL 64
+#define CRC_PROTECTED 1
+#define CRC_UNPROTECTED 0
+
+#define DEFAULT_AUTOCONNECT TRUE
+
+#define GST_AVDTP_SINK_MUTEX_LOCK(s) G_STMT_START {	\
+		g_mutex_lock(s->sink_lock);		\
+	} G_STMT_END
+
+#define GST_AVDTP_SINK_MUTEX_UNLOCK(s) G_STMT_START {	\
+		g_mutex_unlock(s->sink_lock);		\
+	} G_STMT_END
+
+
+struct bluetooth_data {
+	struct bt_get_capabilities_rsp *caps; /* Bluetooth device caps */
+	guint link_mtu;
+
+	gchar buffer[BUFFER_SIZE];	/* Codec transfer buffer */
+};
+
+#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0)
+#define IS_MPEG_AUDIO(n) (strcmp((n), "audio/mpeg") == 0)
+
+enum {
+	PROP_0,
+	PROP_DEVICE,
+	PROP_AUTOCONNECT
+};
+
+GST_BOILERPLATE(GstAvdtpSink, gst_avdtp_sink, GstBaseSink,
+			GST_TYPE_BASE_SINK);
+
+static const GstElementDetails avdtp_sink_details =
+	GST_ELEMENT_DETAILS("Bluetooth AVDTP sink",
+				"Sink/Audio",
+				"Plays audio to an A2DP device",
+				"Marcel Holtmann <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 GIOError gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
+					const bt_audio_msg_header_t *msg);
+static GIOError gst_avdtp_sink_audioservice_expect(
+				GstAvdtpSink *self,
+				bt_audio_msg_header_t *outmsg,
+				guint8 expected_name);
+
+
+static void gst_avdtp_sink_base_init(gpointer g_class)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&avdtp_sink_factory));
+
+	gst_element_class_set_details(element_class, &avdtp_sink_details);
+}
+
+static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+	GST_INFO_OBJECT(self, "stop");
+
+	if (self->watch_id != 0) {
+		g_source_remove(self->watch_id);
+		self->watch_id = 0;
+	}
+
+	if (self->server) {
+		bt_audio_service_close(g_io_channel_unix_get_fd(self->server));
+		g_io_channel_unref(self->server);
+		self->server = NULL;
+	}
+
+	if (self->stream) {
+		g_io_channel_shutdown(self->stream, TRUE, NULL);
+		g_io_channel_unref(self->stream);
+		self->stream = NULL;
+	}
+
+	if (self->data) {
+		g_free(self->data);
+		self->data = NULL;
+	}
+
+	if (self->stream_caps) {
+		gst_caps_unref(self->stream_caps);
+		self->stream_caps = NULL;
+	}
+
+	if (self->dev_caps) {
+		gst_caps_unref(self->dev_caps);
+		self->dev_caps = NULL;
+	}
+
+	return TRUE;
+}
+
+static void gst_avdtp_sink_finalize(GObject *object)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(object);
+
+	if (self->data)
+		gst_avdtp_sink_stop(GST_BASE_SINK(self));
+
+	if (self->device)
+		g_free(self->device);
+
+	g_mutex_free(self->sink_lock);
+
+	G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+static void gst_avdtp_sink_set_property(GObject *object, guint prop_id,
+					const GValue *value, GParamSpec *pspec)
+{
+	GstAvdtpSink *sink = GST_AVDTP_SINK(object);
+
+	switch (prop_id) {
+	case PROP_DEVICE:
+		if (sink->device)
+			g_free(sink->device);
+		sink->device = g_value_dup_string(value);
+		break;
+
+	case PROP_AUTOCONNECT:
+		sink->autoconnect = g_value_get_boolean(value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static void gst_avdtp_sink_get_property(GObject *object, guint prop_id,
+					GValue *value, GParamSpec *pspec)
+{
+	GstAvdtpSink *sink = GST_AVDTP_SINK(object);
+
+	switch (prop_id) {
+	case PROP_DEVICE:
+		g_value_set_string(value, sink->device);
+		break;
+
+	case PROP_AUTOCONNECT:
+		g_value_set_boolean(value, sink->autoconnect);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static gint gst_avdtp_sink_bluetooth_recvmsg_fd(GstAvdtpSink *sink)
+{
+	int err, ret;
+
+	ret = bt_audio_service_get_data_fd(
+			g_io_channel_unix_get_fd(sink->server));
+
+	if (ret < 0) {
+		err = errno;
+		GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)",
+				strerror(err), err);
+		return -err;
+	}
+
+	sink->stream = g_io_channel_unix_new(ret);
+	GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret);
+
+	return 0;
+}
+
+static codec_capabilities_t *gst_avdtp_find_caps(GstAvdtpSink *sink,
+						uint8_t codec_type)
+{
+	struct bt_get_capabilities_rsp *rsp = sink->data->caps;
+	codec_capabilities_t *codec = (void *) rsp->data;
+	int bytes_left = rsp->h.length - sizeof(*rsp);
+
+	while (bytes_left > 0) {
+		if ((codec->type == codec_type) &&
+				!(codec->lock & BT_WRITE_LOCK))
+			break;
+
+		bytes_left -= codec->length;
+		codec = (void *) codec + codec->length;
+	}
+
+	if (bytes_left <= 0)
+		return NULL;
+
+	return codec;
+}
+
+static gboolean gst_avdtp_sink_init_sbc_pkt_conf(GstAvdtpSink *sink,
+					GstCaps *caps,
+					sbc_capabilities_t *pkt)
+{
+	sbc_capabilities_t *cfg;
+	const GValue *value = NULL;
+	const char *pref, *name;
+	gint rate, subbands, blocks;
+	GstStructure *structure = gst_caps_get_structure(caps, 0);
+
+	cfg = (void *) gst_avdtp_find_caps(sink, BT_A2DP_SBC_SINK);
+	name = gst_structure_get_name(structure);
+
+	if (!(IS_SBC(name))) {
+		GST_ERROR_OBJECT(sink, "Unexpected format %s, "
+				"was expecting sbc", name);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "rate");
+	rate = g_value_get_int(value);
+	if (rate == 44100)
+		cfg->frequency = BT_SBC_SAMPLING_FREQ_44100;
+	else if (rate == 48000)
+		cfg->frequency = BT_SBC_SAMPLING_FREQ_48000;
+	else if (rate == 32000)
+		cfg->frequency = BT_SBC_SAMPLING_FREQ_32000;
+	else if (rate == 16000)
+		cfg->frequency = BT_SBC_SAMPLING_FREQ_16000;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid rate while setting caps");
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "mode");
+	pref = g_value_get_string(value);
+	if (strcmp(pref, "mono") == 0)
+		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+	else if (strcmp(pref, "dual") == 0)
+		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+	else if (strcmp(pref, "stereo") == 0)
+		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+	else if (strcmp(pref, "joint") == 0)
+		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid mode %s", pref);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "allocation");
+	pref = g_value_get_string(value);
+	if (strcmp(pref, "loudness") == 0)
+		cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+	else if (strcmp(pref, "snr") == 0)
+		cfg->allocation_method = BT_A2DP_ALLOCATION_SNR;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "subbands");
+	subbands = g_value_get_int(value);
+	if (subbands == 8)
+		cfg->subbands = BT_A2DP_SUBBANDS_8;
+	else if (subbands == 4)
+		cfg->subbands = BT_A2DP_SUBBANDS_4;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "blocks");
+	blocks = g_value_get_int(value);
+	if (blocks == 16)
+		cfg->block_length = BT_A2DP_BLOCK_LENGTH_16;
+	else if (blocks == 12)
+		cfg->block_length = BT_A2DP_BLOCK_LENGTH_12;
+	else if (blocks == 8)
+		cfg->block_length = BT_A2DP_BLOCK_LENGTH_8;
+	else if (blocks == 4)
+		cfg->block_length = BT_A2DP_BLOCK_LENGTH_4;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "bitpool");
+	cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value);
+
+	memcpy(pkt, cfg, sizeof(*pkt));
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_conf_recv_stream_fd(
+					GstAvdtpSink *self)
+{
+	struct bluetooth_data *data = self->data;
+	gint ret;
+	GIOError err;
+	GError *gerr = NULL;
+	GIOStatus status;
+	GIOFlags flags;
+	gsize read;
+
+	ret = gst_avdtp_sink_bluetooth_recvmsg_fd(self);
+	if (ret < 0)
+		return FALSE;
+
+	if (!self->stream) {
+		GST_ERROR_OBJECT(self, "Error while configuring device: "
+				"could not acquire audio socket");
+		return FALSE;
+	}
+
+	/* set stream socket to nonblock */
+	GST_LOG_OBJECT(self, "setting stream socket to nonblock");
+	flags = g_io_channel_get_flags(self->stream);
+	flags |= G_IO_FLAG_NONBLOCK;
+	status = g_io_channel_set_flags(self->stream, flags, &gerr);
+	if (status != G_IO_STATUS_NORMAL) {
+		if (gerr)
+			GST_WARNING_OBJECT(self, "Error while "
+				"setting server socket to nonblock: "
+				"%s", gerr->message);
+		else
+			GST_WARNING_OBJECT(self, "Error while "
+					"setting server "
+					"socket to nonblock");
+	}
+
+	/* It is possible there is some outstanding
+	data in the pipe - we have to empty it */
+	GST_LOG_OBJECT(self, "emptying stream pipe");
+	while (1) {
+		err = g_io_channel_read(self->stream, data->buffer,
+					(gsize) data->link_mtu,
+					&read);
+		if (err != G_IO_ERROR_NONE || read <= 0)
+			break;
+	}
+
+	/* set stream socket to block */
+	GST_LOG_OBJECT(self, "setting stream socket to block");
+	flags = g_io_channel_get_flags(self->stream);
+	flags &= ~G_IO_FLAG_NONBLOCK;
+	status = g_io_channel_set_flags(self->stream, flags, &gerr);
+	if (status != G_IO_STATUS_NORMAL) {
+		if (gerr)
+			GST_WARNING_OBJECT(self, "Error while "
+				"setting server socket to block:"
+				"%s", gerr->message);
+		else
+			GST_WARNING_OBJECT(self, "Error while "
+				"setting server "
+				"socket to block");
+	}
+
+	memset(data->buffer, 0, sizeof(data->buffer));
+
+	return TRUE;
+}
+
+static gboolean server_callback(GIOChannel *chan,
+					GIOCondition cond, gpointer data)
+{
+	if (cond & G_IO_HUP || cond & G_IO_NVAL)
+		return FALSE;
+	else if (cond & G_IO_ERR)
+		GST_WARNING_OBJECT(GST_AVDTP_SINK(data),
+					"Untreated callback G_IO_ERR");
+
+	return TRUE;
+}
+
+static GstStructure *gst_avdtp_sink_parse_sbc_caps(
+			GstAvdtpSink *self, sbc_capabilities_t *sbc)
+{
+	GstStructure *structure;
+	GValue *value;
+	GValue *list;
+	gboolean mono, stereo;
+
+	structure = gst_structure_empty_new("audio/x-sbc");
+	value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING);
+
+	/* mode */
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
+		g_value_set_static_string(value, "mono");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) {
+		g_value_set_static_string(value, "stereo");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) {
+		g_value_set_static_string(value, "dual");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) {
+		g_value_set_static_string(value, "joint");
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "mode", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* subbands */
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	value = g_value_init(value, G_TYPE_INT);
+	if (sbc->subbands & BT_A2DP_SUBBANDS_4) {
+		g_value_set_int(value, 4);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->subbands & BT_A2DP_SUBBANDS_8) {
+		g_value_set_int(value, 8);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "subbands", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* blocks */
+	value = g_value_init(value, G_TYPE_INT);
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) {
+		g_value_set_int(value, 16);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) {
+		g_value_set_int(value, 12);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) {
+		g_value_set_int(value, 8);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) {
+		g_value_set_int(value, 4);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "blocks", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* allocation */
+	g_value_init(value, G_TYPE_STRING);
+	list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST);
+	if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) {
+		g_value_set_static_string(value, "loudness");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) {
+		g_value_set_static_string(value, "snr");
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "allocation", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* rate */
+	g_value_init(value, G_TYPE_INT);
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_48000) {
+		g_value_set_int(value, 48000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) {
+		g_value_set_int(value, 44100);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) {
+		g_value_set_int(value, 32000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_16000) {
+		g_value_set_int(value, 16000);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "rate", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* bitpool */
+	value = g_value_init(value, GST_TYPE_INT_RANGE);
+	gst_value_set_int_range(value,
+			MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL),
+			MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL));
+	gst_structure_set_value(structure, "bitpool", value);
+	g_value_unset(value);
+
+	/* channels */
+	mono = FALSE;
+	stereo = FALSE;
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+		mono = TRUE;
+	if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+			(sbc->channel_mode &
+			BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+			(sbc->channel_mode &
+			BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+		stereo = TRUE;
+
+	if (mono && stereo) {
+		g_value_init(value, GST_TYPE_INT_RANGE);
+		gst_value_set_int_range(value, 1, 2);
+	} else {
+		g_value_init(value, G_TYPE_INT);
+		if (mono)
+			g_value_set_int(value, 1);
+		else if (stereo)
+			g_value_set_int(value, 2);
+		else {
+			GST_ERROR_OBJECT(self,
+				"Unexpected number of channels");
+			g_value_set_int(value, 0);
+		}
+	}
+
+	gst_structure_set_value(structure, "channels", value);
+	g_free(value);
+
+	return structure;
+}
+
+static GstStructure *gst_avdtp_sink_parse_mpeg_caps(
+			GstAvdtpSink *self, mpeg_capabilities_t *mpeg)
+{
+	GstStructure *structure;
+	GValue *value;
+	GValue *list;
+	gboolean valid_layer = FALSE;
+	gboolean mono, stereo;
+
+	if (!mpeg)
+		return NULL;
+
+	GST_LOG_OBJECT(self, "parsing mpeg caps");
+
+	structure = gst_structure_empty_new("audio/mpeg");
+	value = g_new0(GValue, 1);
+	g_value_init(value, G_TYPE_INT);
+
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	g_value_set_int(value, 1);
+	gst_value_list_prepend_value(list, value);
+	g_value_set_int(value, 2);
+	gst_value_list_prepend_value(list, value);
+	gst_structure_set_value(structure, "mpegversion", list);
+	g_free(list);
+
+	/* layer */
+	GST_LOG_OBJECT(self, "setting mpeg layer");
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (mpeg->layer & BT_MPEG_LAYER_1) {
+		g_value_set_int(value, 1);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (mpeg->layer & BT_MPEG_LAYER_2) {
+		g_value_set_int(value, 2);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (mpeg->layer & BT_MPEG_LAYER_3) {
+		g_value_set_int(value, 3);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (list) {
+		gst_structure_set_value(structure, "layer", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	if (!valid_layer) {
+		gst_structure_free(structure);
+		g_free(value);
+		return NULL;
+	}
+
+	/* rate */
+	GST_LOG_OBJECT(self, "setting mpeg rate");
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) {
+		g_value_set_int(value, 48000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) {
+		g_value_set_int(value, 44100);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) {
+		g_value_set_int(value, 32000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) {
+		g_value_set_int(value, 24000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) {
+		g_value_set_int(value, 22050);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_16000) {
+		g_value_set_int(value, 16000);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "rate", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* channels */
+	GST_LOG_OBJECT(self, "setting mpeg channels");
+	mono = FALSE;
+	stereo = FALSE;
+	if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+		mono = TRUE;
+	if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+			(mpeg->channel_mode &
+			BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+			(mpeg->channel_mode &
+			BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+		stereo = TRUE;
+
+	if (mono && stereo) {
+		g_value_init(value, GST_TYPE_INT_RANGE);
+		gst_value_set_int_range(value, 1, 2);
+	} else {
+		g_value_init(value, G_TYPE_INT);
+		if (mono)
+			g_value_set_int(value, 1);
+		else if (stereo)
+			g_value_set_int(value, 2);
+		else {
+			GST_ERROR_OBJECT(self,
+				"Unexpected number of channels");
+			g_value_set_int(value, 0);
+		}
+	}
+	gst_structure_set_value(structure, "channels", value);
+	g_free(value);
+
+	return structure;
+}
+
+static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self)
+{
+	sbc_capabilities_t *sbc;
+	mpeg_capabilities_t *mpeg;
+	GstStructure *sbc_structure;
+	GstStructure *mpeg_structure;
+	gchar *tmp;
+
+	GST_LOG_OBJECT(self, "updating device caps");
+
+	sbc = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK);
+	mpeg = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK);
+
+	sbc_structure = gst_avdtp_sink_parse_sbc_caps(self, sbc);
+	mpeg_structure = gst_avdtp_sink_parse_mpeg_caps(self, mpeg);
+
+	if (self->dev_caps != NULL)
+		gst_caps_unref(self->dev_caps);
+	self->dev_caps = gst_caps_new_full(sbc_structure, NULL);
+	if (mpeg_structure != NULL)
+		gst_caps_append_structure(self->dev_caps, mpeg_structure);
+
+	tmp = gst_caps_to_string(self->dev_caps);
+	GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp);
+	g_free(tmp);
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_get_capabilities(GstAvdtpSink *self)
+{
+	gchar *buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_get_capabilities_req *req = (void *) buf;
+	struct bt_get_capabilities_rsp *rsp = (void *) buf;
+	GIOError io_error;
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_GET_CAPABILITIES;
+	req->h.length = sizeof(*req);
+
+	if (self->device == NULL)
+		return FALSE;
+	strncpy(req->destination, self->device, 18);
+	if (self->autoconnect)
+		req->flags |= BT_FLAG_AUTOCONNECT;
+
+	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while asking device caps");
+		return FALSE;
+	}
+
+	rsp->h.length = 0;
+	io_error = gst_avdtp_sink_audioservice_expect(self,
+			&rsp->h, BT_GET_CAPABILITIES);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while getting device caps");
+		return FALSE;
+	}
+
+	self->data->caps = g_malloc0(rsp->h.length);
+	memcpy(self->data->caps, rsp, rsp->h.length);
+	if (!gst_avdtp_sink_update_caps(self)) {
+		GST_WARNING_OBJECT(self, "failed to update capabilities");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gint gst_avdtp_sink_get_channel_mode(const gchar *mode)
+{
+	if (strcmp(mode, "stereo") == 0)
+		return BT_A2DP_CHANNEL_MODE_STEREO;
+	else if (strcmp(mode, "joint-stereo") == 0)
+		return BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+	else if (strcmp(mode, "dual-channel") == 0)
+		return BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+	else if (strcmp(mode, "mono") == 0)
+		return BT_A2DP_CHANNEL_MODE_MONO;
+	else
+		return -1;
+}
+
+static void gst_avdtp_sink_tag(const GstTagList *taglist,
+			const gchar *tag, gpointer user_data)
+{
+	gboolean crc;
+	gchar *channel_mode = NULL;
+	GstAvdtpSink *self = GST_AVDTP_SINK(user_data);
+
+	if (strcmp(tag, "has-crc") == 0) {
+
+		if (!gst_tag_list_get_boolean(taglist, tag, &crc)) {
+			GST_WARNING_OBJECT(self, "failed to get crc tag");
+			return;
+		}
+
+		gst_avdtp_sink_set_crc(self, crc);
+
+	} else if (strcmp(tag, "channel-mode") == 0) {
+
+		if (!gst_tag_list_get_string(taglist, tag, &channel_mode)) {
+			GST_WARNING_OBJECT(self,
+				"failed to get channel-mode tag");
+			return;
+		}
+
+		self->channel_mode = gst_avdtp_sink_get_channel_mode(
+					channel_mode);
+		if (self->channel_mode == -1)
+			GST_WARNING_OBJECT(self, "Received invalid channel "
+					"mode: %s", channel_mode);
+		g_free(channel_mode);
+
+	} else
+		GST_DEBUG_OBJECT(self, "received unused tag: %s", tag);
+}
+
+static gboolean gst_avdtp_sink_event(GstBaseSink *basesink,
+			GstEvent *event)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+	GstTagList *taglist = NULL;
+
+	if (GST_EVENT_TYPE(event) == GST_EVENT_TAG) {
+		/* we check the tags, mp3 has tags that are importants and
+		 * are outside caps */
+		gst_event_parse_tag(event, &taglist);
+		gst_tag_list_foreach(taglist, gst_avdtp_sink_tag, self);
+	}
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+	gint sk;
+	gint err;
+
+	GST_INFO_OBJECT(self, "start");
+
+	self->watch_id = 0;
+
+	sk = bt_audio_service_open();
+	if (sk <= 0) {
+		err = errno;
+		GST_ERROR_OBJECT(self, "Cannot open connection to bt "
+			"audio service: %s %d", strerror(err), err);
+		goto failed;
+	}
+
+	self->server = g_io_channel_unix_new(sk);
+	self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR |
+					G_IO_NVAL, server_callback, self);
+
+	self->data = g_new0(struct bluetooth_data, 1);
+
+	self->stream = NULL;
+	self->stream_caps = NULL;
+	self->mp3_using_crc = -1;
+	self->channel_mode = -1;
+
+	if (!gst_avdtp_sink_get_capabilities(self)) {
+		GST_ERROR_OBJECT(self, "failed to get capabilities "
+				"from device");
+		goto failed;
+	}
+
+	return TRUE;
+
+failed:
+	bt_audio_service_close(sk);
+	return FALSE;
+}
+
+static gboolean gst_avdtp_sink_stream_start(GstAvdtpSink *self)
+{
+	gchar buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_start_stream_req *req = (void *) buf;
+	struct bt_start_stream_rsp *rsp = (void *) buf;
+	struct bt_new_stream_ind *ind = (void *) buf;
+	GIOError io_error;
+
+	memset(req, 0, sizeof(buf));
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_START_STREAM;
+	req->h.length = sizeof(*req);
+
+	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error ocurred while sending "
+					"start packet");
+		return FALSE;
+	}
+
+	rsp->h.length = sizeof(*rsp);
+	io_error = gst_avdtp_sink_audioservice_expect(self,
+			&rsp->h, BT_START_STREAM);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while stream "
+			"start confirmation");
+		return FALSE;
+	}
+
+	ind->h.length = sizeof(*ind);
+	io_error = gst_avdtp_sink_audioservice_expect(self, &ind->h,
+			BT_NEW_STREAM);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while receiving "
+			"stream filedescriptor");
+		return FALSE;
+	}
+
+	if (!gst_avdtp_sink_conf_recv_stream_fd(self))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_init_mp3_pkt_conf(
+		GstAvdtpSink *self, GstCaps *caps,
+		mpeg_capabilities_t *pkt)
+{
+	const GValue *value = NULL;
+	gint rate, layer;
+	const gchar *name;
+	GstStructure *structure = gst_caps_get_structure(caps, 0);
+
+	name = gst_structure_get_name(structure);
+
+	if (!(IS_MPEG_AUDIO(name))) {
+		GST_ERROR_OBJECT(self, "Unexpected format %s, "
+				"was expecting mp3", name);
+		return FALSE;
+	}
+
+	/* layer */
+	value = gst_structure_get_value(structure, "layer");
+	layer = g_value_get_int(value);
+	if (layer == 1)
+		pkt->layer = BT_MPEG_LAYER_1;
+	else if (layer == 2)
+		pkt->layer = BT_MPEG_LAYER_2;
+	else if (layer == 3)
+		pkt->layer = BT_MPEG_LAYER_3;
+	else {
+		GST_ERROR_OBJECT(self, "Unexpected layer: %d", layer);
+		return FALSE;
+	}
+
+	/* crc */
+	if (self->mp3_using_crc != -1)
+		pkt->crc = self->mp3_using_crc;
+	else {
+		GST_ERROR_OBJECT(self, "No info about crc was received, "
+				" can't proceed");
+		return FALSE;
+	}
+
+	/* channel mode */
+	if (self->channel_mode != -1)
+		pkt->channel_mode = self->channel_mode;
+	else {
+		GST_ERROR_OBJECT(self, "No info about channel mode "
+				"received, can't proceed");
+		return FALSE;
+	}
+
+	/* mpf - we will only use the mandatory one */
+	pkt->mpf = 0;
+
+	value = gst_structure_get_value(structure, "rate");
+	rate = g_value_get_int(value);
+	if (rate == 44100)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_44100;
+	else if (rate == 48000)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_48000;
+	else if (rate == 32000)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_32000;
+	else if (rate == 24000)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_24000;
+	else if (rate == 22050)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_22050;
+	else if (rate == 16000)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_16000;
+	else {
+		GST_ERROR_OBJECT(self, "Invalid rate while setting caps");
+		return FALSE;
+	}
+
+	/* vbr - we always say its vbr, we don't have how to know it */
+	pkt->bitrate = 0x8000;
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_configure(GstAvdtpSink *self,
+			GstCaps *caps)
+{
+	gchar buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_open_req *open_req = (void *) buf;
+	struct bt_open_rsp *open_rsp = (void *) buf;
+	struct bt_set_configuration_req *req = (void *) buf;
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+	gboolean ret;
+	GIOError io_error;
+	gchar *temp;
+	GstStructure *structure;
+	codec_capabilities_t *codec = NULL;
+
+	temp = gst_caps_to_string(caps);
+	GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp);
+	g_free(temp);
+
+	structure = gst_caps_get_structure(caps, 0);
+
+	if (gst_structure_has_name(structure, "audio/x-sbc"))
+		codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK);
+	else if (gst_structure_has_name(structure, "audio/mpeg"))
+		codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK);
+
+	if (codec == NULL) {
+		GST_ERROR_OBJECT(self, "Couldn't parse caps "
+				"to packet configuration");
+		return FALSE;
+	}
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	open_req->h.type = BT_REQUEST;
+	open_req->h.name = BT_OPEN;
+	open_req->h.length = sizeof(*open_req);
+
+	strncpy(open_req->destination, self->device, 18);
+	open_req->seid = codec->seid;
+	open_req->lock = BT_WRITE_LOCK;
+
+	io_error = gst_avdtp_sink_audioservice_send(self, &open_req->h);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error ocurred while sending "
+					"open packet");
+		return FALSE;
+	}
+
+	open_rsp->h.length = sizeof(*open_rsp);
+	io_error = gst_avdtp_sink_audioservice_expect(self,
+			&open_rsp->h, BT_OPEN);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while receiving device "
+					"confirmation");
+		return FALSE;
+	}
+
+	memset(req, 0, sizeof(buf));
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_SET_CONFIGURATION;
+	req->h.length = sizeof(*req);
+
+	if (codec->type == BT_A2DP_SBC_SINK)
+		ret = gst_avdtp_sink_init_sbc_pkt_conf(self, caps,
+				(void *) &req->codec);
+	else
+		ret = gst_avdtp_sink_init_mp3_pkt_conf(self, caps,
+				(void *) &req->codec);
+
+	if (!ret) {
+		GST_ERROR_OBJECT(self, "Couldn't parse caps "
+				"to packet configuration");
+		return FALSE;
+	}
+
+	req->h.length += req->codec.length - sizeof(req->codec);
+	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error ocurred while sending "
+					"configurarion packet");
+		return FALSE;
+	}
+
+	rsp->h.length = sizeof(*rsp);
+	io_error = gst_avdtp_sink_audioservice_expect(self,
+			&rsp->h, BT_SET_CONFIGURATION);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while receiving device "
+					"confirmation");
+		return FALSE;
+	}
+
+	self->data->link_mtu = rsp->link_mtu;
+
+	return TRUE;
+}
+
+static GstFlowReturn gst_avdtp_sink_preroll(GstBaseSink *basesink,
+					GstBuffer *buffer)
+{
+	GstAvdtpSink *sink = GST_AVDTP_SINK(basesink);
+	gboolean ret;
+
+	GST_AVDTP_SINK_MUTEX_LOCK(sink);
+
+	ret = gst_avdtp_sink_stream_start(sink);
+
+	GST_AVDTP_SINK_MUTEX_UNLOCK(sink);
+
+	if (!ret)
+		return GST_FLOW_ERROR;
+
+	return GST_FLOW_OK;
+}
+
+static GstFlowReturn gst_avdtp_sink_render(GstBaseSink *basesink,
+					GstBuffer *buffer)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+	gsize ret;
+	GIOError err;
+
+	err = g_io_channel_write(self->stream,
+				(gchar *) GST_BUFFER_DATA(buffer),
+				(gsize) (GST_BUFFER_SIZE(buffer)), &ret);
+
+	if (err != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while writting to socket: %d %s",
+				errno, strerror(errno));
+		return GST_FLOW_ERROR;
+	}
+
+	return GST_FLOW_OK;
+}
+
+static gboolean gst_avdtp_sink_unlock(GstBaseSink *basesink)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+	if (self->stream != NULL)
+		g_io_channel_flush(self->stream, NULL);
+
+	return TRUE;
+}
+
+static GstFlowReturn gst_avdtp_sink_buffer_alloc(GstBaseSink *basesink,
+				guint64 offset, guint size, GstCaps *caps,
+				GstBuffer **buf)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+	*buf = gst_buffer_new_and_alloc(size);
+	if (!(*buf)) {
+		GST_ERROR_OBJECT(self, "buffer allocation failed");
+		return GST_FLOW_ERROR;
+	}
+
+	gst_buffer_set_caps(*buf, caps);
+
+	GST_BUFFER_OFFSET(*buf) = offset;
+
+	return GST_FLOW_OK;
+}
+
+static void gst_avdtp_sink_class_init(GstAvdtpSinkClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS(klass);
+	GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass);
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	object_class->finalize = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_finalize);
+	object_class->set_property = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_set_property);
+	object_class->get_property = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_get_property);
+
+	basesink_class->start = GST_DEBUG_FUNCPTR(gst_avdtp_sink_start);
+	basesink_class->stop = GST_DEBUG_FUNCPTR(gst_avdtp_sink_stop);
+	basesink_class->render = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_render);
+	basesink_class->preroll = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_preroll);
+	basesink_class->unlock = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_unlock);
+	basesink_class->event = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_event);
+
+	basesink_class->buffer_alloc =
+		GST_DEBUG_FUNCPTR(gst_avdtp_sink_buffer_alloc);
+
+	g_object_class_install_property(object_class, PROP_DEVICE,
+					g_param_spec_string("device", "Device",
+					"Bluetooth remote device address",
+					NULL, G_PARAM_READWRITE));
+
+	g_object_class_install_property(object_class, PROP_AUTOCONNECT,
+					g_param_spec_boolean("auto-connect",
+					"Auto-connect",
+					"Automatically attempt to connect "
+					"to device", DEFAULT_AUTOCONNECT,
+					G_PARAM_READWRITE));
+
+	GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0,
+				"A2DP headset sink element");
+}
+
+static void gst_avdtp_sink_init(GstAvdtpSink *self,
+			GstAvdtpSinkClass *klass)
+{
+	self->device = NULL;
+	self->data = NULL;
+
+	self->stream = NULL;
+
+	self->dev_caps = NULL;
+
+	self->autoconnect = DEFAULT_AUTOCONNECT;
+
+	self->sink_lock = g_mutex_new();
+
+	/* FIXME this is for not synchronizing with clock, should be tested
+	 * with devices to see the behaviour
+	gst_base_sink_set_sync(GST_BASE_SINK(self), FALSE);
+	*/
+}
+
+static GIOError gst_avdtp_sink_audioservice_send(
+					GstAvdtpSink *self,
+					const bt_audio_msg_header_t *msg)
+{
+	GIOError error;
+	gsize written;
+	const char *type, *name;
+	uint16_t length;
+
+	length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+	error = g_io_channel_write(self->server, (const gchar *) msg, length,
+								&written);
+	if (error != G_IO_ERROR_NONE)
+		GST_ERROR_OBJECT(self, "Error sending data to audio service:"
+			" %s(%d)", strerror(errno), errno);
+
+	type = bt_audio_strtype(msg->type);
+	name = bt_audio_strname(msg->name);
+
+	GST_DEBUG_OBJECT(self, "sent: %s -> %s", type, name);
+
+	return error;
+}
+
+static GIOError gst_avdtp_sink_audioservice_recv(
+					GstAvdtpSink *self,
+					bt_audio_msg_header_t *inmsg)
+{
+	GIOError status;
+	gsize bytes_read;
+	const char *type, *name;
+	uint16_t length;
+
+	length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+	status = g_io_channel_read(self->server, (gchar *) inmsg, length,
+								&bytes_read);
+	if (status != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error receiving data from "
+				"audio service");
+		return status;
+	}
+
+	type = bt_audio_strtype(inmsg->type);
+	if (!type) {
+		status = G_IO_ERROR_INVAL;
+		GST_ERROR_OBJECT(self, "Bogus message type %d "
+				"received from audio service",
+				inmsg->type);
+	}
+
+	name = bt_audio_strname(inmsg->name);
+	if (!name) {
+		status = G_IO_ERROR_INVAL;
+		GST_ERROR_OBJECT(self, "Bogus message name %d "
+				"received from audio service",
+				inmsg->name);
+	}
+
+	if (inmsg->type == BT_ERROR) {
+		bt_audio_error_t *err = (void *) inmsg;
+		status = G_IO_ERROR_INVAL;
+		GST_ERROR_OBJECT(self, "%s failed : "
+					"%s(%d)",
+					name,
+					strerror(err->posix_errno),
+					err->posix_errno);
+	}
+
+	GST_DEBUG_OBJECT(self, "received: %s <- %s", type, name);
+
+	return status;
+}
+
+static GIOError gst_avdtp_sink_audioservice_expect(
+			GstAvdtpSink *self, bt_audio_msg_header_t *outmsg,
+			guint8 expected_name)
+{
+	GIOError status;
+
+	status = gst_avdtp_sink_audioservice_recv(self, outmsg);
+	if (status != G_IO_ERROR_NONE)
+		return status;
+
+	if (outmsg->name != expected_name)
+		status = G_IO_ERROR_INVAL;
+
+	return status;
+}
+
+gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin)
+{
+	return gst_element_register(plugin, "avdtpsink", GST_RANK_NONE,
+							GST_TYPE_AVDTP_SINK);
+}
+
+
+/* public functions */
+GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink)
+{
+	if (sink->dev_caps == NULL)
+		return NULL;
+
+	return gst_caps_copy(sink->dev_caps);
+}
+
+gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *self,
+			GstCaps *caps)
+{
+	gboolean ret;
+
+	GST_DEBUG_OBJECT(self, "setting device caps");
+	GST_AVDTP_SINK_MUTEX_LOCK(self);
+	ret = gst_avdtp_sink_configure(self, caps);
+
+	if (self->stream_caps)
+		gst_caps_unref(self->stream_caps);
+	self->stream_caps = gst_caps_ref(caps);
+
+	GST_AVDTP_SINK_MUTEX_UNLOCK(self);
+
+	return ret;
+}
+
+guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink)
+{
+	return sink->data->link_mtu;
+}
+
+void gst_avdtp_sink_set_device(GstAvdtpSink *self, const gchar *dev)
+{
+	if (self->device != NULL)
+		g_free(self->device);
+
+	GST_LOG_OBJECT(self, "Setting device: %s", dev);
+	self->device = g_strdup(dev);
+}
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self)
+{
+	return g_strdup(self->device);
+}
+
+void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc)
+{
+	gint new_crc;
+
+	new_crc = crc ? CRC_PROTECTED : CRC_UNPROTECTED;
+
+	/* test if we already received a different crc */
+	if (self->mp3_using_crc != -1 && new_crc != self->mp3_using_crc) {
+		GST_WARNING_OBJECT(self, "crc changed during stream");
+		return;
+	}
+	self->mp3_using_crc = new_crc;
+
+}
+
+void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self,
+			const gchar *mode)
+{
+	gint new_mode;
+
+	new_mode = gst_avdtp_sink_get_channel_mode(mode);
+
+	if (self->channel_mode != -1 && new_mode != self->channel_mode) {
+		GST_WARNING_OBJECT(self, "channel mode changed during stream");
+		return;
+	}
+
+	self->channel_mode = new_mode;
+	if (self->channel_mode == -1)
+		GST_WARNING_OBJECT(self, "Received invalid channel "
+				"mode: %s", mode);
+}
diff --git a/audio/gstavdtpsink.h b/audio/gstavdtpsink.h
new file mode 100644
index 0000000..b4bee39
--- /dev/null
+++ b/audio/gstavdtpsink.h
@@ -0,0 +1,101 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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;
+	GIOChannel *stream;
+
+	struct bluetooth_data *data;
+	gboolean autoconnect;
+	GIOChannel *server;
+
+	/* mp3 stream data (outside caps data)*/
+	gint mp3_using_crc;
+	gint channel_mode;
+
+	/* stream connection data */
+	GstCaps *stream_caps;
+
+	GstCaps *dev_caps;
+
+	GMutex *sink_lock;
+
+	guint watch_id;
+};
+
+struct _GstAvdtpSinkClass {
+	GstBaseSinkClass parent_class;
+};
+
+//GType gst_avdtp_sink_get_type(void);
+
+GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink);
+gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *sink,
+			GstCaps *caps);
+
+guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink);
+
+void gst_avdtp_sink_set_device(GstAvdtpSink *sink,
+		const gchar* device);
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink);
+
+gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin);
+
+void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc);
+
+void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self,
+			const gchar *mode);
+
+
+G_END_DECLS
+
+#endif /* __GST_AVDTP_SINK_H */
diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c
new file mode 100644
index 0000000..7775b48
--- /dev/null
+++ b/audio/gstbluetooth.c
@@ -0,0 +1,106 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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 (sbc_init(&sbc, 0) < 0)
+		return;
+
+	if (data == NULL || *data != 0x9c)	/* SBC syncword */
+		return;
+
+	aux = g_new(guint8, 32);
+	memcpy(aux, data, 32);
+	sbc_parse(&sbc, aux, 32);
+	g_free(aux);
+	caps = gst_sbc_parse_caps_from_sbc(&sbc);
+	sbc_finish(&sbc);
+
+	gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, caps);
+	gst_caps_unref(caps);
+}
+
+static gchar *sbc_exts[] = { "sbc", NULL };
+
+static gboolean plugin_init(GstPlugin *plugin)
+{
+	GST_INFO("Bluetooth plugin %s", VERSION);
+
+	if (gst_type_find_register(plugin, "sbc",
+			GST_RANK_PRIMARY, sbc_typefind, sbc_exts,
+					SBC_CAPS, NULL, NULL) == FALSE)
+		return FALSE;
+
+	if (!gst_sbc_enc_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_sbc_dec_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_sbc_parse_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_avdtp_sink_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_a2dp_sink_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_rtp_sbc_pay_plugin_init(plugin))
+		return FALSE;
+
+	return TRUE;
+}
+
+extern GstPluginDesc gst_plugin_desc __attribute__ ((visibility("default")));
+
+GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR,
+	"bluetooth", "Bluetooth plugin library",
+	plugin_init, VERSION, "LGPL", "BlueZ", "http://www.bluez.org/")
diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c
new file mode 100644
index 0000000..78ffb2a
--- /dev/null
+++ b/audio/gstrtpsbcpay.c
@@ -0,0 +1,351 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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 "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
index 0000000..7b314aa
--- /dev/null
+++ b/audio/gstrtpsbcpay.h
@@ -0,0 +1,66 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..4b46f52
--- /dev/null
+++ b/audio/gstsbcdec.c
@@ -0,0 +1,222 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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 "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
index 0000000..383a3bf
--- /dev/null
+++ b/audio/gstsbcdec.h
@@ -0,0 +1,66 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..0ecf39c
--- /dev/null
+++ b/audio/gstsbcenc.c
@@ -0,0 +1,602 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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 "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
index 0000000..6d69922
--- /dev/null
+++ b/audio/gstsbcenc.h
@@ -0,0 +1,75 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..30d56ba
--- /dev/null
+++ b/audio/gstsbcparse.c
@@ -0,0 +1,219 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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 "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
index 0000000..ef3d1d8
--- /dev/null
+++ b/audio/gstsbcparse.h
@@ -0,0 +1,69 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..b102da3
--- /dev/null
+++ b/audio/gstsbcutil.c
@@ -0,0 +1,521 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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 sucessfull conversion.
+ * If an error occurs, it will return NULL and error_message will contain the
+ * error message.
+ *
+ * error_message must be passed NULL, if an error occurs, the caller has the
+ * ownership of the error_message, it must be freed after use.
+ */
+GstCaps *gst_sbc_util_caps_fixate(GstCaps *caps, gchar **error_message)
+{
+	GstCaps *result;
+	GstStructure *structure;
+	const GValue *value;
+	gboolean error = FALSE;
+	gint temp, rate, channels, blocks, subbands, bitpool;
+	const gchar *allocation = NULL;
+	const gchar *mode = NULL;
+
+	g_assert(*error_message == NULL);
+
+	structure = gst_caps_get_structure(caps, 0);
+
+	if (!gst_structure_has_field(structure, "rate")) {
+		error = TRUE;
+		*error_message = g_strdup("no rate");
+		goto error;
+	} else {
+		value = gst_structure_get_value(structure, "rate");
+		if (GST_VALUE_HOLDS_LIST(value))
+			temp = gst_sbc_select_rate_from_list(value);
+		else
+			temp = g_value_get_int(value);
+		rate = temp;
+	}
+
+	if (!gst_structure_has_field(structure, "channels")) {
+		error = TRUE;
+		*error_message = g_strdup("no channels");
+		goto error;
+	} else {
+		value = gst_structure_get_value(structure, "channels");
+		if (GST_VALUE_HOLDS_INT_RANGE(value))
+			temp = gst_sbc_select_channels_from_range(value);
+		else
+			temp = g_value_get_int(value);
+		channels = temp;
+	}
+
+	if (!gst_structure_has_field(structure, "blocks")) {
+		error = TRUE;
+		*error_message = g_strdup("no blocks.");
+		goto error;
+	} else {
+		value = gst_structure_get_value(structure, "blocks");
+		if (GST_VALUE_HOLDS_LIST(value))
+			temp = gst_sbc_select_blocks_from_list(value);
+		else
+			temp = g_value_get_int(value);
+		blocks = temp;
+	}
+
+	if (!gst_structure_has_field(structure, "subbands")) {
+		error = TRUE;
+		*error_message = g_strdup("no subbands");
+		goto error;
+	} else {
+		value = gst_structure_get_value(structure, "subbands");
+		if (GST_VALUE_HOLDS_LIST(value))
+			temp = gst_sbc_select_subbands_from_list(value);
+		else
+			temp = g_value_get_int(value);
+		subbands = temp;
+	}
+
+	if (!gst_structure_has_field(structure, "bitpool")) {
+		error = TRUE;
+		*error_message = g_strdup("no bitpool");
+		goto error;
+	} else {
+		value = gst_structure_get_value(structure, "bitpool");
+		if (GST_VALUE_HOLDS_INT_RANGE(value))
+			temp = gst_sbc_select_bitpool_from_range(value);
+		else
+			temp = g_value_get_int(value);
+		bitpool = temp;
+	}
+
+	if (!gst_structure_has_field(structure, "allocation")) {
+		error = TRUE;
+		*error_message = g_strdup("no allocation");
+		goto error;
+	} else {
+		value = gst_structure_get_value(structure, "allocation");
+		if (GST_VALUE_HOLDS_LIST(value))
+			allocation = gst_sbc_get_allocation_from_list(value);
+		else
+			allocation = g_value_get_string(value);
+	}
+
+	if (!gst_structure_has_field(structure, "mode")) {
+		error = TRUE;
+		*error_message = g_strdup("no mode");
+		goto error;
+	} else {
+		value = gst_structure_get_value(structure, "mode");
+		if (GST_VALUE_HOLDS_LIST(value)) {
+			mode = gst_sbc_get_mode_from_list(value, channels);
+		} else
+			mode = g_value_get_string(value);
+	}
+
+	/* perform validation
+	 * if channels is 1, we must have channel mode = mono
+	 * if channels is 2, we can't have channel mode = mono */
+	if ( (channels == 1 && (strcmp(mode, "mono") != 0) ) ||
+			( channels == 2 && ( strcmp(mode, "mono") == 0))) {
+		*error_message = g_strdup_printf("Invalid combination of "
+					"channels (%d) and channel mode (%s)",
+					channels, mode);
+		error = TRUE;
+	}
+
+error:
+	if (error)
+		return NULL;
+
+	result = gst_caps_new_simple("audio/x-sbc",
+					"rate", G_TYPE_INT, rate,
+					"channels", G_TYPE_INT, channels,
+					"mode", G_TYPE_STRING, mode,
+					"blocks", G_TYPE_INT, blocks,
+					"subbands", G_TYPE_INT, subbands,
+					"allocation", G_TYPE_STRING, allocation,
+					"bitpool", G_TYPE_INT, bitpool,
+					NULL);
+
+	return result;
+}
+
+/**
+ * Sets the int field_value to the  param "field" on the structure.
+ * value is used to do the operation, it must be a uninitialized (zero-filled)
+ * GValue, it will be left unitialized at the end of the function.
+ */
+void gst_sbc_util_set_structure_int_param(GstStructure *structure,
+			const gchar *field, gint field_value,
+			GValue *value)
+{
+	value = g_value_init(value, G_TYPE_INT);
+	g_value_set_int(value, field_value);
+	gst_structure_set_value(structure, field, value);
+	g_value_unset(value);
+}
+
+/**
+ * Sets the string field_value to the  param "field" on the structure.
+ * value is used to do the operation, it must be a uninitialized (zero-filled)
+ * GValue, it will be left unitialized at the end of the function.
+ */
+void gst_sbc_util_set_structure_string_param(GstStructure *structure,
+			const gchar *field, const gchar *field_value,
+			GValue *value)
+{
+	value = g_value_init(value, G_TYPE_STRING);
+	g_value_set_string(value, field_value);
+	gst_structure_set_value(structure, field, value);
+	g_value_unset(value);
+}
+
+gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps)
+{
+	GstStructure *structure;
+	gint rate, channels, subbands, blocks, bitpool;
+	const gchar *mode;
+	const gchar *allocation;
+
+	g_assert(gst_caps_is_fixed(caps));
+
+	structure = gst_caps_get_structure(caps, 0);
+
+	if (!gst_structure_get_int(structure, "rate", &rate))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "channels", &channels))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "subbands", &subbands))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "blocks", &blocks))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "bitpool", &bitpool))
+		return FALSE;
+
+	if (!(mode = gst_structure_get_string(structure, "mode")))
+		return FALSE;
+	if (!(allocation = gst_structure_get_string(structure, "allocation")))
+		return FALSE;
+
+	if (channels == 1 && strcmp(mode, "mono") != 0)
+		return FALSE;
+
+	sbc->frequency = gst_sbc_parse_rate_to_sbc(rate);
+	sbc->blocks = gst_sbc_parse_blocks_to_sbc(blocks);
+	sbc->subbands = gst_sbc_parse_subbands_to_sbc(subbands);
+	sbc->bitpool = bitpool;
+	sbc->mode = gst_sbc_parse_mode_to_sbc(mode);
+	sbc->allocation = gst_sbc_parse_allocation_to_sbc(allocation);
+
+	return TRUE;
+}
+
diff --git a/audio/gstsbcutil.h b/audio/gstsbcutil.h
new file mode 100644
index 0000000..b8f626b
--- /dev/null
+++ b/audio/gstsbcutil.h
@@ -0,0 +1,75 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..2c852d1
--- /dev/null
+++ b/audio/headset.c
@@ -0,0 +1,2850 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "device.h"
+#include "manager.h"
+#include "error.h"
+#include "telephony.h"
+#include "headset.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "dbus-common.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#define DC_TIMEOUT 3
+
+#define RING_INTERVAL 3
+
+#define BUF_SIZE 1024
+
+#define HEADSET_GAIN_SPEAKER 'S'
+#define HEADSET_GAIN_MICROPHONE 'M'
+
+static struct {
+	gboolean telephony_ready;	/* Telephony plugin initialized */
+	uint32_t features;		/* HFP AG features */
+	const struct indicator *indicators;	/* Available HFP indicators */
+	int er_mode;			/* Event reporting mode */
+	int er_ind;			/* Event reporting for indicators */
+	int rh;				/* Response and Hold state */
+	char *number;			/* Incoming phone number */
+	int number_type;		/* Incoming number type */
+	guint ring_timer;		/* For incoming call indication */
+	const char *chld;		/* Response to AT+CHLD=? */
+} ag = {
+	.telephony_ready = FALSE,
+	.features = 0,
+	.er_mode = 3,
+	.er_ind = 0,
+	.rh = -1,
+	.number = NULL,
+	.number_type = 0,
+	.ring_timer = 0,
+};
+
+static gboolean sco_hci = TRUE;
+
+static GSList *active_devices = NULL;
+
+static char *str_state[] = {
+	"HEADSET_STATE_DISCONNECTED",
+	"HEADSET_STATE_CONNECT_IN_PROGRESS",
+	"HEADSET_STATE_CONNECTED",
+	"HEADSET_STATE_PLAY_IN_PROGRESS",
+	"HEADSET_STATE_PLAYING",
+};
+
+struct headset_state_callback {
+	headset_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct connect_cb {
+	unsigned int id;
+	headset_stream_cb_t cb;
+	void *cb_data;
+};
+
+struct pending_connect {
+	DBusMessage *msg;
+	DBusPendingCall *call;
+	GIOChannel *io;
+	int err;
+	headset_state_t target_state;
+	GSList *callbacks;
+	uint16_t svclass;
+};
+
+struct headset {
+	uint32_t hsp_handle;
+	uint32_t hfp_handle;
+
+	int rfcomm_ch;
+
+	GIOChannel *rfcomm;
+	GIOChannel *tmp_rfcomm;
+	GIOChannel *sco;
+	guint sco_id;
+	guint dc_id;
+
+	gboolean auto_dc;
+
+	guint dc_timer;
+
+	char buf[BUF_SIZE];
+	int data_start;
+	int data_length;
+
+	gboolean hfp_active;
+	gboolean search_hfp;
+	gboolean cli_active;
+	gboolean cme_enabled;
+	gboolean cwa_enabled;
+	gboolean pending_ring;
+	gboolean inband_ring;
+	gboolean nrec;
+	gboolean nrec_req;
+
+	headset_state_t state;
+	struct pending_connect *pending;
+
+	int sp_gain;
+	int mic_gain;
+
+	unsigned int hf_features;
+	headset_lock_t lock;
+};
+
+struct event {
+	const char *cmd;
+	int (*callback) (struct audio_device *device, const char *buf);
+};
+
+static GSList *headset_callbacks = NULL;
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+			"Invalid arguments in method call");
+}
+
+static DBusHandlerResult error_not_supported(DBusConnection *conn,
+							DBusMessage *msg)
+{
+	return error_common_reply(conn, msg, ERROR_INTERFACE ".NotSupported",
+							"Not supported");
+}
+
+static DBusHandlerResult error_connection_attempt_failed(DBusConnection *conn,
+						DBusMessage *msg, int err)
+{
+	return error_common_reply(conn, msg,
+			ERROR_INTERFACE ".ConnectionAttemptFailed",
+			err > 0 ? strerror(err) : "Connection attempt failed");
+}
+
+static int rfcomm_connect(struct audio_device *device, headset_stream_cb_t cb,
+				void *user_data, unsigned int *cb_id);
+static int get_records(struct audio_device *device, headset_stream_cb_t cb,
+			void *user_data, unsigned int *cb_id);
+
+static void print_ag_features(uint32_t features)
+{
+	GString *gstr;
+	char *str;
+
+	if (features == 0) {
+		debug("HFP AG features: (none)");
+		return;
+	}
+
+	gstr = g_string_new("HFP AG features: ");
+
+	if (features & AG_FEATURE_THREE_WAY_CALLING)
+		g_string_append(gstr, "\"Three-way calling\" ");
+	if (features & AG_FEATURE_EC_ANDOR_NR)
+		g_string_append(gstr, "\"EC and/or NR function\" ");
+	if (features & AG_FEATURE_VOICE_RECOGNITION)
+		g_string_append(gstr, "\"Voice recognition function\" ");
+	if (features & AG_FEATURE_INBAND_RINGTONE)
+		g_string_append(gstr, "\"In-band ring tone capability\" ");
+	if (features & AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG)
+		g_string_append(gstr, "\"Attach a number to a voice tag\" ");
+	if (features & AG_FEATURE_REJECT_A_CALL)
+		g_string_append(gstr, "\"Ability to reject a call\" ");
+	if (features & AG_FEATURE_ENHANCED_CALL_STATUS)
+		g_string_append(gstr, "\"Enhanced call status\" ");
+	if (features & AG_FEATURE_ENHANCED_CALL_CONTROL)
+		g_string_append(gstr, "\"Enhanced call control\" ");
+	if (features & AG_FEATURE_EXTENDED_ERROR_RESULT_CODES)
+		g_string_append(gstr, "\"Extended Error Result Codes\" ");
+
+	str = g_string_free(gstr, FALSE);
+
+	debug("%s", str);
+
+	g_free(str);
+}
+
+static void print_hf_features(uint32_t features)
+{
+	GString *gstr;
+	char *str;
+
+	if (features == 0) {
+		debug("HFP HF features: (none)");
+		return;
+	}
+
+	gstr = g_string_new("HFP HF features: ");
+
+	if (features & HF_FEATURE_EC_ANDOR_NR)
+		g_string_append(gstr, "\"EC and/or NR function\" ");
+	if (features & HF_FEATURE_CALL_WAITING_AND_3WAY)
+		g_string_append(gstr, "\"Call waiting and 3-way calling\" ");
+	if (features & HF_FEATURE_CLI_PRESENTATION)
+		g_string_append(gstr, "\"CLI presentation capability\" ");
+	if (features & HF_FEATURE_VOICE_RECOGNITION)
+		g_string_append(gstr, "\"Voice recognition activation\" ");
+	if (features & HF_FEATURE_REMOTE_VOLUME_CONTROL)
+		g_string_append(gstr, "\"Remote volume control\" ");
+	if (features & HF_FEATURE_ENHANCED_CALL_STATUS)
+		g_string_append(gstr, "\"Enhanced call status\" ");
+	if (features & HF_FEATURE_ENHANCED_CALL_CONTROL)
+		g_string_append(gstr, "\"Enhanced call control\" ");
+
+	str = g_string_free(gstr, FALSE);
+
+	debug("%s", str);
+
+	g_free(str);
+}
+
+static const char *state2str(headset_state_t state)
+{
+	switch (state) {
+	case HEADSET_STATE_DISCONNECTED:
+		return "disconnected";
+	case HEADSET_STATE_CONNECT_IN_PROGRESS:
+		return "connecting";
+	case HEADSET_STATE_CONNECTED:
+	case HEADSET_STATE_PLAY_IN_PROGRESS:
+		return "connected";
+	case HEADSET_STATE_PLAYING:
+		return "playing";
+	}
+
+	return NULL;
+}
+
+static int headset_send_valist(struct headset *hs, char *format, va_list ap)
+{
+	char rsp[BUF_SIZE];
+	ssize_t total_written, count;
+	int fd;
+
+	count = vsnprintf(rsp, sizeof(rsp), format, ap);
+
+	if (count < 0)
+		return -EINVAL;
+
+	if (!hs->rfcomm) {
+		error("headset_send: the headset is not connected");
+		return -EIO;
+	}
+
+	total_written = 0;
+	fd = g_io_channel_unix_get_fd(hs->rfcomm);
+
+	while (total_written < count) {
+		ssize_t written;
+
+		written = write(fd, rsp + total_written,
+				count - total_written);
+		if (written < 0)
+			return -errno;
+
+		total_written += written;
+	}
+
+	return 0;
+}
+
+static int headset_send(struct headset *hs, char *format, ...)
+{
+	va_list ap;
+	int ret;
+
+	va_start(ap, format);
+	ret = headset_send_valist(hs, format, ap);
+	va_end(ap);
+
+	return ret;
+}
+
+static int supported_features(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+	int err;
+
+	if (strlen(buf) < 9)
+		return -EINVAL;
+
+	hs->hf_features = strtoul(&buf[8], NULL, 10);
+
+	print_hf_features(hs->hf_features);
+
+	err = headset_send(hs, "\r\n+BRSF: %u\r\n", ag.features);
+	if (err < 0)
+		return err;
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+static char *indicator_ranges(const struct indicator *indicators)
+{
+	int i;
+	GString *gstr;
+
+	gstr = g_string_new("\r\n+CIND: ");
+
+	for (i = 0; indicators[i].desc != NULL; i++) {
+		if (i == 0)
+			g_string_append_printf(gstr, "(\"%s\",(%s))",
+						indicators[i].desc,
+						indicators[i].range);
+		else
+			g_string_append_printf(gstr, ",(\"%s\",(%s))",
+						indicators[i].desc,
+						indicators[i].range);
+	}
+
+	g_string_append(gstr, "\r\n");
+
+	return g_string_free(gstr, FALSE);
+}
+
+static char *indicator_values(const struct indicator *indicators)
+{
+	int i;
+	GString *gstr;
+
+	gstr = g_string_new("\r\n+CIND: ");
+
+	for (i = 0; indicators[i].desc != NULL; i++) {
+		if (i == 0)
+			g_string_append_printf(gstr, "%d", indicators[i].val);
+		else
+			g_string_append_printf(gstr, ",%d", indicators[i].val);
+	}
+
+	g_string_append(gstr, "\r\n");
+
+	return g_string_free(gstr, FALSE);
+}
+
+static int report_indicators(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+	int err;
+	char *str;
+
+	if (strlen(buf) < 8)
+		return -EINVAL;
+
+	if (ag.indicators == NULL) {
+		error("HFP AG indicators not initialized");
+		return headset_send(hs, "\r\nERROR\r\n");
+	}
+
+	if (buf[7] == '=')
+		str = indicator_ranges(ag.indicators);
+	else
+		str = indicator_values(ag.indicators);
+
+	err = headset_send(hs, str);
+
+	g_free(str);
+
+	if (err < 0)
+		return err;
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+static void pending_connect_complete(struct connect_cb *cb, struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	if (hs->pending->err)
+		cb->cb(NULL, cb->cb_data);
+	else
+		cb->cb(dev, cb->cb_data);
+}
+
+static void pending_connect_finalize(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+	struct pending_connect *p = hs->pending;
+
+	if (p == NULL)
+		return;
+
+	if (p->svclass)
+		bt_cancel_discovery(&dev->src, &dev->dst);
+
+	g_slist_foreach(p->callbacks, (GFunc) pending_connect_complete, dev);
+
+	g_slist_foreach(p->callbacks, (GFunc) g_free, NULL);
+	g_slist_free(p->callbacks);
+
+	if (p->io) {
+		g_io_channel_shutdown(p->io, TRUE, NULL);
+		g_io_channel_unref(p->io);
+	}
+
+	if (p->msg)
+		dbus_message_unref(p->msg);
+
+	if (p->call) {
+		dbus_pending_call_cancel(p->call);
+		dbus_pending_call_unref(p->call);
+	}
+
+	g_free(p);
+
+	hs->pending = NULL;
+}
+
+static void pending_connect_init(struct headset *hs, headset_state_t target_state)
+{
+	if (hs->pending) {
+		if (hs->pending->target_state < target_state)
+			hs->pending->target_state = target_state;
+		return;
+	}
+
+	hs->pending = g_new0(struct pending_connect, 1);
+	hs->pending->target_state = target_state;
+}
+
+static unsigned int connect_cb_new(struct headset *hs,
+					headset_state_t target_state,
+					headset_stream_cb_t func,
+					void *user_data)
+{
+	struct connect_cb *cb;
+	unsigned int free_cb_id = 1;
+
+	pending_connect_init(hs, target_state);
+
+	if (!func)
+		return 0;
+
+	cb = g_new(struct connect_cb, 1);
+
+	cb->cb = func;
+	cb->cb_data = user_data;
+	cb->id = free_cb_id++;
+
+	hs->pending->callbacks = g_slist_append(hs->pending->callbacks,
+						cb);
+
+	return cb->id;
+}
+
+static void send_foreach_headset(GSList *devices,
+					int (*cmp) (struct headset *hs),
+					char *format, ...)
+{
+	GSList *l;
+	va_list ap;
+
+	for (l = devices; l != NULL; l = l->next) {
+		struct audio_device *device = l->data;
+		struct headset *hs = device->headset;
+		int ret;
+
+		assert(hs != NULL);
+
+		if (cmp && cmp(hs) != 0)
+			continue;
+
+		va_start(ap, format);
+		ret = headset_send_valist(hs, format, ap);
+		if (ret < 0)
+			error("Failed to send to headset: %s (%d)",
+					strerror(-ret), -ret);
+		va_end(ap);
+	}
+}
+
+static int cli_cmp(struct headset *hs)
+{
+	if (!hs->hfp_active)
+		return -1;
+
+	if (hs->cli_active)
+		return 0;
+	else
+		return -1;
+}
+
+static gboolean ring_timer_cb(gpointer data)
+{
+	send_foreach_headset(active_devices, NULL, "\r\nRING\r\n");
+
+	if (ag.number)
+		send_foreach_headset(active_devices, cli_cmp,
+					"\r\n+CLIP: \"%s\",%d\r\n",
+					ag.number, ag.number_type);
+
+	return TRUE;
+}
+
+static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+	int sk;
+	struct audio_device *dev = user_data;
+	struct headset *hs = dev->headset;
+	struct pending_connect *p = hs->pending;
+
+	if (err) {
+		error("%s", err->message);
+
+		if (p && p->msg)
+			error_connection_attempt_failed(dev->conn, p->msg, p->err);
+
+		pending_connect_finalize(dev);
+
+		if (hs->rfcomm)
+			headset_set_state(dev, HEADSET_STATE_CONNECTED);
+		else
+			headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+
+		return;
+	}
+
+	debug("SCO socket opened for headset %s", dev->path);
+
+	sk = g_io_channel_unix_get_fd(chan);
+
+	debug("SCO fd=%d", sk);
+
+	if (p) {
+		p->io = NULL;
+		if (p->msg) {
+			DBusMessage *reply;
+			reply = dbus_message_new_method_return(p->msg);
+			g_dbus_send_message(dev->conn, reply);
+		}
+
+		pending_connect_finalize(dev);
+	}
+
+	fcntl(sk, F_SETFL, 0);
+
+	headset_set_state(dev, HEADSET_STATE_PLAYING);
+
+	if (hs->pending_ring) {
+		ring_timer_cb(NULL);
+		ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL,
+						ring_timer_cb,
+						NULL);
+		hs->pending_ring = FALSE;
+	}
+}
+
+static int sco_connect(struct audio_device *dev, headset_stream_cb_t cb,
+			void *user_data, unsigned int *cb_id)
+{
+	struct headset *hs = dev->headset;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	if (hs->state != HEADSET_STATE_CONNECTED)
+		return -EINVAL;
+
+	io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+				BT_IO_OPT_DEST_BDADDR, &dev->dst,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+		return -EIO;
+	}
+
+	hs->sco = io;
+
+	headset_set_state(dev, HEADSET_STATE_PLAY_IN_PROGRESS);
+
+	pending_connect_init(hs, HEADSET_STATE_PLAYING);
+
+	if (cb) {
+		unsigned int id = connect_cb_new(hs, HEADSET_STATE_PLAYING,
+							cb, user_data);
+		if (cb_id)
+			*cb_id = id;
+	}
+
+	return 0;
+}
+
+static int hfp_cmp(struct headset *hs)
+{
+	if (hs->hfp_active)
+		return 0;
+	else
+		return -1;
+}
+
+static void hfp_slc_complete(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+	struct pending_connect *p = hs->pending;
+
+	debug("HFP Service Level Connection established");
+
+	headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+	if (p == NULL)
+		return;
+
+	if (p->target_state == HEADSET_STATE_CONNECTED) {
+		if (p->msg) {
+			DBusMessage *reply = dbus_message_new_method_return(p->msg);
+			g_dbus_send_message(dev->conn, reply);
+		}
+		pending_connect_finalize(dev);
+		return;
+	}
+
+	p->err = sco_connect(dev, NULL, NULL, NULL);
+	if (p->err < 0) {
+		if (p->msg)
+			error_connection_attempt_failed(dev->conn, p->msg, p->err);
+		pending_connect_finalize(dev);
+	}
+}
+
+static int telephony_generic_rsp(struct audio_device *device, cme_error_t err)
+{
+	struct headset *hs = device->headset;
+
+	if (err != CME_ERROR_NONE) {
+		if (hs->cme_enabled)
+			return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+		else
+			return headset_send(hs, "\r\nERROR\r\n");
+	}
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err)
+{
+	struct audio_device *device = telephony_device;
+	struct headset *hs = device->headset;
+	int ret;
+
+	if (err != CME_ERROR_NONE)
+		return telephony_generic_rsp(telephony_device, err);
+
+	ret = headset_send(hs, "\r\nOK\r\n");
+	if (ret < 0)
+		return ret;
+
+	if (hs->state != HEADSET_STATE_CONNECT_IN_PROGRESS)
+		return 0;
+
+	if (hs->hf_features & HF_FEATURE_CALL_WAITING_AND_3WAY &&
+			ag.features & AG_FEATURE_THREE_WAY_CALLING)
+		return 0;
+
+	hfp_slc_complete(device);
+
+	return 0;
+}
+
+static int event_reporting(struct audio_device *dev, const char *buf)
+{
+	char **tokens; /* <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;
+
+	debug("Event reporting (CMER): mode=%d, ind=%d",
+			ag.er_mode, ag.er_ind);
+
+	switch (ag.er_ind) {
+	case 0:
+	case 1:
+		telephony_event_reporting_req(dev, ag.er_ind);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int call_hold(struct audio_device *dev, const char *buf)
+{
+	struct headset *hs = dev->headset;
+	int err;
+
+	if (strlen(buf) < 9)
+		return -EINVAL;
+
+	if (buf[8] != '?') {
+		telephony_call_hold_req(dev, &buf[8]);
+		return 0;
+	}
+
+	err = headset_send(hs, "\r\n+CHLD: (%s)\r\n", ag.chld);
+	if (err < 0)
+		return err;
+
+	err = headset_send(hs, "\r\nOK\r\n");
+	if (err < 0)
+		return err;
+
+	if (hs->state != HEADSET_STATE_CONNECT_IN_PROGRESS)
+		return 0;
+
+	hfp_slc_complete(dev);
+
+	return 0;
+}
+
+int telephony_key_press_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+static int key_press(struct audio_device *device, const char *buf)
+{
+	if (strlen(buf) < 9)
+		return -EINVAL;
+
+	g_dbus_emit_signal(device->conn, device->path,
+			AUDIO_HEADSET_INTERFACE, "AnswerRequested",
+			DBUS_TYPE_INVALID);
+
+	if (ag.ring_timer) {
+		g_source_remove(ag.ring_timer);
+		ag.ring_timer = 0;
+	}
+
+	telephony_key_press_req(device, &buf[8]);
+
+	return 0;
+}
+
+int telephony_answer_call_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+static int answer_call(struct audio_device *device, const char *buf)
+{
+	if (ag.ring_timer) {
+		g_source_remove(ag.ring_timer);
+		ag.ring_timer = 0;
+	}
+
+	if (ag.number) {
+		g_free(ag.number);
+		ag.number = NULL;
+	}
+
+	telephony_answer_call_req(device);
+
+	return 0;
+}
+
+int telephony_terminate_call_rsp(void *telephony_device,
+					cme_error_t err)
+{
+	struct audio_device *device = telephony_device;
+	struct headset *hs = device->headset;
+
+	if (err != CME_ERROR_NONE)
+		return telephony_generic_rsp(telephony_device, err);
+
+	g_dbus_emit_signal(device->conn, device->path,
+			AUDIO_HEADSET_INTERFACE, "CallTerminated",
+			DBUS_TYPE_INVALID);
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int terminate_call(struct audio_device *device, const char *buf)
+{
+	if (ag.number) {
+		g_free(ag.number);
+		ag.number = NULL;
+	}
+
+	if (ag.ring_timer) {
+		g_source_remove(ag.ring_timer);
+		ag.ring_timer = 0;
+	}
+
+	telephony_terminate_call_req(device);
+
+	return 0;
+}
+
+static int cli_notification(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+
+	if (strlen(buf) < 9)
+		return -EINVAL;
+
+	hs->cli_active = buf[8] == '1' ? TRUE : FALSE;
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+static int response_and_hold(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+
+	if (strlen(buf) < 8)
+		return -EINVAL;
+
+	if (buf[7] == '=') {
+		telephony_response_and_hold_req(device, atoi(&buf[8]) < 0);
+		return 0;
+	}
+
+	if (ag.rh >= 0)
+		headset_send(hs, "\r\n+BTRH: %d\r\n", ag.rh);
+
+	return headset_send(hs, "\r\nOK\r\n", ag.rh);
+}
+
+int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+static int last_dialed_number(struct audio_device *device, const char *buf)
+{
+	telephony_last_dialed_number_req(device);
+
+	return 0;
+}
+
+int telephony_dial_number_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+static int dial_number(struct audio_device *device, const char *buf)
+{
+	char number[BUF_SIZE];
+	size_t buf_len;
+
+	buf_len = strlen(buf);
+
+	if (buf[buf_len - 1] != ';') {
+		debug("Rejecting non-voice call dial request");
+		return -EINVAL;
+	}
+
+	memset(number, 0, sizeof(number));
+	strncpy(number, &buf[3], buf_len - 4);
+
+	telephony_dial_number_req(device, number);
+
+	return 0;
+}
+
+static int signal_gain_setting(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+	const char *property;
+	const char *name;
+	dbus_uint16_t gain;
+
+	if (strlen(buf) < 8) {
+		error("Too short string for Gain setting");
+		return -EINVAL;
+	}
+
+	gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10);
+
+	if (gain > 15) {
+		error("Invalid gain value received: %u", gain);
+		return -EINVAL;
+	}
+
+	switch (buf[5]) {
+	case HEADSET_GAIN_SPEAKER:
+		if (hs->sp_gain == gain)
+			goto ok;
+		name = "SpeakerGainChanged";
+		property = "SpeakerGain";
+		hs->sp_gain = gain;
+		break;
+	case HEADSET_GAIN_MICROPHONE:
+		if (hs->mic_gain == gain)
+			goto ok;
+		name = "MicrophoneGainChanged";
+		property = "MicrophoneGain";
+		hs->mic_gain = gain;
+		break;
+	default:
+		error("Unknown gain setting");
+		return -EINVAL;
+	}
+
+	g_dbus_emit_signal(device->conn, device->path,
+				AUDIO_HEADSET_INTERFACE, name,
+				DBUS_TYPE_UINT16, &gain,
+				DBUS_TYPE_INVALID);
+
+	emit_property_changed(device->conn, device->path,
+				AUDIO_HEADSET_INTERFACE, property,
+				DBUS_TYPE_UINT16, &gain);
+
+ok:
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+static int dtmf_tone(struct audio_device *device, const char *buf)
+{
+	if (strlen(buf) < 8) {
+		error("Too short string for DTMF tone");
+		return -EINVAL;
+	}
+
+	telephony_transmit_dtmf_req(device, buf[7]);
+
+	return 0;
+}
+
+int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+static int subscriber_number(struct audio_device *device, const char *buf)
+{
+	telephony_subscriber_number_req(device);
+
+	return 0;
+}
+
+int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+static int list_current_calls(struct audio_device *device, const char *buf)
+{
+	telephony_list_current_calls_req(device);
+
+	return 0;
+}
+
+static int extended_errors(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+
+	if (strlen(buf) < 9)
+		return -EINVAL;
+
+	if (buf[8] == '1') {
+		hs->cme_enabled = TRUE;
+		debug("CME errors enabled for headset %p", hs);
+	} else {
+		hs->cme_enabled = FALSE;
+		debug("CME errors disabled for headset %p", hs);
+	}
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int call_waiting_notify(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+
+	if (strlen(buf) < 9)
+		return -EINVAL;
+
+	if (buf[8] == '1') {
+		hs->cwa_enabled = TRUE;
+		debug("Call waiting notification enabled for headset %p", hs);
+	} else {
+		hs->cwa_enabled = FALSE;
+		debug("Call waiting notification disabled for headset %p", hs);
+	}
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_call_hold_rsp(void *telephony_device, cme_error_t err)
+{
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err)
+{
+	struct audio_device *device = telephony_device;
+	struct headset *hs = device->headset;
+
+	if (err == CME_ERROR_NONE)
+		hs->nrec = hs->nrec_req;
+
+	return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_operator_selection_ind(int mode, const char *oper)
+{
+	if (!active_devices)
+		return -ENODEV;
+
+	send_foreach_headset(active_devices, hfp_cmp,
+				"\r\n+COPS: %d,0,\"%s\"\r\n",
+				mode, oper);
+	return 0;
+}
+
+static int operator_selection(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+
+	if (strlen(buf) < 8)
+		return -EINVAL;
+
+	switch (buf[7]) {
+	case '?':
+		telephony_operator_selection_req(device);
+		break;
+	case '=':
+		return headset_send(hs, "\r\nOK\r\n");
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int nr_and_ec(struct audio_device *device, const char *buf)
+{
+	struct headset *hs = device->headset;
+
+	if (strlen(buf) < 9)
+		return -EINVAL;
+
+	if (buf[8] == '0')
+		hs->nrec_req = FALSE;
+	else
+		hs->nrec_req = TRUE;
+
+	telephony_nr_and_ec_req(device, hs->nrec_req);
+
+	return 0;
+}
+
+static struct event event_callbacks[] = {
+	{ "ATA", answer_call },
+	{ "ATD", dial_number },
+	{ "AT+VG", signal_gain_setting },
+	{ "AT+BRSF", supported_features },
+	{ "AT+CIND", report_indicators },
+	{ "AT+CMER", event_reporting },
+	{ "AT+CHLD", call_hold },
+	{ "AT+CHUP", terminate_call },
+	{ "AT+CKPD", key_press },
+	{ "AT+CLIP", cli_notification },
+	{ "AT+BTRH", response_and_hold },
+	{ "AT+BLDN", last_dialed_number },
+	{ "AT+VTS", dtmf_tone },
+	{ "AT+CNUM", subscriber_number },
+	{ "AT+CLCC", list_current_calls },
+	{ "AT+CMEE", extended_errors },
+	{ "AT+CCWA", call_waiting_notify },
+	{ "AT+COPS", operator_selection },
+	{ "AT+NREC", nr_and_ec },
+	{ 0 }
+};
+
+static int handle_event(struct audio_device *device, const char *buf)
+{
+	struct event *ev;
+
+	debug("Received %s", buf);
+
+	for (ev = event_callbacks; ev->cmd; ev++) {
+		if (!strncmp(buf, ev->cmd, strlen(ev->cmd)))
+			return ev->callback(device, buf);
+	}
+
+	return -EINVAL;
+}
+
+static void close_sco(struct audio_device *device)
+{
+	struct headset *hs = device->headset;
+
+	if (hs->sco) {
+		int sock = g_io_channel_unix_get_fd(hs->sco);
+		shutdown(sock, SHUT_RDWR);
+		g_io_channel_shutdown(hs->sco, TRUE, NULL);
+		g_io_channel_unref(hs->sco);
+		hs->sco = NULL;
+	}
+
+	if (hs->sco_id) {
+		g_source_remove(hs->sco_id);
+		hs->sco_id = 0;
+	}
+}
+
+static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond,
+				struct audio_device *device)
+{
+	struct headset *hs;
+	unsigned char buf[BUF_SIZE];
+	gsize bytes_read = 0;
+	gsize free_space;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	hs = device->headset;
+
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		debug("ERR or HUP on RFCOMM socket");
+		goto failed;
+	}
+
+	if (g_io_channel_read(chan, (gchar *) buf, sizeof(buf) - 1,
+				&bytes_read) != G_IO_ERROR_NONE)
+		return TRUE;
+
+	free_space = sizeof(hs->buf) - hs->data_start - hs->data_length - 1;
+
+	if (free_space < bytes_read) {
+		/* Very likely that the HS is sending us garbage so
+		 * just ignore the data and disconnect */
+		error("Too much data to fit incomming buffer");
+		goto failed;
+	}
+
+	memcpy(&hs->buf[hs->data_start], buf, bytes_read);
+	hs->data_length += bytes_read;
+
+	/* Make sure the data is null terminated so we can use string
+	 * functions */
+	hs->buf[hs->data_start + hs->data_length] = '\0';
+
+	while (hs->data_length > 0) {
+		char *cr;
+		int err;
+		off_t cmd_len;
+
+		cr = strchr(&hs->buf[hs->data_start], '\r');
+		if (!cr)
+			break;
+
+		cmd_len = 1 + (off_t) cr - (off_t) &hs->buf[hs->data_start];
+		*cr = '\0';
+
+		if (cmd_len > 1)
+			err = handle_event(device, &hs->buf[hs->data_start]);
+		else
+			/* Silently skip empty commands */
+			err = 0;
+
+		if (err == -EINVAL) {
+			error("Badly formated or unrecognized command: %s",
+					&hs->buf[hs->data_start]);
+			err = headset_send(hs, "\r\nERROR\r\n");
+		} else if (err < 0)
+			error("Error handling command %s: %s (%d)",
+						&hs->buf[hs->data_start],
+						strerror(-err), -err);
+
+		hs->data_start += cmd_len;
+		hs->data_length -= cmd_len;
+
+		if (!hs->data_length)
+			hs->data_start = 0;
+	}
+
+	return TRUE;
+
+failed:
+	headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+
+	return FALSE;
+}
+
+static gboolean sco_cb(GIOChannel *chan, GIOCondition cond,
+			struct audio_device *device)
+{
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	error("Audio connection got disconnected");
+
+	headset_set_state(device, HEADSET_STATE_CONNECTED);
+
+	return FALSE;
+}
+
+void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct headset *hs = dev->headset;
+	struct pending_connect *p = hs->pending;
+	char hs_address[18];
+
+	if (err) {
+		error("%s", err->message);
+		goto failed;
+	}
+
+	/* For HFP telephony isn't ready just disconnect */
+	if (hs->hfp_active && !ag.telephony_ready) {
+		error("Unable to accept HFP connection since the telephony "
+				"subsystem isn't initialized");
+		goto failed;
+	}
+
+	hs->rfcomm = hs->tmp_rfcomm;
+	hs->tmp_rfcomm = NULL;
+
+	ba2str(&dev->dst, hs_address);
+
+	if (p)
+		p->io = NULL;
+	else
+		hs->auto_dc = FALSE;
+
+	g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL,
+			(GIOFunc) rfcomm_io_cb, dev);
+
+	debug("%s: Connected to %s", dev->path, hs_address);
+
+	/* In HFP mode wait for Service Level Connection */
+	if (hs->hfp_active)
+		return;
+
+	headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+	if (p && p->target_state == HEADSET_STATE_PLAYING) {
+		p->err = sco_connect(dev, NULL, NULL, NULL);
+		if (p->err < 0)
+			goto failed;
+		return;
+	}
+
+	if (p && p->msg) {
+		DBusMessage *reply = dbus_message_new_method_return(p->msg);
+		g_dbus_send_message(dev->conn, reply);
+	}
+
+	pending_connect_finalize(dev);
+
+	return;
+
+failed:
+	if (p && p->msg)
+		error_connection_attempt_failed(dev->conn, p->msg, p->err);
+	pending_connect_finalize(dev);
+	if (hs->rfcomm)
+		headset_set_state(dev, HEADSET_STATE_CONNECTED);
+	else
+		headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+static int headset_set_channel(struct headset *headset,
+				const sdp_record_t *record, uint16_t svc)
+{
+	int ch;
+	sdp_list_t *protos;
+
+	if (sdp_get_access_protos(record, &protos) < 0) {
+		error("Unable to get access protos from headset record");
+		return -1;
+	}
+
+	ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+
+	sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+	sdp_list_free(protos, NULL);
+
+	if (ch <= 0) {
+		error("Unable to get RFCOMM channel from Headset record");
+		return -1;
+	}
+
+	headset->rfcomm_ch = ch;
+
+	if (svc == HANDSFREE_SVCLASS_ID) {
+		headset->hfp_handle = record->handle;
+		debug("Discovered Handsfree service on channel %d", ch);
+	} else {
+		headset->hsp_handle = record->handle;
+		debug("Discovered Headset service on channel %d", ch);
+	}
+
+	return 0;
+}
+
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct headset *hs = dev->headset;
+	struct pending_connect *p = hs->pending;
+	sdp_record_t *record = NULL;
+	sdp_list_t *r;
+
+	assert(hs->pending != NULL);
+
+	if (err < 0) {
+		error("Unable to get service record: %s (%d)",
+							strerror(-err), -err);
+		p->err = -err;
+		error_connection_attempt_failed(dev->conn, p->msg, p->err);
+		goto failed;
+	}
+
+	if (!recs || !recs->data) {
+		error("No records found");
+		goto failed_not_supported;
+	}
+
+	for (r = recs; r != NULL; r = r->next) {
+		sdp_list_t *classes;
+		uuid_t uuid;
+
+		record = r->data;
+
+		if (sdp_get_service_classes(record, &classes) < 0) {
+			error("Unable to get service classes from record");
+			continue;
+		}
+
+		memcpy(&uuid, classes->data, sizeof(uuid));
+
+		sdp_list_free(classes, free);
+
+		if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16) {
+			error("Not a 16 bit UUID");
+			continue;
+		}
+
+		if (uuid.value.uuid16 == p->svclass)
+			break;
+	}
+
+	if (r == NULL) {
+		error("No record found with UUID 0x%04x", p->svclass);
+		goto failed_not_supported;
+	}
+
+	if (headset_set_channel(hs, record, p->svclass) < 0) {
+		error("Unable to extract RFCOMM channel from service record");
+		goto failed_not_supported;
+	}
+
+	/* Set svclass to 0 so we can easily check that SDP is no-longer
+	 * going on (to know if bt_cancel_discovery needs to be called) */
+	p->svclass = 0;
+
+	err = rfcomm_connect(dev, NULL, NULL, NULL);
+	if (err < 0) {
+		error("Unable to connect: %s (%d)", strerror(-err), -err);
+		p->err = -err;
+		error_connection_attempt_failed(dev->conn, p->msg, p->err);
+		goto failed;
+	}
+
+	return;
+
+failed_not_supported:
+	if (p->svclass == HANDSFREE_SVCLASS_ID &&
+			get_records(dev, NULL, NULL, NULL) == 0)
+		return;
+	if (p->msg)
+		error_not_supported(dev->conn, p->msg);
+failed:
+	p->svclass = 0;
+	pending_connect_finalize(dev);
+	headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+static int get_records(struct audio_device *device, headset_stream_cb_t cb,
+			void *user_data, unsigned int *cb_id)
+{
+	struct headset *hs = device->headset;
+	uint16_t svclass;
+	uuid_t uuid;
+	int err;
+
+	if (hs->pending && hs->pending->svclass == HANDSFREE_SVCLASS_ID)
+		svclass = HEADSET_SVCLASS_ID;
+	else
+		svclass = hs->search_hfp ? HANDSFREE_SVCLASS_ID :
+							HEADSET_SVCLASS_ID;
+
+	sdp_uuid16_create(&uuid, svclass);
+
+	err = bt_search_service(&device->src, &device->dst, &uuid,
+						get_record_cb, device, NULL);
+	if (err < 0)
+		return err;
+
+	if (hs->pending) {
+		hs->pending->svclass = svclass;
+		return 0;
+	}
+
+	headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS);
+
+	pending_connect_init(hs, HEADSET_STATE_CONNECTED);
+
+	hs->pending->svclass = svclass;
+
+	if (cb) {
+		unsigned int id;
+		id = connect_cb_new(hs, HEADSET_STATE_CONNECTED,
+					cb, user_data);
+		if (cb_id)
+			*cb_id = id;
+	}
+
+	return 0;
+}
+
+static int rfcomm_connect(struct audio_device *dev, headset_stream_cb_t cb,
+				void *user_data, unsigned int *cb_id)
+{
+	struct headset *hs = dev->headset;
+	char address[18];
+	GError *err = NULL;
+
+	if (!manager_allow_headset_connection(dev))
+		return -ECONNREFUSED;
+
+	if (hs->rfcomm_ch < 0)
+		return get_records(dev, cb, user_data, cb_id);
+
+	ba2str(&dev->dst, address);
+
+	debug("%s: Connecting to %s channel %d", dev->path, address,
+		hs->rfcomm_ch);
+
+	hs->tmp_rfcomm = bt_io_connect(BT_IO_RFCOMM, headset_connect_cb, dev,
+					NULL, &err,
+					BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+					BT_IO_OPT_DEST_BDADDR, &dev->dst,
+					BT_IO_OPT_CHANNEL, hs->rfcomm_ch,
+					BT_IO_OPT_INVALID);
+
+	hs->rfcomm_ch = -1;
+
+	if (!hs->tmp_rfcomm) {
+		error("%s", err->message);
+		g_error_free(err);
+		return -EIO;
+	}
+
+	hs->hfp_active = hs->hfp_handle != 0 ? TRUE : FALSE;
+
+	headset_set_state(dev, HEADSET_STATE_CONNECT_IN_PROGRESS);
+
+	pending_connect_init(hs, HEADSET_STATE_CONNECTED);
+
+	if (cb) {
+		unsigned int id = connect_cb_new(hs, HEADSET_STATE_CONNECTED,
+							cb, user_data);
+		if (cb_id)
+			*cb_id = id;
+	}
+
+	return 0;
+}
+
+static DBusMessage *hs_stop(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	DBusMessage *reply = NULL;
+
+	if (hs->state < HEADSET_STATE_PLAY_IN_PROGRESS)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	headset_set_state(device, HEADSET_STATE_CONNECTED);
+
+	return reply;
+}
+
+static DBusMessage *hs_is_playing(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	DBusMessage *reply;
+	dbus_bool_t playing;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	playing = (hs->state == HEADSET_STATE_PLAYING);
+
+	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &playing,
+					DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static DBusMessage *hs_disconnect(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	char hs_address[18];
+
+	if (hs->state == HEADSET_STATE_DISCONNECTED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	headset_shutdown(device);
+	ba2str(&device->dst, hs_address);
+	info("Disconnected from %s, %s", hs_address, device->path);
+
+	return dbus_message_new_method_return(msg);
+
+}
+
+static DBusMessage *hs_is_connected(DBusConnection *conn,
+						DBusMessage *msg,
+						void *data)
+{
+	struct audio_device *device = data;
+	DBusMessage *reply;
+	dbus_bool_t connected;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	connected = (device->headset->state >= HEADSET_STATE_CONNECTED);
+
+	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+					DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static DBusMessage *hs_connect(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	int err;
+
+	if (hs->state == HEADSET_STATE_CONNECT_IN_PROGRESS)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+						"Connect in Progress");
+	else if (hs->state > HEADSET_STATE_CONNECT_IN_PROGRESS)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".AlreadyConnected",
+						"Already Connected");
+
+	if (hs->hfp_handle && !ag.telephony_ready)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady",
+					"Telephony subsystem not ready");
+
+	device->auto_connect = FALSE;
+
+	err = rfcomm_connect(device, NULL, NULL, NULL);
+	if (err == -ECONNREFUSED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAllowed",
+						"Too many connected devices");
+	else if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".ConnectAttemptFailed",
+						"Connect Attempt Failed");
+
+	hs->auto_dc = FALSE;
+
+	hs->pending->msg = dbus_message_ref(msg);
+
+	return NULL;
+}
+
+static DBusMessage *hs_ring(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	DBusMessage *reply = NULL;
+	int err;
+
+	if (hs->state < HEADSET_STATE_CONNECTED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (ag.ring_timer) {
+		debug("IndicateCall received when already indicating");
+		goto done;
+	}
+
+	err = headset_send(hs, "\r\nRING\r\n");
+	if (err < 0) {
+		dbus_message_unref(reply);
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(-err));
+	}
+
+	ring_timer_cb(NULL);
+	ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
+						NULL);
+
+done:
+	return reply;
+}
+
+static DBusMessage *hs_cancel_call(DBusConnection *conn,
+					DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	DBusMessage *reply = NULL;
+
+	if (hs->state < HEADSET_STATE_CONNECTED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (ag.ring_timer) {
+		g_source_remove(ag.ring_timer);
+		ag.ring_timer = 0;
+	} else
+		debug("Got CancelCall method call but no call is active");
+
+	return reply;
+}
+
+static DBusMessage *hs_play(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	int err;
+
+	if (sco_hci) {
+		error("Refusing Headset.Play() because SCO HCI routing "
+				"is enabled");
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+						"Operation not Available");
+	}
+
+	switch (hs->state) {
+	case HEADSET_STATE_DISCONNECTED:
+	case HEADSET_STATE_CONNECT_IN_PROGRESS:
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+	case HEADSET_STATE_PLAY_IN_PROGRESS:
+		if (hs->pending && hs->pending->msg == NULL) {
+			hs->pending->msg = dbus_message_ref(msg);
+			return NULL;
+		}
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".InProgress",
+						"Play in Progress");
+	case HEADSET_STATE_PLAYING:
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".AlreadyConnected",
+						"Device Already Connected");
+	case HEADSET_STATE_CONNECTED:
+	default:
+		break;
+	}
+
+	err = sco_connect(device, NULL, NULL, NULL);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(-err));
+
+	hs->pending->msg = dbus_message_ref(msg);
+
+	return NULL;
+}
+
+static DBusMessage *hs_get_speaker_gain(DBusConnection *conn,
+					DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	DBusMessage *reply;
+	dbus_uint16_t gain;
+
+	if (hs->state < HEADSET_STATE_CONNECTED || hs->sp_gain < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+						"Operation not Available");
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	gain = (dbus_uint16_t) hs->sp_gain;
+
+	dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
+					DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static DBusMessage *hs_get_mic_gain(DBusConnection *conn,
+					DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	DBusMessage *reply;
+	dbus_uint16_t gain;
+
+	if (hs->state < HEADSET_STATE_CONNECTED || hs->mic_gain < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+						"Operation not Available");
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	gain = (dbus_uint16_t) hs->mic_gain;
+
+	dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
+					DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static DBusMessage *hs_set_gain(DBusConnection *conn,
+				DBusMessage *msg,
+				void *data, uint16_t gain,
+				char type)
+{
+	struct audio_device *device = data;
+	struct headset *hs = device->headset;
+	DBusMessage *reply;
+	int err;
+
+	if (hs->state < HEADSET_STATE_CONNECTED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	if (gain > 15)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".InvalidArgument",
+						"Must be less than or equal to 15");
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (hs->state != HEADSET_STATE_PLAYING)
+		goto done;
+
+	err = headset_send(hs, "\r\n+VG%c=%u\r\n", type, gain);
+	if (err < 0) {
+		dbus_message_unref(reply);
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(-err));
+	}
+
+done:
+	if (type == HEADSET_GAIN_SPEAKER) {
+		hs->sp_gain = gain;
+		g_dbus_emit_signal(conn, device->path,
+					AUDIO_HEADSET_INTERFACE,
+					"SpeakerGainChanged",
+					DBUS_TYPE_UINT16, &gain,
+					DBUS_TYPE_INVALID);
+	} else {
+		hs->mic_gain = gain;
+		g_dbus_emit_signal(conn, device->path,
+					AUDIO_HEADSET_INTERFACE,
+					"MicrophoneGainChanged",
+					DBUS_TYPE_UINT16, &gain,
+					DBUS_TYPE_INVALID);
+	}
+
+	return reply;
+}
+
+static DBusMessage *hs_set_speaker_gain(DBusConnection *conn,
+					DBusMessage *msg,
+					void *data)
+{
+	uint16_t gain;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
+				DBUS_TYPE_INVALID))
+		return NULL;
+
+	return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_SPEAKER);
+}
+
+static DBusMessage *hs_set_mic_gain(DBusConnection *conn,
+					DBusMessage *msg,
+					void *data)
+{
+	uint16_t gain;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
+				DBUS_TYPE_INVALID))
+		return NULL;
+
+	return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_MICROPHONE);
+}
+
+static DBusMessage *hs_get_properties(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	gboolean value;
+	const char *state;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+
+	/* Playing */
+	value = (device->headset->state == HEADSET_STATE_PLAYING);
+	dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value);
+
+	/* State */
+	state = state2str(device->headset->state);
+	if (state)
+		dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+	/* Connected */
+	value = (device->headset->state >= HEADSET_STATE_CONNECTED);
+	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+	if (!value)
+		goto done;
+
+	/* SpeakerGain */
+	dict_append_entry(&dict, "SpeakerGain",
+				DBUS_TYPE_UINT16, &device->headset->sp_gain);
+
+	/* MicrophoneGain */
+	dict_append_entry(&dict, "MicrophoneGain",
+				DBUS_TYPE_UINT16, &device->headset->mic_gain);
+
+done:
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static DBusMessage *hs_set_property(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	const char *property;
+	DBusMessageIter iter;
+	DBusMessageIter sub;
+	uint16_t gain;
+
+	if (!dbus_message_iter_init(msg, &iter))
+		return invalid_args(msg);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+		return invalid_args(msg);
+
+	dbus_message_iter_get_basic(&iter, &property);
+	dbus_message_iter_next(&iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+		return invalid_args(msg);
+	dbus_message_iter_recurse(&iter, &sub);
+
+	if (g_str_equal("SpeakerGain", property)) {
+		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
+			return invalid_args(msg);
+
+		dbus_message_iter_get_basic(&sub, &gain);
+		return hs_set_gain(conn, msg, data, gain,
+					HEADSET_GAIN_SPEAKER);
+	} else if (g_str_equal("MicrophoneGain", property)) {
+		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
+			return invalid_args(msg);
+
+		dbus_message_iter_get_basic(&sub, &gain);
+		return hs_set_gain(conn, msg, data, gain,
+					HEADSET_GAIN_MICROPHONE);
+	}
+
+	return invalid_args(msg);
+}
+static GDBusMethodTable headset_methods[] = {
+	{ "Connect",		"",	"",	hs_connect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Disconnect",		"",	"",	hs_disconnect },
+	{ "IsConnected",	"",	"b",	hs_is_connected },
+	{ "IndicateCall",	"",	"",	hs_ring },
+	{ "CancelCall",		"",	"",	hs_cancel_call },
+	{ "Play",		"",	"",	hs_play,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Stop",		"",	"",	hs_stop },
+	{ "IsPlaying",		"",	"b",	hs_is_playing,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "GetSpeakerGain",	"",	"q",	hs_get_speaker_gain,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "GetMicrophoneGain",	"",	"q",	hs_get_mic_gain,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "SetSpeakerGain",	"q",	"",	hs_set_speaker_gain,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "SetMicrophoneGain",	"q",	"",	hs_set_mic_gain,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "GetProperties",	"",	"a{sv}",hs_get_properties },
+	{ "SetProperty",	"sv",	"",	hs_set_property },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable headset_signals[] = {
+	{ "Connected",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "Disconnected",		"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "AnswerRequested",		""	},
+	{ "Stopped",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "Playing",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "SpeakerGainChanged",		"q",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "MicrophoneGainChanged",	"q",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "CallTerminated",		""	},
+	{ "PropertyChanged",		"sv"	},
+	{ NULL, NULL }
+};
+
+void headset_update(struct audio_device *dev, uint16_t svc,
+			const char *uuidstr)
+{
+	struct headset *headset = dev->headset;
+	const sdp_record_t *record;
+
+	record = btd_device_get_record(dev->btd_dev, uuidstr);
+	if (!record)
+		return;
+
+	switch (svc) {
+	case HANDSFREE_SVCLASS_ID:
+		if (headset->hfp_handle &&
+				(headset->hfp_handle != record->handle)) {
+			error("More than one HFP record found on device");
+			return;
+		}
+
+		headset->hfp_handle = record->handle;
+		break;
+
+	case HEADSET_SVCLASS_ID:
+		if (headset->hsp_handle &&
+				(headset->hsp_handle != record->handle)) {
+			error("More than one HSP record found on device");
+			return;
+		}
+
+		headset->hsp_handle = record->handle;
+
+		/* Ignore this record if we already have access to HFP */
+		if (headset->hfp_handle)
+			return;
+
+		break;
+
+	default:
+		debug("Invalid record passed to headset_update");
+		return;
+	}
+}
+
+static int headset_close_rfcomm(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+	GIOChannel *rfcomm = hs->tmp_rfcomm ? hs->tmp_rfcomm : hs->rfcomm;
+
+	if (rfcomm) {
+		g_io_channel_shutdown(rfcomm, TRUE, NULL);
+		g_io_channel_unref(rfcomm);
+		hs->tmp_rfcomm = NULL;
+		hs->rfcomm = NULL;
+	}
+
+	hs->data_start = 0;
+	hs->data_length = 0;
+
+	hs->nrec = TRUE;
+
+	return 0;
+}
+
+static void headset_free(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	if (hs->dc_timer) {
+		g_source_remove(hs->dc_timer);
+		hs->dc_timer = 0;
+	}
+
+	if (hs->dc_id)
+		device_remove_disconnect_watch(dev->btd_dev, hs->dc_id);
+
+	close_sco(dev);
+
+	headset_close_rfcomm(dev);
+
+	g_free(hs);
+	dev->headset = NULL;
+}
+
+static void path_unregister(void *data)
+{
+	struct audio_device *dev = data;
+	struct headset *hs = dev->headset;
+
+	if (hs->state > HEADSET_STATE_DISCONNECTED) {
+		debug("Headset unregistered while device was connected!");
+		headset_shutdown(dev);
+	}
+
+	debug("Unregistered interface %s on path %s",
+		AUDIO_HEADSET_INTERFACE, dev->path);
+
+	headset_free(dev);
+}
+
+void headset_unregister(struct audio_device *dev)
+{
+	g_dbus_unregister_interface(dev->conn, dev->path,
+		AUDIO_HEADSET_INTERFACE);
+}
+
+struct headset *headset_init(struct audio_device *dev, uint16_t svc,
+				const char *uuidstr)
+{
+	struct headset *hs;
+	const sdp_record_t *record;
+
+	hs = g_new0(struct headset, 1);
+	hs->rfcomm_ch = -1;
+	hs->sp_gain = -1;
+	hs->mic_gain = -1;
+	hs->search_hfp = server_is_enabled(&dev->src, HANDSFREE_SVCLASS_ID);
+	hs->hfp_active = FALSE;
+	hs->cli_active = FALSE;
+	hs->nrec = TRUE;
+
+	record = btd_device_get_record(dev->btd_dev, uuidstr);
+	if (!record)
+		goto register_iface;
+
+	switch (svc) {
+	case HANDSFREE_SVCLASS_ID:
+		hs->hfp_handle = record->handle;
+		break;
+
+	case HEADSET_SVCLASS_ID:
+		hs->hsp_handle = record->handle;
+		break;
+
+	default:
+		debug("Invalid record passed to headset_init");
+		g_free(hs);
+		return NULL;
+	}
+
+register_iface:
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE,
+					headset_methods, headset_signals, NULL,
+					dev, path_unregister)) {
+		g_free(hs);
+		return NULL;
+	}
+
+	debug("Registered interface %s on path %s",
+		AUDIO_HEADSET_INTERFACE, dev->path);
+
+	return hs;
+}
+
+uint32_t headset_config_init(GKeyFile *config)
+{
+	GError *err = NULL;
+	char *str;
+
+	/* Use the default values if there is no config file */
+	if (config == NULL)
+		return ag.features;
+
+	str = g_key_file_get_string(config, "General", "SCORouting",
+					&err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		if (strcmp(str, "PCM") == 0)
+			sco_hci = FALSE;
+		else if (strcmp(str, "HCI") == 0)
+			sco_hci = TRUE;
+		else
+			error("Invalid Headset Routing value: %s", str);
+		g_free(str);
+	}
+
+	return ag.features;
+}
+
+static gboolean hs_dc_timeout(struct audio_device *dev)
+{
+	headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+	return FALSE;
+}
+
+gboolean headset_cancel_stream(struct audio_device *dev, unsigned int id)
+{
+	struct headset *hs = dev->headset;
+	struct pending_connect *p = hs->pending;
+	GSList *l;
+	struct connect_cb *cb = NULL;
+
+	if (!p)
+		return FALSE;
+
+	for (l = p->callbacks; l != NULL; l = l->next) {
+		struct connect_cb *tmp = l->data;
+
+		if (tmp->id == id) {
+			cb = tmp;
+			break;
+		}
+	}
+
+	if (!cb)
+		return FALSE;
+
+	p->callbacks = g_slist_remove(p->callbacks, cb);
+	g_free(cb);
+
+	if (p->callbacks || p->msg)
+		return TRUE;
+
+	if (hs->auto_dc) {
+		if (hs->rfcomm)
+			hs->dc_timer = g_timeout_add_seconds(DC_TIMEOUT,
+						(GSourceFunc) hs_dc_timeout,
+						dev);
+		else
+			headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+	}
+
+	return TRUE;
+}
+
+static gboolean dummy_connect_complete(struct audio_device *dev)
+{
+	pending_connect_finalize(dev);
+	return FALSE;
+}
+
+unsigned int headset_request_stream(struct audio_device *dev,
+					headset_stream_cb_t cb,
+					void *user_data)
+{
+	struct headset *hs = dev->headset;
+	unsigned int id;
+
+	if (hs->state == HEADSET_STATE_PLAYING) {
+		id = connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data);
+		g_idle_add((GSourceFunc) dummy_connect_complete, dev);
+		return id;
+	}
+
+	if (hs->dc_timer) {
+		g_source_remove(hs->dc_timer);
+		hs->dc_timer = 0;
+	}
+
+	if (hs->state == HEADSET_STATE_CONNECT_IN_PROGRESS ||
+			hs->state == HEADSET_STATE_PLAY_IN_PROGRESS)
+		return connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data);
+
+	if (hs->rfcomm == NULL) {
+		if (rfcomm_connect(dev, cb, user_data, &id) < 0)
+			return 0;
+		hs->auto_dc = TRUE;
+	} else if (sco_connect(dev, cb, user_data, &id) < 0)
+		return 0;
+
+	hs->pending->target_state = HEADSET_STATE_PLAYING;
+
+	return id;
+}
+
+unsigned int headset_config_stream(struct audio_device *dev,
+					gboolean auto_dc,
+					headset_stream_cb_t cb,
+					void *user_data)
+{
+	struct headset *hs = dev->headset;
+	unsigned int id = 0;
+
+	if (hs->dc_timer) {
+		g_source_remove(hs->dc_timer);
+		hs->dc_timer = 0;
+	}
+
+	if (hs->state == HEADSET_STATE_CONNECT_IN_PROGRESS)
+		return connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb,
+					user_data);
+
+	if (hs->rfcomm)
+		goto done;
+
+	if (rfcomm_connect(dev, cb, user_data, &id) < 0)
+		return 0;
+
+	hs->auto_dc = auto_dc;
+	hs->pending->target_state = HEADSET_STATE_CONNECTED;
+
+	return id;
+
+done:
+	id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data);
+	g_idle_add((GSourceFunc) dummy_connect_complete, dev);
+	return id;
+}
+
+unsigned int headset_suspend_stream(struct audio_device *dev,
+					headset_stream_cb_t cb,
+					void *user_data)
+{
+	struct headset *hs = dev->headset;
+	unsigned int id;
+
+	if (hs->dc_timer) {
+		g_source_remove(hs->dc_timer);
+		hs->dc_timer = 0;
+	}
+
+	headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+	id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data);
+	g_idle_add((GSourceFunc) dummy_connect_complete, dev);
+
+	return id;
+}
+
+gboolean get_hfp_active(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	return hs->hfp_active;
+}
+
+void set_hfp_active(struct audio_device *dev, gboolean active)
+{
+	struct headset *hs = dev->headset;
+
+	hs->hfp_active = active;
+}
+
+GIOChannel *headset_get_rfcomm(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	return hs->tmp_rfcomm;
+}
+
+int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
+{
+	struct headset *hs = dev->headset;
+
+	if (hs->tmp_rfcomm)
+		return -EALREADY;
+
+	hs->tmp_rfcomm = g_io_channel_ref(io);
+
+	return 0;
+}
+
+int headset_connect_sco(struct audio_device *dev, GIOChannel *io)
+{
+	struct headset *hs = dev->headset;
+
+	if (hs->sco)
+		return -EISCONN;
+
+	hs->sco = g_io_channel_ref(io);
+
+	if (hs->pending_ring) {
+		ring_timer_cb(NULL);
+		ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL,
+						ring_timer_cb,
+						NULL);
+		hs->pending_ring = FALSE;
+	}
+
+	return 0;
+}
+
+static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
+				void *user_data)
+{
+	struct audio_device *device = user_data;
+
+	info("Headset: disconnect %s", device->path);
+
+	headset_shutdown(device);
+}
+
+void headset_set_state(struct audio_device *dev, headset_state_t state)
+{
+	struct headset *hs = dev->headset;
+	gboolean value;
+	const char *state_str;
+	headset_state_t old_state = hs->state;
+	GSList *l;
+
+	if (old_state == state)
+		return;
+
+	state_str = state2str(state);
+
+	switch (state) {
+	case HEADSET_STATE_DISCONNECTED:
+		value = FALSE;
+		close_sco(dev);
+		headset_close_rfcomm(dev);
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE, "State",
+					DBUS_TYPE_STRING, &state_str);
+		g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE,
+					"Disconnected",
+					DBUS_TYPE_INVALID);
+		if (hs->state > HEADSET_STATE_CONNECT_IN_PROGRESS)
+			emit_property_changed(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE, "Connected",
+					DBUS_TYPE_BOOLEAN, &value);
+
+		telephony_device_disconnected(dev);
+		active_devices = g_slist_remove(active_devices, dev);
+		device_remove_disconnect_watch(dev->btd_dev, hs->dc_id);
+		hs->dc_id = 0;
+		break;
+	case HEADSET_STATE_CONNECT_IN_PROGRESS:
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE, "State",
+					DBUS_TYPE_STRING, &state_str);
+		break;
+	case HEADSET_STATE_CONNECTED:
+		close_sco(dev);
+		if (hs->state != HEADSET_STATE_PLAY_IN_PROGRESS)
+			emit_property_changed(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE, "State",
+					DBUS_TYPE_STRING, &state_str);
+		if (hs->state < state) {
+			if (ag.features & AG_FEATURE_INBAND_RINGTONE)
+				hs->inband_ring = TRUE;
+			else
+				hs->inband_ring = FALSE;
+			g_dbus_emit_signal(dev->conn, dev->path,
+						AUDIO_HEADSET_INTERFACE,
+						"Connected",
+						DBUS_TYPE_INVALID);
+			value = TRUE;
+			emit_property_changed(dev->conn, dev->path,
+						AUDIO_HEADSET_INTERFACE,
+						"Connected",
+						DBUS_TYPE_BOOLEAN, &value);
+			active_devices = g_slist_append(active_devices, dev);
+			telephony_device_connected(dev);
+			hs->dc_id = device_add_disconnect_watch(dev->btd_dev,
+								disconnect_cb,
+								dev, NULL);
+		} else if (hs->state == HEADSET_STATE_PLAYING) {
+			value = FALSE;
+			g_dbus_emit_signal(dev->conn, dev->path,
+						AUDIO_HEADSET_INTERFACE,
+						"Stopped",
+						DBUS_TYPE_INVALID);
+			emit_property_changed(dev->conn, dev->path,
+						AUDIO_HEADSET_INTERFACE,
+						"Playing",
+						DBUS_TYPE_BOOLEAN, &value);
+		}
+		break;
+	case HEADSET_STATE_PLAY_IN_PROGRESS:
+		break;
+	case HEADSET_STATE_PLAYING:
+		value = TRUE;
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE, "State",
+					DBUS_TYPE_STRING, &state_str);
+		hs->sco_id = g_io_add_watch(hs->sco,
+					G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+					(GIOFunc) sco_cb, dev);
+
+		g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE, "Playing",
+					DBUS_TYPE_INVALID);
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_HEADSET_INTERFACE, "Playing",
+					DBUS_TYPE_BOOLEAN, &value);
+
+		if (hs->sp_gain >= 0)
+			headset_send(hs, "\r\n+VGS=%u\r\n", hs->sp_gain);
+		if (hs->mic_gain >= 0)
+			headset_send(hs, "\r\n+VGM=%u\r\n", hs->mic_gain);
+		break;
+	}
+
+	hs->state = state;
+
+	debug("State changed %s: %s -> %s", dev->path, str_state[old_state],
+		str_state[state]);
+
+	for (l = headset_callbacks; l != NULL; l = l->next) {
+		struct headset_state_callback *cb = l->data;
+		cb->cb(dev, old_state, state, cb->user_data);
+	}
+}
+
+headset_state_t headset_get_state(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	return hs->state;
+}
+
+int headset_get_channel(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	return hs->rfcomm_ch;
+}
+
+gboolean headset_is_active(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	if (hs->state != HEADSET_STATE_DISCONNECTED)
+		return TRUE;
+
+	return FALSE;
+}
+
+headset_lock_t headset_get_lock(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	return hs->lock;
+}
+
+gboolean headset_lock(struct audio_device *dev, headset_lock_t lock)
+{
+	struct headset *hs = dev->headset;
+
+	if (hs->lock & lock)
+		return FALSE;
+
+	hs->lock |= lock;
+
+	return TRUE;
+}
+
+gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock)
+{
+	struct headset *hs = dev->headset;
+
+	if (!(hs->lock & lock))
+		return FALSE;
+
+	hs->lock &= ~lock;
+
+	if (hs->lock)
+		return TRUE;
+
+	if (hs->state == HEADSET_STATE_PLAYING)
+		headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+	if (hs->auto_dc) {
+		if (hs->state == HEADSET_STATE_CONNECTED)
+			hs->dc_timer = g_timeout_add_seconds(DC_TIMEOUT,
+						(GSourceFunc) hs_dc_timeout,
+						dev);
+		else
+			headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+	}
+
+	return TRUE;
+}
+
+gboolean headset_suspend(struct audio_device *dev, void *data)
+{
+	return TRUE;
+}
+
+gboolean headset_play(struct audio_device *dev, void *data)
+{
+	return TRUE;
+}
+
+int headset_get_sco_fd(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	if (!hs->sco)
+		return -1;
+
+	return g_io_channel_unix_get_fd(hs->sco);
+}
+
+gboolean headset_get_nrec(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	return hs->nrec;
+}
+
+gboolean headset_get_sco_hci(struct audio_device *dev)
+{
+	return sco_hci;
+}
+
+void headset_shutdown(struct audio_device *dev)
+{
+	struct pending_connect *p = dev->headset->pending;
+
+	if (p && p->msg)
+		error_connection_attempt_failed(dev->conn, p->msg, ECANCELED);
+
+	pending_connect_finalize(dev);
+	headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+int telephony_event_ind(int index)
+{
+	if (!active_devices)
+		return -ENODEV;
+
+	if (!ag.er_ind) {
+		debug("telephony_report_event called but events are disabled");
+		return -EINVAL;
+	}
+
+	send_foreach_headset(active_devices, hfp_cmp,
+				"\r\n+CIEV: %d,%d\r\n", index + 1,
+				ag.indicators[index].val);
+
+	return 0;
+}
+
+int telephony_response_and_hold_ind(int rh)
+{
+	if (!active_devices)
+		return -ENODEV;
+
+	ag.rh = rh;
+
+	/* If we aren't in any response and hold state don't send anything */
+	if (ag.rh < 0)
+		return 0;
+
+	send_foreach_headset(active_devices, hfp_cmp, "\r\n+BTRH: %d\r\n",
+				ag.rh);
+
+	return 0;
+}
+
+int telephony_incoming_call_ind(const char *number, int type)
+{
+	struct audio_device *dev;
+	struct headset *hs;
+
+	if (!active_devices)
+		return -ENODEV;
+
+	/* Get the latest connected device */
+	dev = active_devices->data;
+	hs = dev->headset;
+
+	if (ag.ring_timer) {
+		debug("telephony_incoming_call_ind: already calling");
+		return -EBUSY;
+	}
+
+	/* With HSP 1.2 the RING messages should *not* be sent if inband
+	 * ringtone is being used */
+	if (!hs->hfp_active && hs->inband_ring)
+		return 0;
+
+	g_free(ag.number);
+	ag.number = g_strdup(number);
+	ag.number_type = type;
+
+	if (hs->inband_ring && hs->hfp_active &&
+					hs->state != HEADSET_STATE_PLAYING) {
+		hs->pending_ring = TRUE;
+		return 0;
+	}
+
+	ring_timer_cb(NULL);
+	ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
+						NULL);
+
+	return 0;
+}
+
+int telephony_calling_stopped_ind(void)
+{
+	struct audio_device *dev;
+
+	if (ag.ring_timer) {
+		g_source_remove(ag.ring_timer);
+		ag.ring_timer = 0;
+	}
+
+	if (!active_devices)
+		return 0;
+
+	/* In case SCO isn't fully up yet */
+	dev = active_devices->data;
+
+	if (!dev->headset->pending_ring && !ag.ring_timer)
+		return -EINVAL;
+
+	dev->headset->pending_ring = FALSE;
+
+	return 0;
+}
+
+int telephony_ready_ind(uint32_t features,
+			const struct indicator *indicators, int rh,
+			const char *chld)
+{
+	ag.telephony_ready = TRUE;
+	ag.features = features;
+	ag.indicators = indicators;
+	ag.rh = rh;
+	ag.chld = g_strdup(chld);
+
+	debug("Telephony plugin initialized");
+
+	print_ag_features(ag.features);
+
+	return 0;
+}
+
+int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
+					int mprty, const char *number,
+					int type)
+{
+	if (!active_devices)
+		return -ENODEV;
+
+	if (number && strlen(number) > 0)
+		send_foreach_headset(active_devices, hfp_cmp,
+				"\r\n+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n",
+				idx, dir, status, mode, mprty, number, type);
+	else
+		send_foreach_headset(active_devices, hfp_cmp,
+					"\r\n+CLCC: %d,%d,%d,%d,%d\r\n",
+					idx, dir, status, mode, mprty);
+
+	return 0;
+}
+
+int telephony_subscriber_number_ind(const char *number, int type, int service)
+{
+	if (!active_devices)
+		return -ENODEV;
+
+	send_foreach_headset(active_devices, hfp_cmp,
+				"\r\n+CNUM: ,%s,%d,,%d\r\n",
+				number, type, service);
+
+	return 0;
+}
+
+static int cwa_cmp(struct headset *hs)
+{
+	if (!hs->hfp_active)
+		return -1;
+
+	if (hs->cwa_enabled)
+		return 0;
+	else
+		return -1;
+}
+
+int telephony_call_waiting_ind(const char *number, int type)
+{
+	if (!active_devices)
+		return -ENODEV;
+
+	send_foreach_headset(active_devices, cwa_cmp,
+				"\r\n+CCWA: \"%s\",%d\r\n",
+				number, type);
+
+	return 0;
+}
+
+unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data)
+{
+	struct headset_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct headset_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	headset_callbacks = g_slist_append(headset_callbacks, state_cb);
+
+	return state_cb->id;
+}
+
+gboolean headset_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = headset_callbacks; l != NULL; l = l->next) {
+		struct headset_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			headset_callbacks = g_slist_remove(headset_callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff --git a/audio/headset.h b/audio/headset.h
new file mode 100644
index 0000000..ace1edf
--- /dev/null
+++ b/audio/headset.h
@@ -0,0 +1,102 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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_CONNECT_IN_PROGRESS,
+	HEADSET_STATE_CONNECTED,
+	HEADSET_STATE_PLAY_IN_PROGRESS,
+	HEADSET_STATE_PLAYING
+} headset_state_t;
+
+typedef enum {
+	HEADSET_LOCK_READ = 1,
+	HEADSET_LOCK_WRITE = 1 << 1,
+} headset_lock_t;
+
+typedef void (*headset_state_cb) (struct audio_device *dev,
+					headset_state_t old_state,
+					headset_state_t new_state,
+					void *user_data);
+
+unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data);
+gboolean headset_remove_state_cb(unsigned int id);
+
+typedef void (*headset_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data);
+
+GIOChannel *headset_get_rfcomm(struct audio_device *dev);
+
+struct headset *headset_init(struct audio_device *dev, uint16_t svc,
+				const char *uuidstr);
+
+void headset_unregister(struct audio_device *dev);
+
+uint32_t headset_config_init(GKeyFile *config);
+
+void headset_update(struct audio_device *dev, uint16_t svc,
+			const char *uuidstr);
+
+unsigned int headset_config_stream(struct audio_device *dev,
+					gboolean auto_dc,
+					headset_stream_cb_t cb,
+					void *user_data);
+unsigned int headset_request_stream(struct audio_device *dev,
+					headset_stream_cb_t cb,
+					void *user_data);
+unsigned int headset_suspend_stream(struct audio_device *dev,
+					headset_stream_cb_t cb,
+					void *user_data);
+gboolean headset_cancel_stream(struct audio_device *dev, unsigned int id);
+
+gboolean get_hfp_active(struct audio_device *dev);
+void set_hfp_active(struct audio_device *dev, gboolean active);
+
+void headset_set_authorized(struct audio_device *dev);
+int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int headset_connect_sco(struct audio_device *dev, GIOChannel *io);
+
+headset_state_t headset_get_state(struct audio_device *dev);
+void headset_set_state(struct audio_device *dev, headset_state_t state);
+
+int headset_get_channel(struct audio_device *dev);
+
+int headset_get_sco_fd(struct audio_device *dev);
+gboolean headset_get_nrec(struct audio_device *dev);
+gboolean headset_get_sco_hci(struct audio_device *dev);
+
+gboolean headset_is_active(struct audio_device *dev);
+
+headset_lock_t headset_get_lock(struct audio_device *dev);
+gboolean headset_lock(struct audio_device *dev, headset_lock_t lock);
+gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock);
+gboolean headset_suspend(struct audio_device *dev, void *data);
+gboolean headset_play(struct audio_device *dev, void *data);
+void headset_shutdown(struct audio_device *dev);
diff --git a/audio/ipc.c b/audio/ipc.c
new file mode 100644
index 0000000..28569dc
--- /dev/null
+++ b/audio/ipc.c
@@ -0,0 +1,133 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..2e170f5
--- /dev/null
+++ b/audio/ipc.h
@@ -0,0 +1,349 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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_CAPABILITIES_TRANSPORT_A2DP	0
+#define BT_CAPABILITIES_TRANSPORT_SCO	1
+#define BT_CAPABILITIES_TRANSPORT_ANY	2
+
+#define BT_CAPABILITIES_ACCESS_MODE_READ	1
+#define BT_CAPABILITIES_ACCESS_MODE_WRITE	2
+#define BT_CAPABILITIES_ACCESS_MODE_READWRITE	3
+
+#define BT_FLAG_AUTOCONNECT	1
+
+struct bt_get_capabilities_req {
+	bt_audio_msg_header_t	h;
+	char			source[18];	/* Address of the local Device */
+	char			destination[18];/* Address of the remote Device */
+	char			object[128];	/* DBus object path */
+	uint8_t			transport;	/* Requested transport */
+	uint8_t			flags;		/* Requested flags */
+	uint8_t			seid;		/* Requested capability configuration */
+} __attribute__ ((packed));
+
+/**
+ * SBC Codec parameters as per A2DP profile 1.0 § 4.3
+ */
+
+/* A2DP seid are 6 bytes long so HSP/HFP are assigned to 7-8 bits */
+#define BT_A2DP_SEID_RANGE			(1 << 6) - 1
+
+#define BT_A2DP_SBC_SOURCE			0x00
+#define BT_A2DP_SBC_SINK			0x01
+#define BT_A2DP_MPEG12_SOURCE			0x02
+#define BT_A2DP_MPEG12_SINK			0x03
+#define BT_A2DP_MPEG24_SOURCE			0x04
+#define BT_A2DP_MPEG24_SINK			0x05
+#define BT_A2DP_ATRAC_SOURCE			0x06
+#define BT_A2DP_ATRAC_SINK			0x07
+#define BT_A2DP_UNKNOWN_SOURCE			0x08
+#define BT_A2DP_UNKNOWN_SINK			0x09
+
+#define BT_SBC_SAMPLING_FREQ_16000		(1 << 3)
+#define BT_SBC_SAMPLING_FREQ_32000		(1 << 2)
+#define BT_SBC_SAMPLING_FREQ_44100		(1 << 1)
+#define BT_SBC_SAMPLING_FREQ_48000		1
+
+#define BT_A2DP_CHANNEL_MODE_MONO		(1 << 3)
+#define BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL	(1 << 2)
+#define BT_A2DP_CHANNEL_MODE_STEREO		(1 << 1)
+#define BT_A2DP_CHANNEL_MODE_JOINT_STEREO	1
+
+#define BT_A2DP_BLOCK_LENGTH_4			(1 << 3)
+#define BT_A2DP_BLOCK_LENGTH_8			(1 << 2)
+#define BT_A2DP_BLOCK_LENGTH_12			(1 << 1)
+#define BT_A2DP_BLOCK_LENGTH_16			1
+
+#define BT_A2DP_SUBBANDS_4			(1 << 1)
+#define BT_A2DP_SUBBANDS_8			1
+
+#define BT_A2DP_ALLOCATION_SNR			(1 << 1)
+#define BT_A2DP_ALLOCATION_LOUDNESS		1
+
+#define BT_MPEG_SAMPLING_FREQ_16000		(1 << 5)
+#define BT_MPEG_SAMPLING_FREQ_22050		(1 << 4)
+#define BT_MPEG_SAMPLING_FREQ_24000		(1 << 3)
+#define BT_MPEG_SAMPLING_FREQ_32000		(1 << 2)
+#define BT_MPEG_SAMPLING_FREQ_44100		(1 << 1)
+#define BT_MPEG_SAMPLING_FREQ_48000		1
+
+#define BT_MPEG_LAYER_1				(1 << 2)
+#define BT_MPEG_LAYER_2				(1 << 1)
+#define BT_MPEG_LAYER_3				1
+
+#define BT_HFP_CODEC_PCM			0x00
+
+#define BT_PCM_FLAG_NREC			0x01
+#define BT_PCM_FLAG_PCM_ROUTING			0x02
+
+#define BT_WRITE_LOCK				(1 << 1)
+#define BT_READ_LOCK				1
+
+typedef struct {
+	uint8_t seid;
+	uint8_t transport;
+	uint8_t type;
+	uint8_t length;
+	uint8_t configured;
+	uint8_t lock;
+	uint8_t data[0];
+} __attribute__ ((packed)) codec_capabilities_t;
+
+typedef struct {
+	codec_capabilities_t capability;
+	uint8_t channel_mode;
+	uint8_t frequency;
+	uint8_t allocation_method;
+	uint8_t subbands;
+	uint8_t block_length;
+	uint8_t min_bitpool;
+	uint8_t max_bitpool;
+} __attribute__ ((packed)) sbc_capabilities_t;
+
+typedef struct {
+	codec_capabilities_t capability;
+	uint8_t channel_mode;
+	uint8_t crc;
+	uint8_t layer;
+	uint8_t frequency;
+	uint8_t mpf;
+	uint16_t bitrate;
+} __attribute__ ((packed)) mpeg_capabilities_t;
+
+typedef struct {
+	codec_capabilities_t capability;
+	uint8_t flags;
+	uint16_t sampling_rate;
+} __attribute__ ((packed)) pcm_capabilities_t;
+
+struct bt_get_capabilities_rsp {
+	bt_audio_msg_header_t	h;
+	char			source[18];	/* Address of the local Device */
+	char			destination[18];/* Address of the remote Device */
+	char			object[128];	/* DBus object path */
+	uint8_t			data[0];	/* First codec_capabilities_t */
+} __attribute__ ((packed));
+
+struct bt_open_req {
+	bt_audio_msg_header_t	h;
+	char			source[18];	/* Address of the local Device */
+	char			destination[18];/* Address of the remote Device */
+	char			object[128];	/* DBus object path */
+	uint8_t			seid;		/* Requested capability configuration to lock */
+	uint8_t			lock;		/* Requested lock */
+} __attribute__ ((packed));
+
+struct bt_open_rsp {
+	bt_audio_msg_header_t	h;
+	char			source[18];	/* Address of the local Device */
+	char			destination[18];/* Address of the remote Device */
+	char			object[128];	/* DBus object path */
+} __attribute__ ((packed));
+
+struct bt_set_configuration_req {
+	bt_audio_msg_header_t	h;
+	codec_capabilities_t	codec;		/* Requested codec */
+} __attribute__ ((packed));
+
+struct bt_set_configuration_rsp {
+	bt_audio_msg_header_t	h;
+	uint16_t		link_mtu;	/* Max length that transport supports */
+} __attribute__ ((packed));
+
+#define BT_STREAM_ACCESS_READ		0
+#define BT_STREAM_ACCESS_WRITE		1
+#define BT_STREAM_ACCESS_READWRITE	2
+struct bt_start_stream_req {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+struct bt_start_stream_rsp {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+/* This message is followed by one byte of data containing the stream data fd
+   as ancilliary data */
+struct bt_new_stream_ind {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+struct bt_stop_stream_req {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+struct bt_stop_stream_rsp {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+struct bt_close_req {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+struct bt_close_rsp {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+struct bt_suspend_stream_ind {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+struct bt_resume_stream_ind {
+	bt_audio_msg_header_t	h;
+} __attribute__ ((packed));
+
+#define BT_CONTROL_KEY_POWER			0x40
+#define BT_CONTROL_KEY_VOL_UP			0x41
+#define BT_CONTROL_KEY_VOL_DOWN			0x42
+#define BT_CONTROL_KEY_MUTE			0x43
+#define BT_CONTROL_KEY_PLAY			0x44
+#define BT_CONTROL_KEY_STOP			0x45
+#define BT_CONTROL_KEY_PAUSE			0x46
+#define BT_CONTROL_KEY_RECORD			0x47
+#define BT_CONTROL_KEY_REWIND			0x48
+#define BT_CONTROL_KEY_FAST_FORWARD		0x49
+#define BT_CONTROL_KEY_EJECT			0x4A
+#define BT_CONTROL_KEY_FORWARD			0x4B
+#define BT_CONTROL_KEY_BACKWARD			0x4C
+
+struct bt_control_req {
+	bt_audio_msg_header_t	h;
+	uint8_t			mode;		/* Control Mode */
+	uint8_t			key;		/* Control Key */
+} __attribute__ ((packed));
+
+struct bt_control_rsp {
+	bt_audio_msg_header_t	h;
+	uint8_t			mode;		/* Control Mode */
+	uint8_t			key;		/* Control Key */
+} __attribute__ ((packed));
+
+struct bt_control_ind {
+	bt_audio_msg_header_t	h;
+	uint8_t			mode;		/* Control Mode */
+	uint8_t			key;		/* Control Key */
+} __attribute__ ((packed));
+
+/* Function declaration */
+
+/* Opens a connection to the audio service: return a socket descriptor */
+int bt_audio_service_open(void);
+
+/* Closes a connection to the audio service */
+int bt_audio_service_close(int sk);
+
+/* Receives stream data file descriptor : must be called after a
+BT_STREAMFD_IND message is returned */
+int bt_audio_service_get_data_fd(int sk);
+
+/* Human readable message type string */
+const char *bt_audio_strtype(uint8_t type);
+
+/* Human readable message name string */
+const char *bt_audio_strname(uint8_t name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BT_AUDIOCLIENT_H */
diff --git a/audio/ipctest-a2dp-easy.test b/audio/ipctest-a2dp-easy.test
new file mode 100644
index 0000000..ba62d93
--- /dev/null
+++ b/audio/ipctest-a2dp-easy.test
@@ -0,0 +1,8 @@
+profile a2dp
+init_bt
+init_profile
+start_stream
+sleep 2
+stop_stream
+quit
+
diff --git a/audio/ipctest-a2dp-resume-fast.test b/audio/ipctest-a2dp-resume-fast.test
new file mode 100644
index 0000000..bbc99e2
--- /dev/null
+++ b/audio/ipctest-a2dp-resume-fast.test
@@ -0,0 +1,82 @@
+#!./ipctest
+
+profile a2dp
+init_bt
+
+init_profile
+
+start_stream
+sleep 1
+stop_stream
+
+stop_stream
+start_stream
+
+stop_stream
+start_stream
+
+stop_stream
+start_stream
+
+stop_stream
+start_stream
+
+stop_stream
+start_stream
+
+stop_stream
+stop_stream
+stop_stream
+
+start_stream
+start_stream
+start_stream
+
+stop_stream
+
+sleep 1
+
+start_stream
+start_stream
+start_stream
+stop_stream
+stop_stream
+
+start_stream
+sleep 1
+stop_stream
+
+start_stream
+stop_stream
+
+start_stream
+stop_stream
+
+shutdown_bt
+
+init_bt
+
+init_profile
+
+start_stream
+stop_stream
+stop_stream
+start_stream
+stop_stream
+start_stream
+stop_stream
+start_stream
+stop_stream
+start_stream
+stop_stream
+start_stream
+stop_stream
+stop_stream
+stop_stream
+
+start_stream
+start_stream
+start_stream
+stop_stream
+
+quit
diff --git a/audio/ipctest-hsp-a2dp-switch.test b/audio/ipctest-hsp-a2dp-switch.test
new file mode 100644
index 0000000..e99878f
--- /dev/null
+++ b/audio/ipctest-hsp-a2dp-switch.test
@@ -0,0 +1,50 @@
+init_bt
+
+profile a2dp
+init_profile
+start_stream
+sleep 2
+stop_stream
+start_stream
+sleep 2
+stop_stream
+
+shutdown_bt
+init_bt
+
+profile hsp
+init_profile
+start_stream
+sleep 2
+stop_stream
+start_stream
+sleep 2
+stop_stream
+
+shutdown_bt
+init_bt
+
+profile a2dp
+init_profile
+start_stream
+sleep 2
+stop_stream
+start_stream
+sleep 2
+stop_stream
+
+shutdown_bt
+init_bt
+
+profile hsp
+init_profile
+start_stream
+sleep 2
+stop_stream
+start_stream
+sleep 2
+stop_stream
+
+shutdown_bt
+
+quit
diff --git a/audio/ipctest-hsp-easy.test b/audio/ipctest-hsp-easy.test
new file mode 100644
index 0000000..0756a78
--- /dev/null
+++ b/audio/ipctest-hsp-easy.test
@@ -0,0 +1,7 @@
+profile hsp
+init_bt
+init_profile
+start_stream
+sleep 2
+stop_stream
+quit
diff --git a/audio/ipctest-init-shutdown.test b/audio/ipctest-init-shutdown.test
new file mode 100644
index 0000000..578883a
--- /dev/null
+++ b/audio/ipctest-init-shutdown.test
@@ -0,0 +1,59 @@
+#!./ipctest
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+
+init_bt
+sleep 1
+shutdown_bt
+
+init_bt
+sleep 1
+shutdown_bt
+
+init_bt
+sleep 1
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+init_bt
+shutdown_bt
+
+quit
+
diff --git a/audio/ipctest.c b/audio/ipctest.c
new file mode 100644
index 0000000..d18f62e
--- /dev/null
+++ b/audio/ipctest.c
@@ -0,0 +1,1131 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2009  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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");
+
+		if (u->address)
+			free(u->address);
+
+		u->address = address;
+		DBG("bdaddr %s", u->address);
+	}
+
+	IF_CMD(profile) {
+		char *profile = NULL;
+
+		if (sscanf(line, "%*s %as", &profile) != 1)
+			DBG("set with profile [hsp|a2dp]");
+		if (strncmp(profile, "hsp", 4) == 0) {
+			u->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+		} else if (strncmp(profile, "a2dp", 5) == 0) {
+			u->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+		} else {
+			DBG("set with profile [hsp|a2dp]");
+		}
+
+		if (profile)
+			free(profile);
+		DBG("profile %s", u->transport == BT_CAPABILITIES_TRANSPORT_SCO ?
+			"hsp" : "a2dp");
+	}
+
+	if (!success && strlen(line) != 0) {
+		DBG("%s, unknown command", line);
+	}
+
+	printf(">>> ");
+	fflush(stdout);
+	return TRUE;
+}
+
+
+static void show_usage(char* prgname)
+{
+	printf("%s: ipctest [--interactive] BDADDR\n", basename(prgname));
+}
+
+static void sig_term(int sig)
+{
+	g_main_loop_quit(main_loop);
+}
+
+int main(int argc, char *argv[])
+{
+	if (argc < 2) {
+		show_usage(argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	assert(main_loop = g_main_loop_new(NULL, FALSE));
+
+	if (strncmp("--interactive", argv[1], 14) == 0) {
+		if (argc < 3) {
+			show_usage(argv[0]);
+			exit(EXIT_FAILURE);
+		}
+
+		data.address = strdup(argv[2]);
+
+		signal(SIGTERM, sig_term);
+		signal(SIGINT, sig_term);
+
+		assert(data.gin = g_io_channel_unix_new(fileno(stdin)));
+
+		data.gin_watch = g_io_add_watch(data.gin,
+			G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, &data);
+
+		printf(">>> ");
+		fflush(stdout);
+
+		g_main_loop_run(main_loop);
+
+	} else {
+		data.address = strdup(argv[1]);
+
+		assert(init_bt(&data) == 0);
+
+		assert(init_profile(&data) == 0);
+
+		assert(start_stream(&data) == 0);
+
+		g_main_loop_run(main_loop);
+
+		assert(stop_stream(&data) == 0);
+
+		shutdown_bt(&data);
+	}
+
+	g_main_loop_unref(main_loop);
+
+	printf("\nExiting\n");
+
+	exit(EXIT_SUCCESS);
+
+	return 0;
+}
diff --git a/audio/liba2dp.c b/audio/liba2dp.c
new file mode 100755
index 0000000..7403886
--- /dev/null
+++ b/audio/liba2dp.c
@@ -0,0 +1,1204 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2008  Marcel Holtmann <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 <signal.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include <netinet/in.h>
+#include <sys/poll.h>
+#include <sys/prctl.h>
+
+#include "ipc.h"
+#include "sbc.h"
+#include "rtp.h"
+#include "liba2dp.h"
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "A2DP"
+#include <utils/Log.h>
+
+#define ENABLE_DEBUG
+/* #define ENABLE_VERBOSE */
+/* #define ENABLE_TIMING */
+
+#define BUFFER_SIZE 2048
+
+#ifdef ENABLE_DEBUG
+#define DBG LOGD
+#else
+#define DBG(fmt, arg...)
+#endif
+
+#ifdef ENABLE_VERBOSE
+#define VDBG LOGV
+#else
+#define VDBG(fmt, arg...)
+#endif
+
+#ifndef MIN
+# define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+#ifndef MAX
+# define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+#define ERR LOGE
+
+/* Number of packets to buffer in the stream socket */
+#define PACKET_BUFFER_COUNT		10
+
+/* timeout in milliseconds to prevent poll() from hanging indefinitely */
+#define POLL_TIMEOUT			1000
+
+/* timeout in milliseconds for a2dp_write */
+#define WRITE_TIMEOUT			1000
+
+/* timeout in seconds for command socket recv() */
+#define RECV_TIMEOUT            5
+
+
+typedef enum {
+	A2DP_STATE_NONE = 0,
+	A2DP_STATE_INITIALIZED,
+	A2DP_STATE_CONFIGURING,
+	A2DP_STATE_CONFIGURED,
+	A2DP_STATE_STARTING,
+	A2DP_STATE_STARTED,
+	A2DP_STATE_STOPPING,
+} a2dp_state_t;
+
+typedef enum {
+	A2DP_CMD_NONE = 0,
+	A2DP_CMD_INIT,
+	A2DP_CMD_CONFIGURE,
+	A2DP_CMD_START,
+	A2DP_CMD_STOP,
+	A2DP_CMD_QUIT,
+} a2dp_command_t;
+
+struct bluetooth_data {
+	int link_mtu;					/* MTU for transport channel */
+	struct pollfd stream;			/* Audio stream filedescriptor */
+	struct pollfd server;			/* Audio daemon filedescriptor */
+	a2dp_state_t state;				/* Current A2DP state */
+	a2dp_command_t command;			/* Current command for a2dp_thread */
+	pthread_t thread;
+	pthread_mutex_t mutex;
+	int started;
+	pthread_cond_t thread_start;
+	pthread_cond_t thread_wait;
+	pthread_cond_t client_wait;
+
+	sbc_capabilities_t sbc_capabilities;
+	sbc_t sbc;				/* Codec data */
+	int	frame_duration;			/* length of an SBC frame in microseconds */
+	int codesize;				/* SBC codesize */
+	int samples;				/* Number of encoded samples */
+	uint8_t buffer[BUFFER_SIZE];		/* Codec transfer buffer */
+	int count;				/* Codec transfer buffer counter */
+
+	int nsamples;				/* Cumulative number of codec samples */
+	uint16_t seq_num;			/* Cumulative packet sequence */
+	int frame_count;			/* Current frames in buffer*/
+
+	char	address[20];
+	int	rate;
+	int	channels;
+
+	/* used for pacing our writes to the output socket */
+	uint64_t	next_write;
+};
+
+static uint64_t get_microseconds()
+{
+	struct timeval now;
+	gettimeofday(&now, NULL);
+	return (now.tv_sec * 1000000UL + now.tv_usec);
+}
+
+#ifdef ENABLE_TIMING
+static void print_time(const char* message, uint64_t then, uint64_t now)
+{
+	DBG("%s: %lld us", message, now - then);
+}
+#endif
+
+static int audioservice_send(struct bluetooth_data *data, const bt_audio_msg_header_t *msg);
+static int audioservice_expect(struct bluetooth_data *data, bt_audio_msg_header_t *outmsg,
+				int expected_type);
+static int bluetooth_a2dp_hw_params(struct bluetooth_data *data);
+static void set_state(struct bluetooth_data *data, a2dp_state_t state);
+
+
+static void bluetooth_close(struct bluetooth_data *data)
+{
+	DBG("bluetooth_close");
+	if (data->server.fd >= 0) {
+		bt_audio_service_close(data->server.fd);
+		data->server.fd = -1;
+	}
+
+	if (data->stream.fd >= 0) {
+		close(data->stream.fd);
+		data->stream.fd = -1;
+	}
+
+	data->state = A2DP_STATE_NONE;
+}
+
+static int bluetooth_start(struct bluetooth_data *data)
+{
+	char c = 'w';
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_start_stream_req *start_req = (void*) buf;
+	struct bt_start_stream_rsp *start_rsp = (void*) buf;
+	struct bt_new_stream_ind *streamfd_ind = (void*) buf;
+	int opt_name, err, bytes;
+
+	DBG("bluetooth_start");
+	data->state = A2DP_STATE_STARTING;
+	/* send start */
+	memset(start_req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	start_req->h.type = BT_REQUEST;
+	start_req->h.name = BT_START_STREAM;
+	start_req->h.length = sizeof(*start_req);
+
+
+	err = audioservice_send(data, &start_req->h);
+	if (err < 0)
+		goto error;
+
+	start_rsp->h.length = sizeof(*start_rsp);
+	err = audioservice_expect(data, &start_rsp->h, BT_START_STREAM);
+	if (err < 0)
+		goto error;
+
+	streamfd_ind->h.length = sizeof(*streamfd_ind);
+	err = audioservice_expect(data, &streamfd_ind->h, BT_NEW_STREAM);
+	if (err < 0)
+		goto error;
+
+	data->stream.fd = bt_audio_service_get_data_fd(data->server.fd);
+	if (data->stream.fd < 0) {
+		ERR("bt_audio_service_get_data_fd failed, errno: %d", errno);
+		err = -errno;
+		goto error;
+	}
+	data->stream.events = POLLOUT;
+
+	/* set our socket buffer to the size of PACKET_BUFFER_COUNT packets */
+	bytes = data->link_mtu * PACKET_BUFFER_COUNT;
+	setsockopt(data->stream.fd, SOL_SOCKET, SO_SNDBUF, &bytes,
+			sizeof(bytes));
+
+	data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
+	data->frame_count = 0;
+	data->samples = 0;
+	data->nsamples = 0;
+	data->seq_num = 0;
+	data->frame_count = 0;
+	data->next_write = 0;
+
+	set_state(data, A2DP_STATE_STARTED);
+	return 0;
+
+error:
+	/* set state to A2DP_STATE_INITIALIZED to force reconfiguration */
+	if (data->state == A2DP_STATE_STARTING)
+		set_state(data, A2DP_STATE_INITIALIZED);
+	return err;
+}
+
+static int bluetooth_stop(struct bluetooth_data *data)
+{
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_stop_stream_req *stop_req = (void*) buf;
+	struct bt_stop_stream_rsp *stop_rsp = (void*) buf;
+	int err;
+
+	DBG("bluetooth_stop");
+
+	data->state = A2DP_STATE_STOPPING;
+	if (data->stream.fd >= 0) {
+		close(data->stream.fd);
+		data->stream.fd = -1;
+	}
+
+	/* send stop request */
+	memset(stop_req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	stop_req->h.type = BT_REQUEST;
+	stop_req->h.name = BT_STOP_STREAM;
+	stop_req->h.length = sizeof(*stop_req);
+
+	err = audioservice_send(data, &stop_req->h);
+	if (err < 0)
+		goto error;
+
+	stop_rsp->h.length = sizeof(*stop_rsp);
+	err = audioservice_expect(data, &stop_rsp->h, BT_STOP_STREAM);
+	if (err < 0)
+		goto error;
+
+error:
+	if (data->state == A2DP_STATE_STOPPING)
+		set_state(data, A2DP_STATE_CONFIGURED);
+	return err;
+}
+
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+	switch (freq) {
+	case BT_SBC_SAMPLING_FREQ_16000:
+	case BT_SBC_SAMPLING_FREQ_32000:
+		return 53;
+	case BT_SBC_SAMPLING_FREQ_44100:
+		switch (mode) {
+		case BT_A2DP_CHANNEL_MODE_MONO:
+		case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
+			return 31;
+		case BT_A2DP_CHANNEL_MODE_STEREO:
+		case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
+			return 53;
+		default:
+			ERR("Invalid channel mode %u", mode);
+			return 53;
+		}
+	case BT_SBC_SAMPLING_FREQ_48000:
+		switch (mode) {
+		case BT_A2DP_CHANNEL_MODE_MONO:
+		case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
+			return 29;
+		case BT_A2DP_CHANNEL_MODE_STEREO:
+		case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
+			return 51;
+		default:
+			ERR("Invalid channel mode %u", mode);
+			return 51;
+		}
+	default:
+		ERR("Invalid sampling freq %u", freq);
+		return 53;
+	}
+}
+
+static int bluetooth_a2dp_init(struct bluetooth_data *data)
+{
+	sbc_capabilities_t *cap = &data->sbc_capabilities;
+	unsigned int max_bitpool, min_bitpool;
+	int dir;
+
+	switch (data->rate) {
+	case 48000:
+		cap->frequency = BT_SBC_SAMPLING_FREQ_48000;
+		break;
+	case 44100:
+		cap->frequency = BT_SBC_SAMPLING_FREQ_44100;
+		break;
+	case 32000:
+		cap->frequency = BT_SBC_SAMPLING_FREQ_32000;
+		break;
+	case 16000:
+		cap->frequency = BT_SBC_SAMPLING_FREQ_16000;
+		break;
+	default:
+		ERR("Rate %d not supported", data->rate);
+		return -1;
+	}
+
+	if (data->channels == 2) {
+		if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
+			cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+		else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
+			cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+		else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
+			cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+	} else {
+		if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+			cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+	}
+
+	if (!cap->channel_mode) {
+		ERR("No supported channel modes");
+		return -1;
+	}
+
+	if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16)
+		cap->block_length = BT_A2DP_BLOCK_LENGTH_16;
+	else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12)
+		cap->block_length = BT_A2DP_BLOCK_LENGTH_12;
+	else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8)
+		cap->block_length = BT_A2DP_BLOCK_LENGTH_8;
+	else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4)
+		cap->block_length = BT_A2DP_BLOCK_LENGTH_4;
+	else {
+		ERR("No supported block lengths");
+		return -1;
+	}
+
+	if (cap->subbands & BT_A2DP_SUBBANDS_8)
+		cap->subbands = BT_A2DP_SUBBANDS_8;
+	else if (cap->subbands & BT_A2DP_SUBBANDS_4)
+		cap->subbands = BT_A2DP_SUBBANDS_4;
+	else {
+		ERR("No supported subbands");
+		return -1;
+	}
+
+	if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS)
+		cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+	else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR)
+		cap->allocation_method = BT_A2DP_ALLOCATION_SNR;
+
+		min_bitpool = MAX(MIN_BITPOOL, cap->min_bitpool);
+		max_bitpool = MIN(default_bitpool(cap->frequency,
+					cap->channel_mode),
+					cap->max_bitpool);
+
+	cap->min_bitpool = min_bitpool;
+	cap->max_bitpool = max_bitpool;
+
+	return 0;
+}
+
+static void bluetooth_a2dp_setup(struct bluetooth_data *data)
+{
+	sbc_capabilities_t active_capabilities = data->sbc_capabilities;
+
+	sbc_reinit(&data->sbc, 0);
+
+	if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000)
+		data->sbc.frequency = SBC_FREQ_16000;
+
+	if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000)
+		data->sbc.frequency = SBC_FREQ_32000;
+
+	if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100)
+		data->sbc.frequency = SBC_FREQ_44100;
+
+	if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000)
+		data->sbc.frequency = SBC_FREQ_48000;
+
+	if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+		data->sbc.mode = SBC_MODE_MONO;
+
+	if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
+		data->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+
+	if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
+		data->sbc.mode = SBC_MODE_STEREO;
+
+	if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
+		data->sbc.mode = SBC_MODE_JOINT_STEREO;
+
+	data->sbc.allocation = active_capabilities.allocation_method
+				== BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR
+				: SBC_AM_LOUDNESS;
+
+	switch (active_capabilities.subbands) {
+	case BT_A2DP_SUBBANDS_4:
+		data->sbc.subbands = SBC_SB_4;
+		break;
+	case BT_A2DP_SUBBANDS_8:
+		data->sbc.subbands = SBC_SB_8;
+		break;
+	}
+
+	switch (active_capabilities.block_length) {
+	case BT_A2DP_BLOCK_LENGTH_4:
+		data->sbc.blocks = SBC_BLK_4;
+		break;
+	case BT_A2DP_BLOCK_LENGTH_8:
+		data->sbc.blocks = SBC_BLK_8;
+		break;
+	case BT_A2DP_BLOCK_LENGTH_12:
+		data->sbc.blocks = SBC_BLK_12;
+		break;
+	case BT_A2DP_BLOCK_LENGTH_16:
+		data->sbc.blocks = SBC_BLK_16;
+		break;
+	}
+
+	data->sbc.bitpool = active_capabilities.max_bitpool;
+	data->codesize = sbc_get_codesize(&data->sbc);
+	data->frame_duration = sbc_get_frame_duration(&data->sbc);
+	DBG("frame_duration: %d us", data->frame_duration);
+}
+
+static int bluetooth_a2dp_hw_params(struct bluetooth_data *data)
+{
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_open_req *open_req = (void *) buf;
+	struct bt_open_rsp *open_rsp = (void *) buf;
+	struct bt_set_configuration_req *setconf_req = (void*) buf;
+	struct bt_set_configuration_rsp *setconf_rsp = (void*) buf;
+	int err;
+
+	memset(open_req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	open_req->h.type = BT_REQUEST;
+	open_req->h.name = BT_OPEN;
+	open_req->h.length = sizeof(*open_req);
+	strncpy(open_req->destination, data->address, 18);
+	open_req->seid = data->sbc_capabilities.capability.seid;
+	open_req->lock = BT_WRITE_LOCK;
+
+	err = audioservice_send(data, &open_req->h);
+	if (err < 0)
+		return err;
+
+	open_rsp->h.length = sizeof(*open_rsp);
+	err = audioservice_expect(data, &open_rsp->h, BT_OPEN);
+	if (err < 0)
+		return err;
+
+	err = bluetooth_a2dp_init(data);
+	if (err < 0)
+		return err;
+
+
+	memset(setconf_req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	setconf_req->h.type = BT_REQUEST;
+	setconf_req->h.name = BT_SET_CONFIGURATION;
+	setconf_req->h.length = sizeof(*setconf_req);
+	memcpy(&setconf_req->codec, &data->sbc_capabilities,
+						sizeof(data->sbc_capabilities));
+
+	setconf_req->codec.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+	setconf_req->codec.length = sizeof(data->sbc_capabilities);
+	setconf_req->h.length += setconf_req->codec.length - sizeof(setconf_req->codec);
+
+	DBG("bluetooth_a2dp_hw_params sending configuration:\n");
+	switch (data->sbc_capabilities.channel_mode) {
+		case BT_A2DP_CHANNEL_MODE_MONO:
+			DBG("\tchannel_mode: MONO\n");
+			break;
+		case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
+			DBG("\tchannel_mode: DUAL CHANNEL\n");
+			break;
+		case BT_A2DP_CHANNEL_MODE_STEREO:
+			DBG("\tchannel_mode: STEREO\n");
+			break;
+		case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
+			DBG("\tchannel_mode: JOINT STEREO\n");
+			break;
+		default:
+			DBG("\tchannel_mode: UNKNOWN (%d)\n",
+				data->sbc_capabilities.channel_mode);
+	}
+	switch (data->sbc_capabilities.frequency) {
+		case BT_SBC_SAMPLING_FREQ_16000:
+			DBG("\tfrequency: 16000\n");
+			break;
+		case BT_SBC_SAMPLING_FREQ_32000:
+			DBG("\tfrequency: 32000\n");
+			break;
+		case BT_SBC_SAMPLING_FREQ_44100:
+			DBG("\tfrequency: 44100\n");
+			break;
+		case BT_SBC_SAMPLING_FREQ_48000:
+			DBG("\tfrequency: 48000\n");
+			break;
+		default:
+			DBG("\tfrequency: UNKNOWN (%d)\n",
+				data->sbc_capabilities.frequency);
+	}
+	switch (data->sbc_capabilities.allocation_method) {
+		case BT_A2DP_ALLOCATION_SNR:
+			DBG("\tallocation_method: SNR\n");
+			break;
+		case BT_A2DP_ALLOCATION_LOUDNESS:
+			DBG("\tallocation_method: LOUDNESS\n");
+			break;
+		default:
+			DBG("\tallocation_method: UNKNOWN (%d)\n",
+				data->sbc_capabilities.allocation_method);
+	}
+	switch (data->sbc_capabilities.subbands) {
+		case BT_A2DP_SUBBANDS_4:
+			DBG("\tsubbands: 4\n");
+			break;
+		case BT_A2DP_SUBBANDS_8:
+			DBG("\tsubbands: 8\n");
+			break;
+		default:
+			DBG("\tsubbands: UNKNOWN (%d)\n",
+				data->sbc_capabilities.subbands);
+	}
+	switch (data->sbc_capabilities.block_length) {
+		case BT_A2DP_BLOCK_LENGTH_4:
+			DBG("\tblock_length: 4\n");
+			break;
+		case BT_A2DP_BLOCK_LENGTH_8:
+			DBG("\tblock_length: 8\n");
+			break;
+		case BT_A2DP_BLOCK_LENGTH_12:
+			DBG("\tblock_length: 12\n");
+			break;
+		case BT_A2DP_BLOCK_LENGTH_16:
+			DBG("\tblock_length: 16\n");
+			break;
+		default:
+			DBG("\tblock_length: UNKNOWN (%d)\n",
+				data->sbc_capabilities.block_length);
+	}
+	DBG("\tmin_bitpool: %d\n", data->sbc_capabilities.min_bitpool);
+	DBG("\tmax_bitpool: %d\n", data->sbc_capabilities.max_bitpool);
+
+	err = audioservice_send(data, &setconf_req->h);
+	if (err < 0)
+		return err;
+
+	err = audioservice_expect(data, &setconf_rsp->h, BT_SET_CONFIGURATION);
+	if (err < 0)
+		return err;
+
+	data->link_mtu = setconf_rsp->link_mtu;
+	DBG("MTU: %d", data->link_mtu);
+
+	/* Setup SBC encoder now we agree on parameters */
+	bluetooth_a2dp_setup(data);
+
+	DBG("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
+		data->sbc.allocation, data->sbc.subbands, data->sbc.blocks,
+		data->sbc.bitpool);
+
+	return 0;
+}
+
+static int avdtp_write(struct bluetooth_data *data)
+{
+	int ret = 0;
+	struct rtp_header *header;
+	struct rtp_payload *payload;
+
+	uint64_t now;
+	long duration = data->frame_duration * data->frame_count;
+#ifdef ENABLE_TIMING
+	uint64_t begin, end, begin2, end2;
+	begin = get_microseconds();
+#endif
+
+	header = (struct rtp_header *)data->buffer;
+	payload = (struct rtp_payload *)(data->buffer + sizeof(*header));
+
+	memset(data->buffer, 0, sizeof(*header) + sizeof(*payload));
+
+	payload->frame_count = data->frame_count;
+	header->v = 2;
+	header->pt = 1;
+	header->sequence_number = htons(data->seq_num);
+	header->timestamp = htonl(data->nsamples);
+	header->ssrc = htonl(1);
+
+	data->stream.revents = 0;
+#ifdef ENABLE_TIMING
+	begin2 = get_microseconds();
+#endif
+	ret = poll(&data->stream, 1, POLL_TIMEOUT);
+#ifdef ENABLE_TIMING
+	end2 = get_microseconds();
+	print_time("poll", begin2, end2);
+#endif
+	if (ret == 1 && data->stream.revents == POLLOUT) {
+		long ahead = 0;
+		now = get_microseconds();
+
+		if (data->next_write) {
+			ahead = data->next_write - now;
+#ifdef ENABLE_TIMING
+			DBG("duration: %ld, ahead: %ld", duration, ahead);
+#endif
+			if (ahead > 0) {
+				/* too fast, need to throttle */
+				usleep(ahead);
+			}
+		} else {
+			data->next_write = now;
+		}
+		if (ahead < 0) {
+			/* fallen too far behind, don't try to catch up */
+			VDBG("ahead < 0, resetting next_write");
+			data->next_write = 0;
+		} else {
+			/* advance next_write by duration */
+			data->next_write += duration;
+		}
+
+#ifdef ENABLE_TIMING
+		begin2 = get_microseconds();
+#endif
+		ret = send(data->stream.fd, data->buffer, data->count, MSG_NOSIGNAL);
+#ifdef ENABLE_TIMING
+		end2 = get_microseconds();
+		print_time("send", begin2, end2);
+#endif
+		if (ret < 0) {
+			/* can happen during normal remote disconnect */
+			VDBG("send() failed: %d (errno %s)", ret, strerror(errno));
+		}
+		if (ret == -EPIPE) {
+			bluetooth_close(data);
+		}
+	} else {
+		/* can happen during normal remote disconnect */
+		VDBG("poll() failed: %d (revents = %d, errno %s)",
+				ret, data->stream.revents, strerror(errno));
+	}
+
+	/* Reset buffer of data to send */
+	data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
+	data->frame_count = 0;
+	data->samples = 0;
+	data->seq_num++;
+
+#ifdef ENABLE_TIMING
+	end = get_microseconds();
+	print_time("avdtp_write", begin, end);
+#endif
+	return 0; /* always return success */
+}
+
+static int audioservice_send(struct bluetooth_data *data,
+		const bt_audio_msg_header_t *msg)
+{
+	int err;
+	uint16_t length;
+
+	length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+	VDBG("sending %s", bt_audio_strmsg(msg->msg_type));
+	if (send(data->server.fd, msg, length,
+			MSG_NOSIGNAL) > 0)
+		err = 0;
+	else {
+		err = -errno;
+		ERR("Error sending data to audio service: %s(%d)",
+			strerror(errno), errno);
+		if (err == -EPIPE)
+			bluetooth_close(data);
+	}
+
+	return err;
+}
+
+static int audioservice_recv(struct bluetooth_data *data,
+		bt_audio_msg_header_t *inmsg)
+{
+	int err, ret;
+	const char *type, *name;
+	uint16_t length;
+
+	length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+	ret = recv(data->server.fd, inmsg, length, 0);
+	if (ret < 0) {
+		err = -errno;
+		ERR("Error receiving IPC data from bluetoothd: %s (%d)",
+						strerror(errno), errno);
+		if (err == -EPIPE)
+			bluetooth_close(data);
+	} else if ((size_t) ret < sizeof(bt_audio_msg_header_t)) {
+		ERR("Too short (%d bytes) IPC packet from bluetoothd", ret);
+		err = -EINVAL;
+	} else if (inmsg->type == BT_ERROR) {
+		bt_audio_error_t *error = (bt_audio_error_t *)inmsg;
+		ret = recv(data->server.fd, &error->posix_errno,
+				sizeof(error->posix_errno), 0);
+		if (ret < 0) {
+			err = -errno;
+			ERR("Error receiving error code for BT_ERROR: %s (%d)",
+						strerror(errno), errno);
+			if (err == -EPIPE)
+				bluetooth_close(data);
+		} else {
+			ERR("%s failed : %s(%d)",
+					bt_audio_strname(error->h.name),
+					strerror(error->posix_errno),
+					error->posix_errno);
+			err = -error->posix_errno;
+		}
+	} else {
+		type = bt_audio_strtype(inmsg->type);
+		name = bt_audio_strname(inmsg->name);
+		if (type && name) {
+			DBG("Received %s - %s", type, name);
+			err = 0;
+		} else {
+			err = -EINVAL;
+			ERR("Bogus message type %d - name %d"
+					" received from audio service",
+					inmsg->type, inmsg->name);
+		}
+
+	}
+	return err;
+}
+
+static int audioservice_expect(struct bluetooth_data *data,
+		bt_audio_msg_header_t *rsp_hdr, int expected_name)
+{
+	int err = audioservice_recv(data, rsp_hdr);
+
+	if (err != 0)
+		return err;
+
+	if (rsp_hdr->name != expected_name) {
+		err = -EINVAL;
+		ERR("Bogus message %s received while %s was expected",
+				bt_audio_strname(rsp_hdr->name),
+				bt_audio_strname(expected_name));
+	}
+	return err;
+
+}
+
+static int bluetooth_init(struct bluetooth_data *data)
+{
+	int sk, err;
+        struct timeval tv = {.tv_sec = RECV_TIMEOUT};
+
+	DBG("bluetooth_init");
+
+	sk = bt_audio_service_open();
+	if (sk <= 0) {
+		ERR("bt_audio_service_open failed\n");
+		return -errno;
+	}
+
+        setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+
+	data->server.fd = sk;
+	data->server.events = POLLIN;
+	data->state = A2DP_STATE_INITIALIZED;
+
+	return 0;
+}
+
+static int bluetooth_parse_capabilities(struct bluetooth_data *data,
+					struct bt_get_capabilities_rsp *rsp)
+{
+	int bytes_left = rsp->h.length - sizeof(*rsp);
+	codec_capabilities_t *codec = (void *) rsp->data;
+
+	if (codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP)
+		return 0;
+
+	while (bytes_left > 0) {
+		if ((codec->type == BT_A2DP_SBC_SINK) &&
+				!(codec->lock & BT_WRITE_LOCK))
+			break;
+
+		bytes_left -= codec->length;
+		codec = (void *) codec + codec->length;
+	}
+
+	if (bytes_left <= 0 ||
+			codec->length != sizeof(data->sbc_capabilities))
+		return -EINVAL;
+
+	memcpy(&data->sbc_capabilities, codec, codec->length);
+	return 0;
+}
+
+
+static int bluetooth_configure(struct bluetooth_data *data)
+{
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_get_capabilities_req *getcaps_req = (void*) buf;
+	struct bt_get_capabilities_rsp *getcaps_rsp = (void*) buf;
+	int err;
+
+	DBG("bluetooth_configure");
+
+	data->state = A2DP_STATE_CONFIGURING;
+	memset(getcaps_req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	getcaps_req->h.type = BT_REQUEST;
+	getcaps_req->h.name = BT_GET_CAPABILITIES;
+
+	getcaps_req->flags = 0;
+	getcaps_req->flags |= BT_FLAG_AUTOCONNECT;
+	strncpy(getcaps_req->destination, data->address, 18);
+	getcaps_req->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+	getcaps_req->h.length = sizeof(*getcaps_req);
+
+	err = audioservice_send(data, &getcaps_req->h);
+	if (err < 0) {
+		ERR("audioservice_send failed for BT_GETCAPABILITIES_REQ\n");
+		goto error;
+	}
+
+	getcaps_rsp->h.length = 0;
+	err = audioservice_expect(data, &getcaps_rsp->h, BT_GET_CAPABILITIES);
+	if (err < 0) {
+		ERR("audioservice_expect failed for BT_GETCAPABILITIES_RSP\n");
+		goto error;
+	}
+
+	bluetooth_parse_capabilities(data, getcaps_rsp);
+	err = bluetooth_a2dp_hw_params(data);
+	if (err < 0) {
+		ERR("bluetooth_a2dp_hw_params failed err: %d", err);
+		goto error;
+	}
+
+	set_state(data, A2DP_STATE_CONFIGURED);
+	return 0;
+
+error:
+	if (data->state == A2DP_STATE_CONFIGURING)
+		set_state(data, A2DP_STATE_INITIALIZED);
+	return err;
+}
+
+static void set_state(struct bluetooth_data *data, a2dp_state_t state)
+{
+	data->state = state;
+	pthread_cond_signal(&data->client_wait);
+}
+
+static void __set_command(struct bluetooth_data *data, a2dp_command_t command)
+{
+	VDBG("set_command %d\n", command);
+	data->command = command;
+	pthread_cond_signal(&data->thread_wait);
+	return;
+}
+
+static void set_command(struct bluetooth_data *data, a2dp_command_t command)
+{
+	pthread_mutex_lock(&data->mutex);
+	__set_command(data, command);
+	pthread_mutex_unlock(&data->mutex);
+}
+
+/* timeout is in milliseconds */
+static int wait_for_start(struct bluetooth_data *data, int timeout)
+{
+	a2dp_state_t state = data->state;
+	struct timeval tv;
+	struct timespec ts;
+	int err = 0;
+
+#ifdef ENABLE_TIMING
+	uint64_t begin, end;
+	begin = get_microseconds();
+#endif
+
+	gettimeofday(&tv, (struct timezone *) NULL);
+	ts.tv_sec = tv.tv_sec + (timeout / 1000);
+	ts.tv_nsec = (tv.tv_usec + (timeout % 1000) * 1000L ) * 1000L;
+
+	pthread_mutex_lock(&data->mutex);
+	while (state != A2DP_STATE_STARTED) {
+		if (state == A2DP_STATE_NONE)
+			__set_command(data, A2DP_CMD_INIT);
+		else if (state == A2DP_STATE_INITIALIZED)
+			__set_command(data, A2DP_CMD_CONFIGURE);
+		else if (state == A2DP_STATE_CONFIGURED) {
+			__set_command(data, A2DP_CMD_START);
+		}
+again:
+		err = pthread_cond_timedwait(&data->client_wait, &data->mutex, &ts);
+		if (err) {
+			/* don't timeout if we're done */
+			if (data->state == A2DP_STATE_STARTED) {
+				err = 0;
+				break;
+			}
+			if (err == ETIMEDOUT)
+				break;
+			goto again;
+		}
+
+		if (state == data->state)
+			goto again;
+
+		state = data->state;
+
+		if (state == A2DP_STATE_NONE) {
+			err = ENODEV;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&data->mutex);
+
+#ifdef ENABLE_TIMING
+	end = get_microseconds();
+	print_time("wait_for_start", begin, end);
+#endif
+
+	/* pthread_cond_timedwait returns positive errors */
+	return -err;
+}
+
+static void a2dp_free(struct bluetooth_data *data)
+{
+	pthread_cond_destroy(&data->client_wait);
+	pthread_cond_destroy(&data->thread_wait);
+	pthread_cond_destroy(&data->thread_start);
+	pthread_mutex_destroy(&data->mutex);
+	free(data);
+	return;
+}
+
+static void* a2dp_thread(void *d)
+{
+	struct bluetooth_data* data = (struct bluetooth_data*)d;
+	a2dp_command_t command = A2DP_CMD_NONE;
+
+	DBG("a2dp_thread started");
+	prctl(PR_SET_NAME, "a2dp_thread", 0, 0, 0);
+
+	pthread_mutex_lock(&data->mutex);
+
+	data->started = 1;
+	pthread_cond_signal(&data->thread_start);
+
+	while (1)
+	{
+		while (1) {
+			pthread_cond_wait(&data->thread_wait, &data->mutex);
+
+			/* Initialization needed */
+			if (data->state == A2DP_STATE_NONE &&
+			    data->command != A2DP_CMD_QUIT) {
+				bluetooth_init(data);
+			}
+
+			/* New state command signaled */
+			if (command != data->command) {
+				command = data->command;
+				break;
+			}
+		}
+
+		switch (command) {
+			case A2DP_CMD_CONFIGURE:
+				if (data->state != A2DP_STATE_INITIALIZED)
+					break;
+				bluetooth_configure(data);
+				break;
+
+			case A2DP_CMD_START:
+				if (data->state != A2DP_STATE_CONFIGURED)
+					break;
+				bluetooth_start(data);
+				break;
+
+			case A2DP_CMD_STOP:
+				if (data->state != A2DP_STATE_STARTED)
+					break;
+				bluetooth_stop(data);
+				break;
+
+			case A2DP_CMD_QUIT:
+				bluetooth_close(data);
+				sbc_finish(&data->sbc);
+				a2dp_free(data);
+				goto done;
+
+			case A2DP_CMD_INIT:
+				/* already called bluetooth_init() */
+			default:
+				break;
+		}
+	}
+
+done:
+	pthread_mutex_unlock(&data->mutex);
+	DBG("a2dp_thread finished");
+	return NULL;
+}
+
+int a2dp_init(int rate, int channels, a2dpData* dataPtr)
+{
+	struct bluetooth_data* data;
+	pthread_attr_t attr;
+	int err;
+
+	DBG("a2dp_init rate: %d channels: %d", rate, channels);
+	*dataPtr = NULL;
+	data = malloc(sizeof(struct bluetooth_data));
+	if (!data)
+		return -1;
+
+	memset(data, 0, sizeof(struct bluetooth_data));
+	data->server.fd = -1;
+	data->stream.fd = -1;
+	data->state = A2DP_STATE_NONE;
+	data->command = A2DP_CMD_NONE;
+
+	strncpy(data->address, "00:00:00:00:00:00", 18);
+	data->rate = rate;
+	data->channels = channels;
+
+	sbc_init(&data->sbc, 0);
+
+	pthread_mutex_init(&data->mutex, NULL);
+	pthread_cond_init(&data->thread_start, NULL);
+	pthread_cond_init(&data->thread_wait, NULL);
+	pthread_cond_init(&data->client_wait, NULL);
+
+	pthread_mutex_lock(&data->mutex);
+	data->started = 0;
+
+	pthread_attr_init(&attr);
+	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+	err = pthread_create(&data->thread, &attr, a2dp_thread, data);
+	if (err) {
+		/* If the thread create fails we must not wait */
+		pthread_mutex_unlock(&data->mutex);
+		err = -err;
+		goto error;
+	}
+
+	/* Make sure the state machine is ready and waiting */
+	while (!data->started) {
+		pthread_cond_wait(&data->thread_start, &data->mutex);
+	}
+
+	/* Poke the state machine to get it going */
+	pthread_cond_signal(&data->thread_wait);
+
+	pthread_mutex_unlock(&data->mutex);
+	pthread_attr_destroy(&attr);
+
+	*dataPtr = data;
+	return 0;
+error:
+	bluetooth_close(data);
+	sbc_finish(&data->sbc);
+	pthread_attr_destroy(&attr);
+	a2dp_free(data);
+
+	return err;
+}
+
+void a2dp_set_sink(a2dpData d, const char* address)
+{
+	struct bluetooth_data* data = (struct bluetooth_data*)d;
+	if (strncmp(data->address, address, 18)) {
+		strncpy(data->address, address, 18);
+		set_command(data, A2DP_CMD_INIT);
+	}
+}
+
+int a2dp_write(a2dpData d, const void* buffer, int count)
+{
+	struct bluetooth_data* data = (struct bluetooth_data*)d;
+	uint8_t* src = (uint8_t *)buffer;
+	int codesize;
+	int err, ret = 0;
+	long frames_left = count;
+	int encoded, written;
+	const char *buff;
+	int did_configure = 0;
+#ifdef ENABLE_TIMING
+	uint64_t begin, end;
+	DBG("********** a2dp_write **********");
+	begin = get_microseconds();
+#endif
+
+	err = wait_for_start(data, WRITE_TIMEOUT);
+	if (err < 0)
+		return err;
+
+	codesize = data->codesize;
+
+	while (frames_left >= codesize) {
+		/* Enough data to encode (sbc wants 512 byte blocks) */
+		encoded = sbc_encode(&(data->sbc), src, codesize,
+					data->buffer + data->count,
+					sizeof(data->buffer) - data->count,
+					&written);
+		if (encoded <= 0) {
+			ERR("Encoding error %d", encoded);
+			goto done;
+		}
+		VDBG("sbc_encode returned %d, codesize: %d, written: %d\n",
+			encoded, codesize, written);
+
+		src += encoded;
+		data->count += written;
+		data->frame_count++;
+		data->samples += encoded;
+		data->nsamples += encoded;
+
+		/* No space left for another frame then send */
+		if ((data->count + written >= data->link_mtu) ||
+				(data->count + written >= BUFFER_SIZE)) {
+			VDBG("sending packet %d, count %d, link_mtu %u",
+					data->seq_num, data->count,
+					data->link_mtu);
+			err = avdtp_write(data);
+			if (err < 0)
+				return err;
+		}
+
+		ret += encoded;
+		frames_left -= encoded;
+	}
+
+	if (frames_left > 0)
+		ERR("%ld bytes left at end of a2dp_write\n", frames_left);
+
+done:
+#ifdef ENABLE_TIMING
+	end = get_microseconds();
+	print_time("a2dp_write total", begin, end);
+#endif
+	return ret;
+}
+
+int a2dp_stop(a2dpData d)
+{
+	struct bluetooth_data* data = (struct bluetooth_data*)d;
+	DBG("a2dp_stop\n");
+	if (!data)
+		return 0;
+
+	set_command(data, A2DP_CMD_STOP);
+	return 0;
+}
+
+void a2dp_cleanup(a2dpData d)
+{
+	struct bluetooth_data* data = (struct bluetooth_data*)d;
+	DBG("a2dp_cleanup\n");
+	set_command(data, A2DP_CMD_QUIT);
+}
diff --git a/audio/liba2dp.h b/audio/liba2dp.h
new file mode 100644
index 0000000..5a9ef34
--- /dev/null
+++ b/audio/liba2dp.h
@@ -0,0 +1,39 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2008  Marcel Holtmann <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 __cplusplus
+extern "C" {
+#endif
+
+typedef void* a2dpData;
+
+int a2dp_init(int rate, int channels, a2dpData* dataPtr);
+void a2dp_set_sink(a2dpData data, const char* address);
+int a2dp_write(a2dpData data, const void* buffer, int count);
+int a2dp_stop(a2dpData data);
+void a2dp_cleanup(a2dpData data);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/audio/main.c b/audio/main.c
new file mode 100644
index 0000000..9defe60
--- /dev/null
+++ b/audio/main.c
@@ -0,0 +1,203 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 "logging.h"
+#include "device.h"
+#include "unix.h"
+#include "headset.h"
+#include "manager.h"
+#include "gateway.h"
+
+static GIOChannel *sco_server = NULL;
+
+static GKeyFile *load_config_file(const char *file)
+{
+	GError *err = NULL;
+	GKeyFile *keyfile;
+
+	keyfile = g_key_file_new();
+
+	g_key_file_set_list_separator(keyfile, ',');
+
+	if (!g_key_file_load_from_file(keyfile, file, 0, &err)) {
+		error("Parsing %s failed: %s", file, err->message);
+		g_error_free(err);
+		g_key_file_free(keyfile);
+		return NULL;
+	}
+
+	return keyfile;
+}
+
+static void sco_server_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+	int sk;
+	struct audio_device *device;
+	char addr[18];
+	bdaddr_t src, dst;
+
+	if (err) {
+		error("sco_server_cb: %s", err->message);
+		return;
+	}
+
+	bt_io_get(chan, BT_IO_SCO, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_DEST, addr,
+			BT_IO_OPT_INVALID);
+	if (err) {
+		error("bt_io_get: %s", err->message);
+		goto drop;
+	}
+
+	device = manager_find_device(NULL, &src, &dst, AUDIO_HEADSET_INTERFACE,
+					FALSE);
+	if (!device)
+		device = manager_find_device(NULL, &src, &dst,
+						AUDIO_GATEWAY_INTERFACE,
+						FALSE);
+
+	if (!device)
+		goto drop;
+
+	if (device->headset) {
+		if (headset_get_state(device) < HEADSET_STATE_CONNECTED) {
+			debug("Refusing SCO from non-connected headset");
+			goto drop;
+		}
+
+		if (!get_hfp_active(device)) {
+			error("Refusing non-HFP SCO connect attempt from %s",
+									addr);
+			goto drop;
+		}
+
+		if (headset_connect_sco(device, chan) < 0)
+			goto drop;
+
+		headset_set_state(device, HEADSET_STATE_PLAYING);
+	} else if (device->gateway) {
+		if (!gateway_is_connected(device)) {
+			debug("Refusing SCO from non-connected AG");
+			goto drop;
+		}
+
+		if (gateway_connect_sco(device, chan) < 0)
+			goto drop;
+	} else
+		goto drop;
+
+	sk = g_io_channel_unix_get_fd(chan);
+	fcntl(sk, F_SETFL, 0);
+
+	debug("Accepted SCO connection from %s", addr);
+
+	return;
+
+drop:
+	g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static DBusConnection *connection;
+
+static int audio_init(void)
+{
+	GKeyFile *config;
+	gboolean enable_sco;
+
+	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return -EIO;
+
+	config = load_config_file(CONFIGDIR "/audio.conf");
+
+	if (unix_init() < 0) {
+		error("Unable to setup unix socket");
+		goto failed;
+	}
+
+	if (audio_manager_init(connection, config, &enable_sco) < 0)
+		goto failed;
+
+	if (!enable_sco)
+		return 0;
+
+	sco_server = bt_io_listen(BT_IO_SCO, sco_server_cb, NULL, NULL,
+					NULL, NULL,
+					BT_IO_OPT_INVALID);
+	if (!sco_server) {
+		error("Unable to start SCO server socket");
+		goto failed;
+	}
+
+	return 0;
+
+failed:
+	audio_manager_exit();
+	unix_exit();
+
+	if (connection) {
+		dbus_connection_unref(connection);
+		connection = NULL;
+	}
+
+	return -EIO;
+}
+
+static void audio_exit(void)
+{
+	if (sco_server) {
+		g_io_channel_shutdown(sco_server, TRUE, NULL);
+		g_io_channel_unref(sco_server);
+		sco_server = NULL;
+	}
+
+	audio_manager_exit();
+
+	unix_exit();
+
+	dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(audio, VERSION,
+			BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, audio_init, audio_exit)
diff --git a/audio/manager.c b/audio/manager.c
new file mode 100644
index 0000000..ca4b1ab
--- /dev/null
+++ b/audio/manager.c
@@ -0,0 +1,1276 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.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/manager.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "logging.h"
+#include "textfile.h"
+#include "ipc.h"
+#include "device.h"
+#include "error.h"
+#include "avdtp.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "gateway.h"
+#include "sink.h"
+#include "source.h"
+#include "control.h"
+#include "manager.h"
+#include "sdpd.h"
+#include "telephony.h"
+
+typedef enum {
+	HEADSET	= 1 << 0,
+	GATEWAY	= 1 << 1,
+	SINK	= 1 << 2,
+	SOURCE	= 1 << 3,
+	CONTROL	= 1 << 4,
+	TARGET	= 1 << 5,
+	INVALID	= 1 << 6
+} audio_service_type;
+
+typedef enum {
+		GENERIC_AUDIO = 0,
+		ADVANCED_AUDIO,
+		AV_REMOTE,
+		GET_RECORDS
+} audio_sdp_state_t;
+
+struct audio_adapter {
+	struct btd_adapter *btd_adapter;
+	uint32_t hsp_ag_record_id;
+	uint32_t hfp_ag_record_id;
+	uint32_t hfp_hs_record_id;
+	GIOChannel *hsp_ag_server;
+	GIOChannel *hfp_ag_server;
+	GIOChannel *hfp_hs_server;
+	gint ref;
+};
+
+static gboolean auto_connect = TRUE;
+static int max_connected_headsets = 1;
+static DBusConnection *connection = NULL;
+static GKeyFile *config = NULL;
+static GSList *adapters = NULL;
+static GSList *devices = NULL;
+
+static struct enabled_interfaces enabled = {
+	.hfp		= TRUE,
+	.headset	= TRUE,
+	.gateway	= FALSE,
+	.sink		= TRUE,
+	.source		= FALSE,
+	.control	= TRUE,
+};
+
+static struct audio_adapter *find_adapter(GSList *list,
+					struct btd_adapter *btd_adapter)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		struct audio_adapter *adapter = l->data;
+
+		if (adapter->btd_adapter == btd_adapter)
+			return adapter;
+	}
+
+	return NULL;
+}
+
+gboolean server_is_enabled(bdaddr_t *src, uint16_t svc)
+{
+	switch (svc) {
+	case HEADSET_SVCLASS_ID:
+		return enabled.headset;
+	case HEADSET_AGW_SVCLASS_ID:
+		return FALSE;
+	case HANDSFREE_SVCLASS_ID:
+		return enabled.headset && enabled.hfp;
+	case HANDSFREE_AGW_SVCLASS_ID:
+		return enabled.gateway;
+	case AUDIO_SINK_SVCLASS_ID:
+		return enabled.sink;
+	case AUDIO_SOURCE_SVCLASS_ID:
+		return enabled.source;
+	case AV_REMOTE_TARGET_SVCLASS_ID:
+	case AV_REMOTE_SVCLASS_ID:
+		return enabled.control;
+	default:
+		return FALSE;
+	}
+}
+
+static void handle_uuid(const char *uuidstr, struct audio_device *device)
+{
+	uuid_t uuid;
+	uint16_t uuid16;
+
+	if (bt_string2uuid(&uuid, uuidstr) < 0) {
+		error("%s not detected as an UUID-128", uuidstr);
+		return;
+	}
+
+	if (!sdp_uuid128_to_uuid(&uuid) && uuid.type != SDP_UUID16) {
+		error("Could not convert %s to a UUID-16", uuidstr);
+		return;
+	}
+
+	uuid16 = uuid.value.uuid16;
+
+	if (!server_is_enabled(&device->src, uuid16)) {
+		debug("audio handle_uuid: server not enabled for %s (0x%04x)",
+				uuidstr, uuid16);
+		return;
+	}
+
+	switch (uuid16) {
+	case HEADSET_SVCLASS_ID:
+		debug("Found Headset record");
+		if (device->headset)
+			headset_update(device, uuid16, uuidstr);
+		else
+			device->headset = headset_init(device, uuid16,
+							uuidstr);
+		break;
+	case HEADSET_AGW_SVCLASS_ID:
+		debug("Found Headset AG record");
+		break;
+	case HANDSFREE_SVCLASS_ID:
+		debug("Found Handsfree record");
+		if (device->headset)
+			headset_update(device, uuid16, uuidstr);
+		else
+			device->headset = headset_init(device, uuid16,
+							uuidstr);
+		break;
+	case HANDSFREE_AGW_SVCLASS_ID:
+		debug("Found Handsfree AG record");
+		if (device->gateway == NULL)
+			device->gateway = gateway_init(device);
+		break;
+	case AUDIO_SINK_SVCLASS_ID:
+		debug("Found Audio Sink");
+		if (device->sink == NULL)
+			device->sink = sink_init(device);
+		break;
+	case AUDIO_SOURCE_SVCLASS_ID:
+		debug("Found Audio Source");
+		if (device->source == NULL)
+			device->source = source_init(device);
+		break;
+	case AV_REMOTE_SVCLASS_ID:
+	case AV_REMOTE_TARGET_SVCLASS_ID:
+		debug("Found AV %s", uuid16 == AV_REMOTE_SVCLASS_ID ?
+							"Remote" : "Target");
+		if (device->control)
+			control_update(device, uuid16);
+		else
+			device->control = control_init(device, uuid16);
+		if (device->sink && sink_is_active(device))
+			avrcp_connect(device);
+		break;
+	default:
+		debug("Unrecognized UUID: 0x%04X", uuid16);
+		break;
+	}
+}
+
+static sdp_record_t *hsp_ag_record(uint8_t ch)
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
+	uuid_t l2cap_uuid, rfcomm_uuid;
+	sdp_profile_desc_t profile;
+	sdp_record_t *record;
+	sdp_list_t *aproto, *proto[2];
+	sdp_data_t *channel;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &svclass_uuid);
+	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
+	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+	sdp_set_service_classes(record, svclass_id);
+
+	sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
+	profile.version = 0x0102;
+	pfseq = sdp_list_append(0, &profile);
+	sdp_set_profile_descs(record, pfseq);
+
+	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap_uuid);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+	proto[1] = sdp_list_append(0, &rfcomm_uuid);
+	channel = sdp_data_alloc(SDP_UINT8, &ch);
+	proto[1] = sdp_list_append(proto[1], channel);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	sdp_set_info_attr(record, "Headset Audio Gateway", 0, 0);
+
+	sdp_data_free(channel);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static sdp_record_t *hfp_hs_record(uint8_t ch)
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
+	uuid_t l2cap_uuid, rfcomm_uuid;
+	sdp_profile_desc_t profile;
+	sdp_record_t *record;
+	sdp_list_t *aproto, *proto[2];
+	sdp_data_t *channel;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &svclass_uuid);
+	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
+	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+	sdp_set_service_classes(record, svclass_id);
+
+	sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
+	profile.version = 0x0100;
+	pfseq = sdp_list_append(0, &profile);
+	sdp_set_profile_descs(record, pfseq);
+
+	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap_uuid);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+	proto[1] = sdp_list_append(0, &rfcomm_uuid);
+	channel = sdp_data_alloc(SDP_UINT8, &ch);
+	proto[1] = sdp_list_append(proto[1], channel);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	sdp_set_info_attr(record, "Hands-Free", 0, 0);
+
+	sdp_data_free(channel);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static sdp_record_t *hfp_ag_record(uint8_t ch, uint32_t feat)
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
+	uuid_t l2cap_uuid, rfcomm_uuid;
+	sdp_profile_desc_t profile;
+	sdp_list_t *aproto, *proto[2];
+	sdp_record_t *record;
+	sdp_data_t *channel, *features;
+	uint8_t netid = 0x01;
+	uint16_t sdpfeat;
+	sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &svclass_uuid);
+	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
+	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+	sdp_set_service_classes(record, svclass_id);
+
+	sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
+	profile.version = 0x0105;
+	pfseq = sdp_list_append(0, &profile);
+	sdp_set_profile_descs(record, pfseq);
+
+	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap_uuid);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+	proto[1] = sdp_list_append(0, &rfcomm_uuid);
+	channel = sdp_data_alloc(SDP_UINT8, &ch);
+	proto[1] = sdp_list_append(proto[1], channel);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	sdpfeat = (uint16_t) feat & 0xF;
+	features = sdp_data_alloc(SDP_UINT16, &sdpfeat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	sdp_set_info_attr(record, "Hands-Free Audio Gateway", 0, 0);
+
+	sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network);
+
+	sdp_data_free(channel);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static void headset_auth_cb(DBusError *derr, void *user_data)
+{
+	struct audio_device *device = user_data;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+		headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+		return;
+	}
+
+	io = headset_get_rfcomm(device);
+
+	if (!bt_io_accept(io, headset_connect_cb, device, NULL, &err)) {
+		error("bt_io_accept: %s", err->message);
+		g_error_free(err);
+		headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+		return;
+	}
+}
+
+static void ag_confirm(GIOChannel *chan, gpointer data)
+{
+	const char *server_uuid, *remote_uuid;
+	struct audio_device *device;
+	gboolean hfp_active;
+	bdaddr_t src, dst;
+	int perr;
+	GError *err = NULL;
+	uint8_t ch;
+
+	bt_io_get(chan, BT_IO_RFCOMM, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_CHANNEL, &ch,
+			BT_IO_OPT_INVALID);
+	if (err) {
+		error("%s", err->message);
+		g_error_free(err);
+		goto drop;
+	}
+
+	if (ch == DEFAULT_HS_AG_CHANNEL) {
+		hfp_active = FALSE;
+		server_uuid = HSP_AG_UUID;
+		remote_uuid = HSP_HS_UUID;
+	} else {
+		hfp_active = TRUE;
+		server_uuid = HFP_AG_UUID;
+		remote_uuid = HFP_HS_UUID;
+	}
+
+	device = manager_get_device(&src, &dst, TRUE);
+	if (!device)
+		goto drop;
+
+	if (!manager_allow_headset_connection(device)) {
+		debug("Refusing headset: too many existing connections");
+		goto drop;
+	}
+
+	if (!device->headset) {
+		btd_device_add_uuid(device->btd_dev, remote_uuid);
+		if (!device->headset)
+			goto drop;
+	}
+
+	if (headset_get_state(device) > HEADSET_STATE_DISCONNECTED) {
+		debug("Refusing new connection since one already exists");
+		goto drop;
+	}
+
+	set_hfp_active(device, hfp_active);
+
+	if (headset_connect_rfcomm(device, chan) < 0) {
+		error("headset_connect_rfcomm failed");
+		goto drop;
+	}
+
+	headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS);
+
+	perr = audio_device_request_authorization(device, server_uuid,
+						headset_auth_cb, device);
+	if (perr < 0) {
+		debug("Authorization denied: %s", strerror(-perr));
+		headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+		return;
+	}
+
+	device->auto_connect = auto_connect;
+
+	return;
+
+drop:
+	g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static void gateway_auth_cb(DBusError *derr, void *user_data)
+{
+	struct audio_device *device = user_data;
+
+	if (derr && dbus_error_is_set(derr))
+		error("Access denied: %s", derr->message);
+	else {
+		char ag_address[18];
+
+		ba2str(&device->dst, ag_address);
+		debug("Accepted AG connection from %s for %s",
+			ag_address, device->path);
+
+		gateway_start_service(device);
+	}
+}
+
+static void hf_io_cb(GIOChannel *chan, gpointer data)
+{
+	bdaddr_t src, dst;
+	GError *err = NULL;
+	uint8_t ch;
+	const char *server_uuid, *remote_uuid;
+	uint16_t svclass;
+	struct audio_device *device;
+	int perr;
+
+	bt_io_get(chan, BT_IO_RFCOMM, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_CHANNEL, &ch,
+			BT_IO_OPT_INVALID);
+
+	if (err) {
+		error("%s", err->message);
+		g_error_free(err);
+		return;
+	}
+
+	server_uuid = HFP_HS_UUID;
+	remote_uuid = HFP_AG_UUID;
+	svclass = HANDSFREE_AGW_SVCLASS_ID;
+
+	device = manager_get_device(&src, &dst, TRUE);
+	if (!device)
+		goto drop;
+
+	if (!device->gateway) {
+		btd_device_add_uuid(device->btd_dev, remote_uuid);
+		if (!device->gateway)
+			goto drop;
+	}
+
+	if (gateway_is_connected(device)) {
+		debug("Refusing new connection since one already exists");
+		goto drop;
+	}
+
+	if (gateway_connect_rfcomm(device, chan) < 0) {
+		error("Allocating new GIOChannel failed!");
+		goto drop;
+	}
+
+	perr = audio_device_request_authorization(device, server_uuid,
+						gateway_auth_cb, device);
+	if (perr < 0) {
+		debug("Authorization denied!");
+		goto drop;
+	}
+
+	return;
+
+drop:
+	g_io_channel_shutdown(chan, TRUE, NULL);
+	g_io_channel_unref(chan);
+	return;
+}
+
+static int headset_server_init(struct audio_adapter *adapter)
+{
+	uint8_t chan = DEFAULT_HS_AG_CHANNEL;
+	sdp_record_t *record;
+	gboolean master = TRUE;
+	GError *err = NULL;
+	uint32_t features;
+	GIOChannel *io;
+	bdaddr_t src;
+
+	if (config) {
+		gboolean tmp;
+
+		tmp = g_key_file_get_boolean(config, "General", "Master",
+						&err);
+		if (err) {
+			debug("audio.conf: %s", err->message);
+			g_clear_error(&err);
+		} else
+			master = tmp;
+	}
+
+	adapter_get_address(adapter->btd_adapter, &src);
+
+	io =  bt_io_listen(BT_IO_RFCOMM, NULL, ag_confirm, adapter, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &src,
+				BT_IO_OPT_CHANNEL, chan,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+				BT_IO_OPT_MASTER, master,
+				BT_IO_OPT_INVALID);
+	if (!io)
+		goto failed;
+
+	adapter->hsp_ag_server = io;
+
+	record = hsp_ag_record(chan);
+	if (!record) {
+		error("Unable to allocate new service record");
+		goto failed;
+	}
+
+	if (add_record_to_server(&src, record) < 0) {
+		error("Unable to register HS AG service record");
+		sdp_record_free(record);
+		goto failed;
+	}
+	adapter->hsp_ag_record_id = record->handle;
+
+	features = headset_config_init(config);
+
+	if (!enabled.hfp)
+		return 0;
+
+	chan = DEFAULT_HF_AG_CHANNEL;
+
+	io = bt_io_listen(BT_IO_RFCOMM, NULL, ag_confirm, adapter, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &src,
+				BT_IO_OPT_CHANNEL, chan,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+				BT_IO_OPT_MASTER, master,
+				BT_IO_OPT_INVALID);
+	if (!io)
+		goto failed;
+
+	adapter->hfp_ag_server = io;
+
+	record = hfp_ag_record(chan, features);
+	if (!record) {
+		error("Unable to allocate new service record");
+		goto failed;
+	}
+
+	if (add_record_to_server(&src, record) < 0) {
+		error("Unable to register HF AG service record");
+		sdp_record_free(record);
+		goto failed;
+	}
+	adapter->hfp_ag_record_id = record->handle;
+
+	return 0;
+
+failed:
+	error("%s", err->message);
+	g_error_free(err);
+	if (adapter->hsp_ag_server) {
+		g_io_channel_shutdown(adapter->hsp_ag_server, TRUE, NULL);
+		g_io_channel_unref(adapter->hsp_ag_server);
+		adapter->hsp_ag_server = NULL;
+	}
+
+	if (adapter->hfp_ag_server) {
+		g_io_channel_shutdown(adapter->hfp_ag_server, TRUE, NULL);
+		g_io_channel_unref(adapter->hfp_ag_server);
+		adapter->hfp_ag_server = NULL;
+	}
+
+	return -1;
+}
+
+static int gateway_server_init(struct audio_adapter *adapter)
+{
+	uint8_t chan = DEFAULT_HFP_HS_CHANNEL;
+	sdp_record_t *record;
+	gboolean master = TRUE;
+	GError *err = NULL;
+	GIOChannel *io;
+	bdaddr_t src;
+
+	if (config) {
+		gboolean tmp;
+
+		tmp = g_key_file_get_boolean(config, "General", "Master",
+						&err);
+		if (err) {
+			debug("audio.conf: %s", err->message);
+			g_clear_error(&err);
+		} else
+			master = tmp;
+	}
+
+	adapter_get_address(adapter->btd_adapter, &src);
+
+	io = bt_io_listen(BT_IO_RFCOMM, NULL, hf_io_cb, adapter, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &src,
+				BT_IO_OPT_CHANNEL, chan,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+				BT_IO_OPT_MASTER, master,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+		return -1;
+	}
+
+	adapter->hfp_hs_server = io;
+	record = hfp_hs_record(chan);
+	if (!record) {
+		error("Unable to allocate new service record");
+		return -1;
+	}
+
+	if (add_record_to_server(&src, record) < 0) {
+		error("Unable to register HFP HS service record");
+		sdp_record_free(record);
+		g_io_channel_unref(adapter->hfp_hs_server);
+		adapter->hfp_hs_server = NULL;
+		return -1;
+	}
+
+	adapter->hfp_hs_record_id = record->handle;
+
+	return 0;
+}
+
+static int audio_probe(struct btd_device *device, GSList *uuids)
+{
+	struct btd_adapter *adapter = device_get_adapter(device);
+	bdaddr_t src, dst;
+	struct audio_device *audio_dev;
+
+	adapter_get_address(adapter, &src);
+	device_get_address(device, &dst);
+
+	audio_dev = manager_get_device(&src, &dst, TRUE);
+	if (!audio_dev) {
+		debug("audio_probe: unable to get a device object");
+		return -1;
+	}
+
+	g_slist_foreach(uuids, (GFunc) handle_uuid, audio_dev);
+
+	return 0;
+}
+
+static void audio_remove(struct btd_device *device)
+{
+	struct audio_device *dev;
+	const char *path;
+
+	path = device_get_path(device);
+
+	dev = manager_find_device(path, NULL, NULL, NULL, FALSE);
+	if (!dev)
+		return;
+
+	devices = g_slist_remove(devices, dev);
+
+	audio_device_unregister(dev);
+}
+
+static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
+{
+	adp->ref++;
+
+	debug("audio_adapter_ref(%p): ref=%d", adp, adp->ref);
+
+	return adp;
+}
+
+static void audio_adapter_unref(struct audio_adapter *adp)
+{
+	adp->ref--;
+
+	debug("audio_adapter_unref(%p): ref=%d", adp, adp->ref);
+
+	if (adp->ref > 0)
+		return;
+
+	adapters = g_slist_remove(adapters, adp);
+	btd_adapter_unref(adp->btd_adapter);
+	g_free(adp);
+}
+
+static struct audio_adapter *audio_adapter_create(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+
+	adp = g_new0(struct audio_adapter, 1);
+	adp->btd_adapter = btd_adapter_ref(adapter);
+
+	return audio_adapter_ref(adp);
+}
+
+static struct audio_adapter *audio_adapter_get(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+
+	adp = find_adapter(adapters, adapter);
+	if (!adp) {
+		adp = audio_adapter_create(adapter);
+		if (!adp)
+			return NULL;
+		adapters = g_slist_append(adapters, adp);
+	} else
+		audio_adapter_ref(adp);
+
+	return adp;
+}
+
+static int headset_server_probe(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+	int ret;
+
+	DBG("path %s", path);
+
+	adp = audio_adapter_get(adapter);
+	if (!adp)
+		return -EINVAL;
+
+	ret = headset_server_init(adp);
+	if (ret < 0) {
+		audio_adapter_unref(adp);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void headset_server_remove(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+
+	DBG("path %s", path);
+
+	adp = find_adapter(adapters, adapter);
+	if (!adp)
+		return;
+
+	if (adp->hsp_ag_record_id) {
+		remove_record_from_server(adp->hsp_ag_record_id);
+		adp->hsp_ag_record_id = 0;
+	}
+
+	if (adp->hsp_ag_server) {
+		g_io_channel_shutdown(adp->hsp_ag_server, TRUE, NULL);
+		g_io_channel_unref(adp->hsp_ag_server);
+		adp->hsp_ag_server = NULL;
+	}
+
+	if (adp->hfp_ag_record_id) {
+		remove_record_from_server(adp->hfp_ag_record_id);
+		adp->hfp_ag_record_id = 0;
+	}
+
+	if (adp->hfp_ag_server) {
+		g_io_channel_shutdown(adp->hfp_ag_server, TRUE, NULL);
+		g_io_channel_unref(adp->hfp_ag_server);
+		adp->hfp_ag_server = NULL;
+	}
+
+	audio_adapter_unref(adp);
+}
+
+static int gateway_server_probe(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+	int ret;
+
+	DBG("path %s", path);
+
+	adp = audio_adapter_get(adapter);
+	if (!adp)
+		return -EINVAL;
+
+	ret = gateway_server_init(adp);
+	if (ret < 0) {
+		audio_adapter_ref(adp);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void gateway_server_remove(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+
+	DBG("path %s", path);
+
+	adp = find_adapter(adapters, adapter);
+	if (!adp)
+		return;
+
+	if (adp->hfp_hs_record_id) {
+		remove_record_from_server(adp->hfp_hs_record_id);
+		adp->hfp_hs_record_id = 0;
+	}
+
+	if (adp->hfp_hs_server) {
+		g_io_channel_unref(adp->hfp_hs_server);
+		adp->hfp_hs_server = NULL;
+	}
+
+	audio_adapter_ref(adp);
+}
+
+static int a2dp_server_probe(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+	bdaddr_t src;
+	int ret;
+
+	DBG("path %s", path);
+
+	adp = audio_adapter_get(adapter);
+	if (!adp)
+		return -EINVAL;
+
+	adapter_get_address(adapter, &src);
+
+	ret = a2dp_register(connection, &src, config);
+	if (ret < 0) {
+		audio_adapter_unref(adp);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void a2dp_server_remove(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+	bdaddr_t src;
+
+	DBG("path %s", path);
+
+	adp = find_adapter(adapters, adapter);
+	if (!adp)
+		return;
+
+	adapter_get_address(adapter, &src);
+	a2dp_unregister(&src);
+	audio_adapter_unref(adp);
+}
+
+static int avrcp_server_probe(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+	bdaddr_t src;
+
+	DBG("path %s", path);
+
+	adp = audio_adapter_get(adapter);
+	if (!adp)
+		return -EINVAL;
+
+	adapter_get_address(adapter, &src);
+
+	return avrcp_register(connection, &src, config);
+}
+
+static void avrcp_server_remove(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+	bdaddr_t src;
+
+	DBG("path %s", path);
+
+	adp = find_adapter(adapters, adapter);
+	if (!adp)
+		return;
+
+	adapter_get_address(adapter, &src);
+	avrcp_unregister(&src);
+	audio_adapter_unref(adp);
+}
+
+static struct btd_device_driver audio_driver = {
+	.name	= "audio",
+	.uuids	= BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID,
+			ADVANCED_AUDIO_UUID, A2DP_SOURCE_UUID, A2DP_SINK_UUID,
+			AVRCP_TARGET_UUID, AVRCP_REMOTE_UUID),
+	.probe	= audio_probe,
+	.remove	= audio_remove,
+};
+
+static struct btd_adapter_driver headset_server_driver = {
+	.name	= "audio-headset",
+	.probe	= headset_server_probe,
+	.remove	= headset_server_remove,
+};
+
+static struct btd_adapter_driver gateway_server_driver = {
+	.name	= "audio-gateway",
+	.probe	= gateway_server_probe,
+	.remove	= gateway_server_remove,
+};
+
+static struct btd_adapter_driver a2dp_server_driver = {
+	.name	= "audio-a2dp",
+	.probe	= a2dp_server_probe,
+	.remove	= a2dp_server_remove,
+};
+
+static struct btd_adapter_driver avrcp_server_driver = {
+	.name	= "audio-control",
+	.probe	= avrcp_server_probe,
+	.remove	= avrcp_server_remove,
+};
+
+int audio_manager_init(DBusConnection *conn, GKeyFile *conf,
+							gboolean *enable_sco)
+{
+	char **list;
+	int i;
+	gboolean b;
+	GError *err = NULL;
+
+	connection = dbus_connection_ref(conn);
+
+	if (!conf)
+		goto proceed;
+
+	config = conf;
+
+	list = g_key_file_get_string_list(config, "General", "Enable",
+						NULL, NULL);
+	for (i = 0; list && list[i] != NULL; i++) {
+		if (g_str_equal(list[i], "Headset"))
+			enabled.headset = TRUE;
+		else if (g_str_equal(list[i], "Gateway"))
+			enabled.gateway = TRUE;
+		else if (g_str_equal(list[i], "Sink"))
+			enabled.sink = TRUE;
+		else if (g_str_equal(list[i], "Source"))
+			enabled.source = TRUE;
+		else if (g_str_equal(list[i], "Control"))
+			enabled.control = TRUE;
+	}
+	g_strfreev(list);
+
+	list = g_key_file_get_string_list(config, "General", "Disable",
+						NULL, NULL);
+	for (i = 0; list && list[i] != NULL; i++) {
+		if (g_str_equal(list[i], "Headset"))
+			enabled.headset = FALSE;
+		else if (g_str_equal(list[i], "Gateway"))
+			enabled.gateway = FALSE;
+		else if (g_str_equal(list[i], "Sink"))
+			enabled.sink = FALSE;
+		else if (g_str_equal(list[i], "Source"))
+			enabled.source = FALSE;
+		else if (g_str_equal(list[i], "Control"))
+			enabled.control = FALSE;
+	}
+	g_strfreev(list);
+
+	b = g_key_file_get_boolean(config, "General", "AutoConnect", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else
+		auto_connect = b;
+
+	b = g_key_file_get_boolean(config, "Headset", "HFP",
+					&err);
+	if (err)
+		g_clear_error(&err);
+	else
+		enabled.hfp = b;
+
+	err = NULL;
+	i = g_key_file_get_integer(config, "Headset", "MaxConnected",
+					&err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else
+		max_connected_headsets = i;
+
+proceed:
+	if (enabled.headset) {
+		telephony_init();
+		btd_register_adapter_driver(&headset_server_driver);
+	}
+
+	if (enabled.gateway)
+		btd_register_adapter_driver(&gateway_server_driver);
+
+	if (enabled.source || enabled.sink)
+		btd_register_adapter_driver(&a2dp_server_driver);
+
+	if (enabled.control)
+		btd_register_adapter_driver(&avrcp_server_driver);
+
+	btd_register_device_driver(&audio_driver);
+
+	*enable_sco = (enabled.gateway || enabled.headset);
+
+	return 0;
+}
+
+void audio_manager_exit(void)
+{
+	/* Bail out early if we haven't been initialized */
+	if (connection == NULL)
+		return;
+
+	dbus_connection_unref(connection);
+	connection = NULL;
+
+	if (config) {
+		g_key_file_free(config);
+		config = NULL;
+	}
+
+	if (enabled.headset) {
+		btd_unregister_adapter_driver(&headset_server_driver);
+		telephony_exit();
+	}
+
+	if (enabled.gateway)
+		btd_unregister_adapter_driver(&gateway_server_driver);
+
+	if (enabled.source || enabled.sink)
+		btd_unregister_adapter_driver(&a2dp_server_driver);
+
+	if (enabled.control)
+		btd_unregister_adapter_driver(&avrcp_server_driver);
+
+	btd_unregister_device_driver(&audio_driver);
+}
+
+struct audio_device *manager_find_device(const char *path,
+					const bdaddr_t *src,
+					const bdaddr_t *dst,
+					const char *interface,
+					gboolean connected)
+{
+	GSList *l;
+
+	for (l = devices; l != NULL; l = l->next) {
+		struct audio_device *dev = l->data;
+
+		if ((path && (strcmp(path, "")) && strcmp(dev->path, path)))
+			continue;
+
+		if ((src && bacmp(src, BDADDR_ANY)) && bacmp(&dev->src, src))
+			continue;
+
+		if ((dst && bacmp(dst, BDADDR_ANY)) && bacmp(&dev->dst, dst))
+			continue;
+
+		if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface)
+				&& !dev->headset)
+			continue;
+
+		if (interface && !strcmp(AUDIO_GATEWAY_INTERFACE, interface)
+				&& !dev->gateway)
+			continue;
+
+		if (interface && !strcmp(AUDIO_SINK_INTERFACE, interface)
+				&& !dev->sink)
+			continue;
+
+		if (interface && !strcmp(AUDIO_SOURCE_INTERFACE, interface)
+				&& !dev->source)
+			continue;
+
+		if (interface && !strcmp(AUDIO_CONTROL_INTERFACE, interface)
+				&& !dev->control)
+			continue;
+
+		if (connected && !audio_device_is_active(dev, interface))
+			continue;
+
+		return dev;
+	}
+
+	return NULL;
+}
+
+struct audio_device *manager_get_device(const bdaddr_t *src,
+					const bdaddr_t *dst,
+					gboolean create)
+{
+	struct audio_device *dev;
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+	char addr[18];
+	const char *path;
+
+	dev = manager_find_device(NULL, src, dst, NULL, FALSE);
+	if (dev)
+		return dev;
+
+	if (!create)
+		return NULL;
+
+	ba2str(src, addr);
+
+	adapter = manager_find_adapter(src);
+	if (!adapter) {
+		error("Unable to get a btd_adapter object for %s",
+				addr);
+		return NULL;
+	}
+
+	ba2str(dst, addr);
+
+	device = adapter_get_device(connection, adapter, addr);
+	if (!device) {
+		error("Unable to get btd_device object for %s", addr);
+		return NULL;
+	}
+
+	path = device_get_path(device);
+
+	dev = audio_device_register(connection, device, path, src, dst);
+	if (!dev)
+		return NULL;
+
+	devices = g_slist_append(devices, dev);
+
+	return dev;
+}
+
+gboolean manager_allow_headset_connection(struct audio_device *device)
+{
+	GSList *l;
+	int connected = 0;
+
+	for (l = devices; l != NULL; l = l->next) {
+		struct audio_device *dev = l->data;
+		struct headset *hs = dev->headset;
+
+		if (dev == device)
+			continue;
+
+		if (bacmp(&dev->src, &device->src))
+			continue;
+
+		if (!hs)
+			continue;
+
+		if (headset_get_state(dev) > HEADSET_STATE_DISCONNECTED)
+			connected++;
+
+		if (connected >= max_connected_headsets)
+			return FALSE;
+	}
+
+	return TRUE;
+}
diff --git a/audio/manager.h b/audio/manager.h
new file mode 100644
index 0000000..cb9d63c
--- /dev/null
+++ b/audio/manager.h
@@ -0,0 +1,50 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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;
+};
+
+int audio_manager_init(DBusConnection *conn, GKeyFile *config,
+							gboolean *enable_sco);
+void audio_manager_exit(void);
+
+gboolean server_is_enabled(bdaddr_t *src, uint16_t svc);
+
+struct audio_device *manager_find_device(const char *path,
+					const bdaddr_t *src,
+					const bdaddr_t *dst,
+					const char *interface,
+					gboolean connected);
+
+struct audio_device *manager_get_device(const bdaddr_t *src,
+					const bdaddr_t *dst,
+					gboolean create);
+
+gboolean manager_allow_headset_connection(struct audio_device *device);
diff --git a/audio/module-bluetooth-sink.c b/audio/module-bluetooth-sink.c
new file mode 100644
index 0000000..2944d18
--- /dev/null
+++ b/audio/module-bluetooth-sink.c
@@ -0,0 +1,39 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..13cf3ee
--- /dev/null
+++ b/audio/pcm_bluetooth.c
@@ -0,0 +1,1778 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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) {
+			SNDERR("poll error: %s (%d)", strerror(errno), errno);
+			if (errno != EINTR)
+				break;
+		} else if (ret > 0) {
+			ret = (fds[0].revents) ? 0 : 1;
+			SNDERR("poll fd %d revents %d", ret, fds[ret].revents);
+			if (fds[ret].revents & (POLLERR | POLLHUP | POLLNVAL))
+				break;
+		}
+
+		/* Offer opportunity to be canceled by main thread */
+		pthread_testcancel();
+	}
+
+	data->hw_thread = 0;
+	pthread_exit(NULL);
+}
+
+static int bluetooth_playback_start(snd_pcm_ioplug_t *io)
+{
+	struct bluetooth_data *data = io->private_data;
+	int err;
+
+	DBG("%p", io);
+
+	data->stopped = 0;
+
+	if (data->hw_thread)
+		return 0;
+
+	err = pthread_create(&data->hw_thread, 0, playback_hw_thread, data);
+
+	return -err;
+}
+
+static int bluetooth_playback_stop(snd_pcm_ioplug_t *io)
+{
+	struct bluetooth_data *data = io->private_data;
+
+	DBG("%p", io);
+
+	data->stopped = 1;
+
+	return 0;
+}
+
+static snd_pcm_sframes_t bluetooth_pointer(snd_pcm_ioplug_t *io)
+{
+	struct bluetooth_data *data = io->private_data;
+
+	return data->hw_ptr;
+}
+
+static void bluetooth_exit(struct bluetooth_data *data)
+{
+	struct bluetooth_a2dp *a2dp = &data->a2dp;
+
+	if (data->server.fd >= 0)
+		bt_audio_service_close(data->server.fd);
+
+	if (data->stream.fd >= 0)
+		close(data->stream.fd);
+
+	if (data->hw_thread) {
+		pthread_cancel(data->hw_thread);
+		pthread_join(data->hw_thread, 0);
+	}
+
+	if (a2dp->sbc_initialized)
+		sbc_finish(&a2dp->sbc);
+
+	if (data->pipefd[0] > 0)
+		close(data->pipefd[0]);
+
+	if (data->pipefd[1] > 0)
+		close(data->pipefd[1]);
+
+	free(data);
+}
+
+static int bluetooth_close(snd_pcm_ioplug_t *io)
+{
+	struct bluetooth_data *data = io->private_data;
+
+	DBG("%p", io);
+
+	bluetooth_exit(data);
+
+	return 0;
+}
+
+static int bluetooth_prepare(snd_pcm_ioplug_t *io)
+{
+	struct bluetooth_data *data = io->private_data;
+	char c = 'w';
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_start_stream_req *req = (void *) buf;
+	struct bt_start_stream_rsp *rsp = (void *) buf;
+	struct bt_new_stream_ind *ind = (void *) buf;
+	uint32_t period_count = io->buffer_size / io->period_size;
+	int opt_name, err;
+	struct timeval t = { 0, period_count };
+
+	DBG("Preparing with io->period_size=%lu io->buffer_size=%lu",
+					io->period_size, io->buffer_size);
+
+	data->reset = 0;
+
+	/* As we're gonna receive messages on the server socket, we have to stop the
+	   hw thread that is polling on it, if any */
+	if (data->hw_thread) {
+		pthread_cancel(data->hw_thread);
+		pthread_join(data->hw_thread, 0);
+		data->hw_thread = 0;
+	}
+
+	if (io->stream == SND_PCM_STREAM_PLAYBACK)
+		/* If not null for playback, xmms doesn't display time
+		 * correctly */
+		data->hw_ptr = 0;
+	else
+		/* ALSA library is really picky on the fact hw_ptr is not null.
+		 * If it is, capture won't start */
+		data->hw_ptr = io->period_size;
+
+	/* send start */
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_START_STREAM;
+	req->h.length = sizeof(*req);
+
+	err = audioservice_send(data->server.fd, &req->h);
+	if (err < 0)
+		return err;
+
+	rsp->h.length = sizeof(*rsp);
+	err = audioservice_expect(data->server.fd, &rsp->h,
+					BT_START_STREAM);
+	if (err < 0)
+		return err;
+
+	ind->h.length = sizeof(*ind);
+	err = audioservice_expect(data->server.fd, &ind->h,
+					BT_NEW_STREAM);
+	if (err < 0)
+		return err;
+
+	if (data->stream.fd >= 0)
+		close(data->stream.fd);
+
+	data->stream.fd = bt_audio_service_get_data_fd(data->server.fd);
+	if (data->stream.fd < 0) {
+		return -errno;
+	}
+
+	if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+		opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ?
+						SO_SNDTIMEO : SO_RCVTIMEO;
+
+		if (setsockopt(data->stream.fd, SOL_SOCKET, opt_name, &t,
+							sizeof(t)) < 0)
+			return -errno;
+	} else {
+		opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ?
+						SCO_TXBUFS : SCO_RXBUFS;
+
+		if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count,
+						sizeof(period_count)) == 0)
+			return 0;
+
+		opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ?
+						SO_SNDBUF : SO_RCVBUF;
+
+		if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count,
+						sizeof(period_count)) == 0)
+			return 0;
+
+		/* FIXME : handle error codes */
+	}
+
+	/* wake up any client polling at us */
+	err = write(data->pipefd[1], &c, 1);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int bluetooth_hsp_hw_params(snd_pcm_ioplug_t *io,
+					snd_pcm_hw_params_t *params)
+{
+	struct bluetooth_data *data = io->private_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_open_req *open_req = (void *) buf;
+	struct bt_open_rsp *open_rsp = (void *) buf;
+	struct bt_set_configuration_req *req = (void *) buf;
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+	int err;
+
+	DBG("Preparing with io->period_size=%lu io->buffer_size=%lu",
+					io->period_size, io->buffer_size);
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	open_req->h.type = BT_REQUEST;
+	open_req->h.name = BT_OPEN;
+	open_req->h.length = sizeof(*open_req);
+
+	strncpy(open_req->destination, data->alsa_config.device, 18);
+	open_req->seid = BT_A2DP_SEID_RANGE + 1;
+	open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ?
+			BT_WRITE_LOCK : BT_READ_LOCK);
+
+	err = audioservice_send(data->server.fd, &open_req->h);
+	if (err < 0)
+		return err;
+
+	open_rsp->h.length = sizeof(*open_rsp);
+	err = audioservice_expect(data->server.fd, &open_rsp->h,
+					BT_OPEN);
+	if (err < 0)
+		return err;
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_SET_CONFIGURATION;
+	req->h.length = sizeof(*req);
+
+	req->codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
+	req->codec.seid = BT_A2DP_SEID_RANGE + 1;
+	req->codec.length = sizeof(pcm_capabilities_t);
+
+	req->h.length += req->codec.length - sizeof(req->codec);
+	err = audioservice_send(data->server.fd, &req->h);
+	if (err < 0)
+		return err;
+
+	rsp->h.length = sizeof(*rsp);
+	err = audioservice_expect(data->server.fd, &rsp->h,
+					BT_SET_CONFIGURATION);
+	if (err < 0)
+		return err;
+
+	data->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+	data->link_mtu = rsp->link_mtu;
+
+	return 0;
+}
+
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+	switch (freq) {
+	case BT_SBC_SAMPLING_FREQ_16000:
+	case BT_SBC_SAMPLING_FREQ_32000:
+		return 53;
+	case BT_SBC_SAMPLING_FREQ_44100:
+		switch (mode) {
+		case BT_A2DP_CHANNEL_MODE_MONO:
+		case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
+			return 31;
+		case BT_A2DP_CHANNEL_MODE_STEREO:
+		case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
+			return 53;
+		default:
+			DBG("Invalid channel mode %u", mode);
+			return 53;
+		}
+	case BT_SBC_SAMPLING_FREQ_48000:
+		switch (mode) {
+		case BT_A2DP_CHANNEL_MODE_MONO:
+		case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
+			return 29;
+		case BT_A2DP_CHANNEL_MODE_STEREO:
+		case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
+			return 51;
+		default:
+			DBG("Invalid channel mode %u", mode);
+			return 51;
+		}
+	default:
+		DBG("Invalid sampling freq %u", freq);
+		return 53;
+	}
+}
+
+static int bluetooth_a2dp_init(struct bluetooth_data *data,
+					snd_pcm_hw_params_t *params)
+{
+	struct bluetooth_alsa_config *cfg = &data->alsa_config;
+	sbc_capabilities_t *cap = &data->a2dp.sbc_capabilities;
+	unsigned int max_bitpool, min_bitpool, rate, channels;
+	int dir;
+
+	snd_pcm_hw_params_get_rate(params, &rate, &dir);
+	snd_pcm_hw_params_get_channels(params, &channels);
+
+	switch (rate) {
+	case 48000:
+		cap->frequency = BT_SBC_SAMPLING_FREQ_48000;
+		break;
+	case 44100:
+		cap->frequency = BT_SBC_SAMPLING_FREQ_44100;
+		break;
+	case 32000:
+		cap->frequency = BT_SBC_SAMPLING_FREQ_32000;
+		break;
+	case 16000:
+		cap->frequency = BT_SBC_SAMPLING_FREQ_16000;
+		break;
+	default:
+		DBG("Rate %d not supported", rate);
+		return -1;
+	}
+
+	if (cfg->has_channel_mode)
+		cap->channel_mode = cfg->channel_mode;
+	else if (channels == 2) {
+		if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
+			cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+		else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
+			cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+		else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
+			cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+	} else {
+		if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+			cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+	}
+
+	if (!cap->channel_mode) {
+		DBG("No supported channel modes");
+		return -1;
+	}
+
+	if (cfg->has_block_length)
+		cap->block_length = cfg->block_length;
+	else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16)
+		cap->block_length = BT_A2DP_BLOCK_LENGTH_16;
+	else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12)
+		cap->block_length = BT_A2DP_BLOCK_LENGTH_12;
+	else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8)
+		cap->block_length = BT_A2DP_BLOCK_LENGTH_8;
+	else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4)
+		cap->block_length = BT_A2DP_BLOCK_LENGTH_4;
+	else {
+		DBG("No supported block lengths");
+		return -1;
+	}
+
+	if (cfg->has_subbands)
+		cap->subbands = cfg->subbands;
+	if (cap->subbands & BT_A2DP_SUBBANDS_8)
+		cap->subbands = BT_A2DP_SUBBANDS_8;
+	else if (cap->subbands & BT_A2DP_SUBBANDS_4)
+		cap->subbands = BT_A2DP_SUBBANDS_4;
+	else {
+		DBG("No supported subbands");
+		return -1;
+	}
+
+	if (cfg->has_allocation_method)
+		cap->allocation_method = cfg->allocation_method;
+	if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS)
+		cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+	else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR)
+		cap->allocation_method = BT_A2DP_ALLOCATION_SNR;
+
+	if (cfg->has_bitpool)
+		min_bitpool = max_bitpool = cfg->bitpool;
+	else {
+		min_bitpool = MAX(MIN_BITPOOL, cap->min_bitpool);
+		max_bitpool = MIN(default_bitpool(cap->frequency,
+					cap->channel_mode),
+					cap->max_bitpool);
+	}
+
+	cap->min_bitpool = min_bitpool;
+	cap->max_bitpool = max_bitpool;
+
+	return 0;
+}
+
+static void bluetooth_a2dp_setup(struct bluetooth_a2dp *a2dp)
+{
+	sbc_capabilities_t active_capabilities = a2dp->sbc_capabilities;
+
+	if (a2dp->sbc_initialized)
+		sbc_reinit(&a2dp->sbc, 0);
+	else
+		sbc_init(&a2dp->sbc, 0);
+	a2dp->sbc_initialized = 1;
+
+	if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000)
+		a2dp->sbc.frequency = SBC_FREQ_16000;
+
+	if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000)
+		a2dp->sbc.frequency = SBC_FREQ_32000;
+
+	if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100)
+		a2dp->sbc.frequency = SBC_FREQ_44100;
+
+	if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000)
+		a2dp->sbc.frequency = SBC_FREQ_48000;
+
+	if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+		a2dp->sbc.mode = SBC_MODE_MONO;
+
+	if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
+		a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+
+	if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
+		a2dp->sbc.mode = SBC_MODE_STEREO;
+
+	if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
+		a2dp->sbc.mode = SBC_MODE_JOINT_STEREO;
+
+	a2dp->sbc.allocation = active_capabilities.allocation_method
+				== BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR
+				: SBC_AM_LOUDNESS;
+
+	switch (active_capabilities.subbands) {
+	case BT_A2DP_SUBBANDS_4:
+		a2dp->sbc.subbands = SBC_SB_4;
+		break;
+	case BT_A2DP_SUBBANDS_8:
+		a2dp->sbc.subbands = SBC_SB_8;
+		break;
+	}
+
+	switch (active_capabilities.block_length) {
+	case BT_A2DP_BLOCK_LENGTH_4:
+		a2dp->sbc.blocks = SBC_BLK_4;
+		break;
+	case BT_A2DP_BLOCK_LENGTH_8:
+		a2dp->sbc.blocks = SBC_BLK_8;
+		break;
+	case BT_A2DP_BLOCK_LENGTH_12:
+		a2dp->sbc.blocks = SBC_BLK_12;
+		break;
+	case BT_A2DP_BLOCK_LENGTH_16:
+		a2dp->sbc.blocks = SBC_BLK_16;
+		break;
+	}
+
+	a2dp->sbc.bitpool = active_capabilities.max_bitpool;
+	a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
+	a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
+}
+
+static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io,
+					snd_pcm_hw_params_t *params)
+{
+	struct bluetooth_data *data = io->private_data;
+	struct bluetooth_a2dp *a2dp = &data->a2dp;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_open_req *open_req = (void *) buf;
+	struct bt_open_rsp *open_rsp = (void *) buf;
+	struct bt_set_configuration_req *req = (void *) buf;
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+	int err;
+
+	DBG("Preparing with io->period_size=%lu io->buffer_size=%lu",
+					io->period_size, io->buffer_size);
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	open_req->h.type = BT_REQUEST;
+	open_req->h.name = BT_OPEN;
+	open_req->h.length = sizeof(*open_req);
+
+	strncpy(open_req->destination, data->alsa_config.device, 18);
+	open_req->seid = a2dp->sbc_capabilities.capability.seid;
+	open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ?
+			BT_WRITE_LOCK : BT_READ_LOCK);
+
+	err = audioservice_send(data->server.fd, &open_req->h);
+	if (err < 0)
+		return err;
+
+	open_rsp->h.length = sizeof(*open_rsp);
+	err = audioservice_expect(data->server.fd, &open_rsp->h,
+					BT_OPEN);
+	if (err < 0)
+		return err;
+
+	err = bluetooth_a2dp_init(data, params);
+	if (err < 0)
+		return err;
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_SET_CONFIGURATION;
+	req->h.length = sizeof(*req);
+
+	memcpy(&req->codec, &a2dp->sbc_capabilities,
+			sizeof(a2dp->sbc_capabilities));
+
+	req->codec.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+	req->codec.length = sizeof(a2dp->sbc_capabilities);
+	req->h.length += req->codec.length - sizeof(req->codec);
+
+	err = audioservice_send(data->server.fd, &req->h);
+	if (err < 0)
+		return err;
+
+	rsp->h.length = sizeof(*rsp);
+	err = audioservice_expect(data->server.fd, &rsp->h,
+					BT_SET_CONFIGURATION);
+	if (err < 0)
+		return err;
+
+	data->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+	data->link_mtu = rsp->link_mtu;
+
+	/* Setup SBC encoder now we agree on parameters */
+	bluetooth_a2dp_setup(a2dp);
+
+	DBG("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
+		a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks,
+		a2dp->sbc.bitpool);
+
+	return 0;
+}
+
+static int bluetooth_poll_descriptors(snd_pcm_ioplug_t *io,
+					struct pollfd *pfd, unsigned int space)
+{
+	struct bluetooth_data *data = io->private_data;
+
+	assert(io);
+
+	if (space < 1)
+		return 0;
+
+	pfd[0].fd = data->stream.fd;
+	pfd[0].events = POLLIN;
+	pfd[0].revents = 0;
+
+	return 1;
+}
+
+static int bluetooth_poll_revents(snd_pcm_ioplug_t *io ATTRIBUTE_UNUSED,
+					struct pollfd *pfds, unsigned int nfds,
+					unsigned short *revents)
+{
+	assert(pfds && nfds == 1 && revents);
+
+	*revents = pfds[0].revents;
+
+	return 0;
+}
+
+static int bluetooth_playback_poll_descriptors_count(snd_pcm_ioplug_t *io)
+{
+	return 2;
+}
+
+static int bluetooth_playback_poll_descriptors(snd_pcm_ioplug_t *io,
+					struct pollfd *pfd, unsigned int space)
+{
+	struct bluetooth_data *data = io->private_data;
+
+	DBG("");
+
+	assert(data->pipefd[0] >= 0);
+
+	if (space < 2)
+		return 0;
+
+	pfd[0].fd = data->pipefd[0];
+	pfd[0].events = POLLIN;
+	pfd[0].revents = 0;
+	pfd[1].fd = data->stream.fd;
+	pfd[1].events = POLLERR | POLLHUP | POLLNVAL;
+	pfd[1].revents = 0;
+
+	return 2;
+}
+
+static int bluetooth_playback_poll_revents(snd_pcm_ioplug_t *io,
+					struct pollfd *pfds, unsigned int nfds,
+					unsigned short *revents)
+{
+	static char buf[1];
+	int ret;
+
+	DBG("");
+
+	assert(pfds);
+	assert(nfds == 2);
+	assert(revents);
+	assert(pfds[0].fd >= 0);
+	assert(pfds[1].fd >= 0);
+
+	if (io->state != SND_PCM_STATE_PREPARED)
+		ret = read(pfds[0].fd, buf, 1);
+
+	if (pfds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
+		io->state = SND_PCM_STATE_DISCONNECTED;
+
+	*revents = (pfds[0].revents & POLLIN) ? POLLOUT : 0;
+
+	return 0;
+}
+
+
+static snd_pcm_sframes_t bluetooth_hsp_read(snd_pcm_ioplug_t *io,
+				const snd_pcm_channel_area_t *areas,
+				snd_pcm_uframes_t offset,
+				snd_pcm_uframes_t size)
+{
+	struct bluetooth_data *data = io->private_data;
+	snd_pcm_uframes_t frames_to_write, ret;
+	unsigned char *buff;
+	unsigned int frame_size = 0;
+	int nrecv;
+
+	DBG("areas->step=%u areas->first=%u offset=%lu size=%lu io->nonblock=%u",
+			areas->step, areas->first, offset, size, io->nonblock);
+
+	frame_size = areas->step / 8;
+
+	if (data->count > 0)
+		goto proceed;
+
+	nrecv = recv(data->stream.fd, data->buffer, data->link_mtu,
+					io->nonblock ? MSG_DONTWAIT : 0);
+
+	if (nrecv < 0) {
+		ret = (errno == EPIPE) ? -EIO : -errno;
+		goto done;
+	}
+
+	if ((unsigned int) nrecv != data->link_mtu) {
+		ret = -EIO;
+		SNDERR(strerror(-ret));
+		goto done;
+	}
+
+	/* Increment hardware transmition pointer */
+	data->hw_ptr = (data->hw_ptr + data->link_mtu / frame_size) %
+				io->buffer_size;
+
+proceed:
+	buff = (unsigned char *) areas->addr +
+			(areas->first + areas->step * offset) / 8;
+
+	if ((data->count + size * frame_size) <= data->link_mtu)
+		frames_to_write = size;
+	else
+		frames_to_write = (data->link_mtu - data->count) / frame_size;
+
+	memcpy(buff, data->buffer + data->count, frame_size * frames_to_write);
+	data->count += (frame_size * frames_to_write);
+	data->count %= data->link_mtu;
+
+	/* Return written frames count */
+	ret = frames_to_write;
+
+done:
+	DBG("returning %lu", ret);
+	return ret;
+}
+
+static snd_pcm_sframes_t bluetooth_hsp_write(snd_pcm_ioplug_t *io,
+				const snd_pcm_channel_area_t *areas,
+				snd_pcm_uframes_t offset,
+				snd_pcm_uframes_t size)
+{
+	struct bluetooth_data *data = io->private_data;
+	snd_pcm_sframes_t ret = 0;
+	snd_pcm_uframes_t frames_to_read;
+	uint8_t *buff;
+	int rsend, frame_size;
+
+	DBG("areas->step=%u areas->first=%u offset=%lu, size=%lu io->nonblock=%u",
+			areas->step, areas->first, offset, size, io->nonblock);
+
+	if (io->hw_ptr > io->appl_ptr) {
+		ret = bluetooth_playback_stop(io);
+		if (ret == 0)
+			ret = -EPIPE;
+		goto done;
+	}
+
+	frame_size = areas->step / 8;
+	if ((data->count + size * frame_size) <= data->link_mtu)
+		frames_to_read = size;
+	else
+		frames_to_read = (data->link_mtu - data->count) / frame_size;
+
+	DBG("count=%d frames_to_read=%lu", data->count, frames_to_read);
+
+	/* Ready for more data */
+	buff = (uint8_t *) areas->addr +
+			(areas->first + areas->step * offset) / 8;
+	memcpy(data->buffer + data->count, buff, frame_size * frames_to_read);
+
+	/* Remember we have some frames in the pipe now */
+	data->count += frames_to_read * frame_size;
+	if (data->count != data->link_mtu) {
+		ret = frames_to_read;
+		goto done;
+	}
+
+	rsend = send(data->stream.fd, data->buffer, data->link_mtu,
+			io->nonblock ? MSG_DONTWAIT : 0);
+	if (rsend > 0) {
+		/* Reset count pointer */
+		data->count = 0;
+
+		ret = frames_to_read;
+	} else if (rsend < 0)
+		ret = (errno == EPIPE) ? -EIO : -errno;
+	else
+		ret = -EIO;
+
+done:
+	DBG("returning %ld", ret);
+	return ret;
+}
+
+static snd_pcm_sframes_t bluetooth_a2dp_read(snd_pcm_ioplug_t *io,
+				const snd_pcm_channel_area_t *areas,
+				snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
+{
+	snd_pcm_uframes_t ret = 0;
+	return ret;
+}
+
+static int avdtp_write(struct bluetooth_data *data)
+{
+	int ret = 0;
+	struct rtp_header *header;
+	struct rtp_payload *payload;
+	struct bluetooth_a2dp *a2dp = &data->a2dp;
+
+	header = (void *) a2dp->buffer;
+	payload = (void *) (a2dp->buffer + sizeof(*header));
+
+	memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload));
+
+	payload->frame_count = a2dp->frame_count;
+	header->v = 2;
+	header->pt = 1;
+	header->sequence_number = htons(a2dp->seq_num);
+	header->timestamp = htonl(a2dp->nsamples);
+	header->ssrc = htonl(1);
+
+	ret = send(data->stream.fd, a2dp->buffer, a2dp->count, MSG_DONTWAIT);
+	if (ret < 0) {
+		DBG("send returned %d errno %s.", ret, strerror(errno));
+		ret = -errno;
+	}
+
+	/* Reset buffer of data to send */
+	a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
+	a2dp->frame_count = 0;
+	a2dp->samples = 0;
+	a2dp->seq_num++;
+
+	return ret;
+}
+
+static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io,
+				const snd_pcm_channel_area_t *areas,
+				snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
+{
+	struct bluetooth_data *data = io->private_data;
+	struct bluetooth_a2dp *a2dp = &data->a2dp;
+	snd_pcm_sframes_t ret = 0;
+	unsigned int bytes_left;
+	int frame_size, encoded;
+	size_t written;
+	uint8_t *buff;
+
+	DBG("areas->step=%u areas->first=%u offset=%lu size=%lu",
+				areas->step, areas->first, offset, size);
+	DBG("hw_ptr=%lu appl_ptr=%lu diff=%lu", io->hw_ptr, io->appl_ptr,
+			io->appl_ptr - io->hw_ptr);
+
+	/* Calutate starting pointers */
+	frame_size = areas->step / 8;
+	bytes_left = size * frame_size;
+	buff = (uint8_t *) areas->addr +
+				(areas->first + areas->step * (offset)) / 8;
+
+	/* Check for underrun */
+	if (io->hw_ptr > io->appl_ptr) {
+		ret = bluetooth_playback_stop(io);
+		if (ret == 0)
+			ret = -EPIPE;
+		data->reset = 1;
+		return ret;
+	}
+
+	/* Check if we should autostart */
+	if (io->state == SND_PCM_STATE_PREPARED) {
+		snd_pcm_sw_params_t *swparams;
+		snd_pcm_uframes_t threshold;
+
+		snd_pcm_sw_params_malloc(&swparams);
+		if (!snd_pcm_sw_params_current(io->pcm, swparams) &&
+				!snd_pcm_sw_params_get_start_threshold(swparams,
+								&threshold)) {
+			if (io->appl_ptr >= threshold) {
+				ret = snd_pcm_start(io->pcm);
+				if (ret != 0)
+					return ret;
+			}
+		}
+
+		snd_pcm_sw_params_free(swparams);
+	}
+
+	/* Check if we have any left over data from the last write */
+	if (data->count > 0 && (bytes_left - data->count) >= a2dp->codesize) {
+		int additional_bytes_needed = a2dp->codesize - data->count;
+
+		memcpy(data->buffer + data->count, buff,
+						additional_bytes_needed);
+
+		/* Enough data to encode (sbc wants 1k blocks) */
+		encoded = sbc_encode(&a2dp->sbc, data->buffer, a2dp->codesize,
+					a2dp->buffer + a2dp->count,
+					sizeof(a2dp->buffer) - a2dp->count,
+								&written);
+		if (encoded <= 0) {
+			DBG("Encoding error %d", encoded);
+			goto done;
+		}
+
+		/* Increment a2dp buffers */
+		a2dp->count += written;
+		a2dp->frame_count++;
+		a2dp->samples += encoded / frame_size;
+		a2dp->nsamples += encoded / frame_size;
+
+		/* No space left for another frame then send */
+		if (a2dp->count + written >= data->link_mtu) {
+			avdtp_write(data);
+			DBG("sending packet %d, count %d, link_mtu %u",
+					a2dp->seq_num, a2dp->count,
+							data->link_mtu);
+		}
+
+		/* Increment up buff pointer to take into account
+		 * the data processed */
+		buff += additional_bytes_needed;
+		bytes_left -= additional_bytes_needed;
+
+		/* Since data has been process mark it as zero */
+		data->count = 0;
+	}
+
+
+	/* Process this buffer in full chunks */
+	while (bytes_left >= a2dp->codesize) {
+		/* Enough data to encode (sbc wants 1k blocks) */
+		encoded = sbc_encode(&a2dp->sbc, buff, a2dp->codesize,
+					a2dp->buffer + a2dp->count,
+					sizeof(a2dp->buffer) - a2dp->count,
+								&written);
+		if (encoded <= 0) {
+			DBG("Encoding error %d", encoded);
+			goto done;
+		}
+
+		/* Increment up buff pointer to take into account
+		 * the data processed */
+		buff += a2dp->codesize;
+		bytes_left -= a2dp->codesize;
+
+		/* Increment a2dp buffers */
+		a2dp->count += written;
+		a2dp->frame_count++;
+		a2dp->samples += encoded / frame_size;
+		a2dp->nsamples += encoded / frame_size;
+
+		/* No space left for another frame then send */
+		if (a2dp->count + written >= data->link_mtu) {
+			avdtp_write(data);
+			DBG("sending packet %d, count %d, link_mtu %u",
+						a2dp->seq_num, a2dp->count,
+							data->link_mtu);
+		}
+	}
+
+	/* Copy the extra to our temp buffer for the next write */
+	if (bytes_left > 0) {
+		memcpy(data->buffer + data->count, buff, bytes_left);
+		data->count += bytes_left;
+		bytes_left = 0;
+	}
+
+done:
+	DBG("returning %ld", size - bytes_left / frame_size);
+
+	return size - bytes_left / frame_size;
+}
+
+static int bluetooth_playback_delay(snd_pcm_ioplug_t *io,
+					snd_pcm_sframes_t *delayp)
+{
+	DBG("");
+
+	/* This updates io->hw_ptr value using pointer() function */
+	snd_pcm_hwsync(io->pcm);
+
+	*delayp = io->appl_ptr - io->hw_ptr;
+	if ((io->state == SND_PCM_STATE_RUNNING) && (*delayp < 0)) {
+		io->callback->stop(io);
+		io->state = SND_PCM_STATE_XRUN;
+		*delayp = 0;
+	}
+
+	/* This should never fail, ALSA API is really not
+	prepared to handle a non zero return value */
+	return 0;
+}
+
+static snd_pcm_ioplug_callback_t bluetooth_hsp_playback = {
+	.start			= bluetooth_playback_start,
+	.stop			= bluetooth_playback_stop,
+	.pointer		= bluetooth_pointer,
+	.close			= bluetooth_close,
+	.hw_params		= bluetooth_hsp_hw_params,
+	.prepare		= bluetooth_prepare,
+	.transfer		= bluetooth_hsp_write,
+	.poll_descriptors_count	= bluetooth_playback_poll_descriptors_count,
+	.poll_descriptors	= bluetooth_playback_poll_descriptors,
+	.poll_revents		= bluetooth_playback_poll_revents,
+	.delay			= bluetooth_playback_delay,
+};
+
+static snd_pcm_ioplug_callback_t bluetooth_hsp_capture = {
+	.start			= bluetooth_start,
+	.stop			= bluetooth_stop,
+	.pointer		= bluetooth_pointer,
+	.close			= bluetooth_close,
+	.hw_params		= bluetooth_hsp_hw_params,
+	.prepare		= bluetooth_prepare,
+	.transfer		= bluetooth_hsp_read,
+	.poll_descriptors	= bluetooth_poll_descriptors,
+	.poll_revents		= bluetooth_poll_revents,
+};
+
+static snd_pcm_ioplug_callback_t bluetooth_a2dp_playback = {
+	.start			= bluetooth_playback_start,
+	.stop			= bluetooth_playback_stop,
+	.pointer		= bluetooth_pointer,
+	.close			= bluetooth_close,
+	.hw_params		= bluetooth_a2dp_hw_params,
+	.prepare		= bluetooth_prepare,
+	.transfer		= bluetooth_a2dp_write,
+	.poll_descriptors_count	= bluetooth_playback_poll_descriptors_count,
+	.poll_descriptors	= bluetooth_playback_poll_descriptors,
+	.poll_revents		= bluetooth_playback_poll_revents,
+	.delay			= bluetooth_playback_delay,
+};
+
+static snd_pcm_ioplug_callback_t bluetooth_a2dp_capture = {
+	.start			= bluetooth_start,
+	.stop			= bluetooth_stop,
+	.pointer		= bluetooth_pointer,
+	.close			= bluetooth_close,
+	.hw_params		= bluetooth_a2dp_hw_params,
+	.prepare		= bluetooth_prepare,
+	.transfer		= bluetooth_a2dp_read,
+	.poll_descriptors	= bluetooth_poll_descriptors,
+	.poll_revents		= bluetooth_poll_revents,
+};
+
+#define ARRAY_NELEMS(a) (sizeof((a)) / sizeof((a)[0]))
+
+static int bluetooth_hsp_hw_constraint(snd_pcm_ioplug_t *io)
+{
+	struct bluetooth_data *data = io->private_data;
+	snd_pcm_access_t access_list[] = {
+		SND_PCM_ACCESS_RW_INTERLEAVED,
+		/* Mmap access is really useless fo this driver, but we
+		 * support it because some pieces of software out there
+		 * insist on using it */
+		SND_PCM_ACCESS_MMAP_INTERLEAVED
+	};
+	unsigned int format_list[] = {
+		SND_PCM_FORMAT_S16
+	};
+	int err;
+
+	/* access type */
+	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
+					ARRAY_NELEMS(access_list), access_list);
+	if (err < 0)
+		return err;
+
+	/* supported formats */
+	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
+					ARRAY_NELEMS(format_list), format_list);
+	if (err < 0)
+		return err;
+
+	/* supported channels */
+	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
+							1, 1);
+	if (err < 0)
+		return err;
+
+	/* supported rate */
+	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE,
+							8000, 8000);
+	if (err < 0)
+		return err;
+
+	/* supported block size */
+	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
+						data->link_mtu, data->link_mtu);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS,
+									2, 200);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int bluetooth_a2dp_hw_constraint(snd_pcm_ioplug_t *io)
+{
+	struct bluetooth_data *data = io->private_data;
+	struct bluetooth_a2dp *a2dp = &data->a2dp;
+	struct bluetooth_alsa_config *cfg = &data->alsa_config;
+	snd_pcm_access_t access_list[] = {
+		SND_PCM_ACCESS_RW_INTERLEAVED,
+		/* Mmap access is really useless fo this driver, but we
+		 * support it because some pieces of software out there
+		 * insist on using it */
+		SND_PCM_ACCESS_MMAP_INTERLEAVED
+	};
+	unsigned int format_list[] = {
+		SND_PCM_FORMAT_S16
+	};
+	unsigned int rate_list[4];
+	unsigned int rate_count;
+	int err, min_channels, max_channels;
+	unsigned int period_list[] = {
+		2048,
+		4096, /* e.g. 23.2msec/period (stereo 16bit at 44.1kHz) */
+		8192
+	};
+
+	/* access type */
+	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
+					ARRAY_NELEMS(access_list), access_list);
+	if (err < 0)
+		return err;
+
+	/* supported formats */
+	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
+					ARRAY_NELEMS(format_list), format_list);
+	if (err < 0)
+		return err;
+
+	/* supported channels */
+	if (cfg->has_channel_mode)
+		a2dp->sbc_capabilities.channel_mode = cfg->channel_mode;
+
+	if (a2dp->sbc_capabilities.channel_mode &
+			BT_A2DP_CHANNEL_MODE_MONO)
+		min_channels = 1;
+	else
+		min_channels = 2;
+
+	if (a2dp->sbc_capabilities.channel_mode &
+			(~BT_A2DP_CHANNEL_MODE_MONO))
+		max_channels = 2;
+	else
+		max_channels = 1;
+
+	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
+							min_channels, max_channels);
+	if (err < 0)
+		return err;
+
+	/* supported buffer sizes
+	 * (can be used as 3*8192, 6*4096, 12*2048, ...) */
+	err = snd_pcm_ioplug_set_param_minmax(io,
+						SND_PCM_IOPLUG_HW_BUFFER_BYTES,
+						8192*3, 8192*3);
+	if (err < 0)
+		return err;
+
+	/* supported block sizes: */
+	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
+				ARRAY_NELEMS(period_list), period_list);
+	if (err < 0)
+		return err;
+
+	/* supported rates */
+	rate_count = 0;
+	if (cfg->has_rate) {
+		rate_list[rate_count] = cfg->rate;
+		rate_count++;
+	} else {
+		if (a2dp->sbc_capabilities.frequency &
+				BT_SBC_SAMPLING_FREQ_16000) {
+			rate_list[rate_count] = 16000;
+			rate_count++;
+		}
+
+		if (a2dp->sbc_capabilities.frequency &
+				BT_SBC_SAMPLING_FREQ_32000) {
+			rate_list[rate_count] = 32000;
+			rate_count++;
+		}
+
+		if (a2dp->sbc_capabilities.frequency &
+				BT_SBC_SAMPLING_FREQ_44100) {
+			rate_list[rate_count] = 44100;
+			rate_count++;
+		}
+
+		if (a2dp->sbc_capabilities.frequency &
+				BT_SBC_SAMPLING_FREQ_48000) {
+			rate_list[rate_count] = 48000;
+			rate_count++;
+		}
+	}
+
+	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE,
+						rate_count, rate_list);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int bluetooth_parse_config(snd_config_t *conf,
+				struct bluetooth_alsa_config *bt_config)
+{
+	snd_config_iterator_t i, next;
+
+	memset(bt_config, 0, sizeof(struct bluetooth_alsa_config));
+
+	/* Set defaults */
+	bt_config->autoconnect = 1;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id, *value;
+
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+
+		if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+			continue;
+
+		if (strcmp(id, "autoconnect") == 0) {
+			int b;
+
+			b = snd_config_get_bool(n);
+			if (b < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			bt_config->autoconnect = b;
+			continue;
+		}
+
+		if (strcmp(id, "device") == 0 || strcmp(id, "bdaddr") == 0) {
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			bt_config->has_device = 1;
+			strncpy(bt_config->device, value, 18);
+			continue;
+		}
+
+		if (strcmp(id, "profile") == 0) {
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			if (strcmp(value, "auto") == 0) {
+				bt_config->transport = BT_CAPABILITIES_TRANSPORT_ANY;
+				bt_config->has_transport = 1;
+			} else if (strcmp(value, "voice") == 0 ||
+						strcmp(value, "hfp") == 0) {
+				bt_config->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+				bt_config->has_transport = 1;
+			} else if (strcmp(value, "hifi") == 0 ||
+						strcmp(value, "a2dp") == 0) {
+				bt_config->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+				bt_config->has_transport = 1;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "rate") == 0) {
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			bt_config->rate = atoi(value);
+			bt_config->has_rate = 1;
+			continue;
+		}
+
+		if (strcmp(id, "mode") == 0) {
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			if (strcmp(value, "mono") == 0) {
+				bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+				bt_config->has_channel_mode = 1;
+			} else if (strcmp(value, "dual") == 0) {
+				bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+				bt_config->has_channel_mode = 1;
+			} else if (strcmp(value, "stereo") == 0) {
+				bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+				bt_config->has_channel_mode = 1;
+			} else if (strcmp(value, "joint") == 0) {
+				bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+				bt_config->has_channel_mode = 1;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "allocation") == 0) {
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			if (strcmp(value, "loudness") == 0) {
+				bt_config->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+				bt_config->has_allocation_method = 1;
+			} else if (strcmp(value, "snr") == 0) {
+				bt_config->allocation_method = BT_A2DP_ALLOCATION_SNR;
+				bt_config->has_allocation_method = 1;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "subbands") == 0) {
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			bt_config->subbands = atoi(value);
+			bt_config->has_subbands = 1;
+			continue;
+		}
+
+		if (strcmp(id, "blocks") == 0) {
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			bt_config->block_length = atoi(value);
+			bt_config->has_block_length = 1;
+			continue;
+		}
+
+		if (strcmp(id, "bitpool") == 0) {
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			bt_config->bitpool = atoi(value);
+			bt_config->has_bitpool = 1;
+			continue;
+		}
+
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int audioservice_send(int sk, const bt_audio_msg_header_t *msg)
+{
+	int err;
+	uint16_t length;
+
+	length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+	DBG("sending %s:%s", bt_audio_strtype(msg->type),
+		bt_audio_strname(msg->name));
+	if (send(sk, msg, length, 0) > 0)
+		err = 0;
+	else {
+		err = -errno;
+		SNDERR("Error sending data to audio service: %s(%d)",
+			strerror(errno), errno);
+	}
+
+	return err;
+}
+
+static int audioservice_recv(int sk, bt_audio_msg_header_t *inmsg)
+{
+	int err;
+	ssize_t ret;
+	const char *type, *name;
+	uint16_t length;
+
+	length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+	DBG("trying to receive msg from audio service...");
+
+	ret = recv(sk, inmsg, length, 0);
+	if (ret < 0) {
+		err = -errno;
+		SNDERR("Error receiving IPC data from bluetoothd: %s (%d)",
+						strerror(errno), errno);
+	} else if ((size_t) ret < sizeof(bt_audio_msg_header_t)) {
+		SNDERR("Too short (%d bytes) IPC packet from bluetoothd", ret);
+		err = -EINVAL;
+	} else {
+		type = bt_audio_strtype(inmsg->type);
+		name = bt_audio_strname(inmsg->name);
+		if (type && name) {
+			DBG("Received %s - %s", type, name);
+			err = 0;
+		} else {
+			err = -EINVAL;
+			SNDERR("Bogus message type %d - name %d"
+					" received from audio service",
+					inmsg->type, inmsg->name);
+		}
+
+	}
+
+	return err;
+}
+
+static int audioservice_expect(int sk, bt_audio_msg_header_t *rsp,
+							int expected_name)
+{
+	bt_audio_error_t *error;
+	int err = audioservice_recv(sk, rsp);
+
+	if (err != 0)
+		return err;
+
+	if (rsp->name != expected_name) {
+		err = -EINVAL;
+		SNDERR("Bogus message %s received while %s was expected",
+				bt_audio_strname(rsp->name),
+				bt_audio_strname(expected_name));
+	}
+
+	if (rsp->type == BT_ERROR) {
+		error = (void *) rsp;
+		SNDERR("%s failed : %s(%d)",
+					bt_audio_strname(rsp->name),
+					strerror(error->posix_errno),
+					error->posix_errno);
+		return -error->posix_errno;
+	}
+
+	return err;
+}
+
+static int bluetooth_parse_capabilities(struct bluetooth_data *data,
+					struct bt_get_capabilities_rsp *rsp)
+{
+	int bytes_left = rsp->h.length - sizeof(*rsp);
+	codec_capabilities_t *codec = (void *) rsp->data;
+
+	data->transport = codec->transport;
+
+	if (codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP)
+		return 0;
+
+	while (bytes_left > 0) {
+		if ((codec->type == BT_A2DP_SBC_SINK) &&
+				!(codec->lock & BT_WRITE_LOCK))
+			break;
+
+		bytes_left -= codec->length;
+		codec = (void *) codec + codec->length;
+	}
+
+	if (bytes_left <= 0 ||
+			codec->length != sizeof(data->a2dp.sbc_capabilities))
+		return -EINVAL;
+
+	memcpy(&data->a2dp.sbc_capabilities, codec, codec->length);
+
+	return 0;
+}
+
+static int bluetooth_init(struct bluetooth_data *data,
+				snd_pcm_stream_t stream, snd_config_t *conf)
+{
+	int sk, err;
+	struct bluetooth_alsa_config *alsa_conf = &data->alsa_config;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_get_capabilities_req *req = (void *) buf;
+	struct bt_get_capabilities_rsp *rsp = (void *) buf;
+
+	memset(data, 0, sizeof(struct bluetooth_data));
+
+	err = bluetooth_parse_config(conf, alsa_conf);
+	if (err < 0)
+		return err;
+
+	data->server.fd = -1;
+	data->stream.fd = -1;
+
+	sk = bt_audio_service_open();
+	if (sk <= 0) {
+		err = -errno;
+		goto failed;
+	}
+
+	data->server.fd = sk;
+	data->server.events = POLLIN;
+
+	data->pipefd[0] = -1;
+	data->pipefd[1] = -1;
+
+	if (pipe(data->pipefd) < 0) {
+		err = -errno;
+		goto failed;
+	}
+	if (fcntl(data->pipefd[0], F_SETFL, O_NONBLOCK) < 0) {
+		err = -errno;
+		goto failed;
+	}
+	if (fcntl(data->pipefd[1], F_SETFL, O_NONBLOCK) < 0) {
+		err = -errno;
+		goto failed;
+	}
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_GET_CAPABILITIES;
+	req->h.length = sizeof(*req);
+
+	if (alsa_conf->autoconnect)
+		req->flags |= BT_FLAG_AUTOCONNECT;
+	strncpy(req->destination, alsa_conf->device, 18);
+	if (alsa_conf->has_transport)
+		req->transport = alsa_conf->transport;
+	else
+		req->transport = BT_CAPABILITIES_TRANSPORT_ANY;
+
+	err = audioservice_send(data->server.fd, &req->h);
+	if (err < 0)
+		goto failed;
+
+	rsp->h.length = 0;
+	err = audioservice_expect(data->server.fd, &rsp->h,
+					BT_GET_CAPABILITIES);
+	if (err < 0)
+		goto failed;
+
+	bluetooth_parse_capabilities(data, rsp);
+
+	return 0;
+
+failed:
+	if (sk >= 0)
+		bt_audio_service_close(sk);
+	return err;
+}
+
+SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth);
+
+SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth)
+{
+	struct bluetooth_data *data;
+	int err;
+
+	DBG("Bluetooth PCM plugin (%s)",
+		stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture");
+
+	data = malloc(sizeof(struct bluetooth_data));
+	if (!data) {
+		err = -ENOMEM;
+		goto error;
+	}
+
+	err = bluetooth_init(data, stream, conf);
+	if (err < 0)
+		goto error;
+
+	data->io.version = SND_PCM_IOPLUG_VERSION;
+	data->io.name = "Bluetooth Audio Device";
+	data->io.mmap_rw = 0; /* No direct mmap communication */
+	data->io.private_data = data;
+
+	if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+		data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
+			&bluetooth_a2dp_playback :
+			&bluetooth_a2dp_capture;
+	else
+		data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
+			&bluetooth_hsp_playback :
+			&bluetooth_hsp_capture;
+
+	err = snd_pcm_ioplug_create(&data->io, name, stream, mode);
+	if (err < 0)
+		goto error;
+
+	if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+		err = bluetooth_a2dp_hw_constraint(&data->io);
+	else
+		err = bluetooth_hsp_hw_constraint(&data->io);
+
+	if (err < 0) {
+		snd_pcm_ioplug_delete(&data->io);
+		goto error;
+	}
+
+	*pcmp = data->io.pcm;
+
+	return 0;
+
+error:
+	if (data)
+		bluetooth_exit(data);
+
+	return err;
+}
+
+SND_PCM_PLUGIN_SYMBOL(bluetooth);
diff --git a/audio/rtp.h b/audio/rtp.h
new file mode 100644
index 0000000..1457362
--- /dev/null
+++ b/audio/rtp.h
@@ -0,0 +1,76 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..90bceab
--- /dev/null
+++ b/audio/sink.c
@@ -0,0 +1,940 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009-2010  Motorola Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <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 "logging.h"
+
+#include "device.h"
+#include "avdtp.h"
+#include "a2dp.h"
+#include "error.h"
+#include "sink.h"
+#include "dbus-common.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#define STREAM_SETUP_RETRY_TIMER 2
+
+struct pending_request {
+	DBusConnection *conn;
+	DBusMessage *msg;
+	unsigned int id;
+};
+
+struct sink {
+	struct audio_device *dev;
+	struct avdtp *session;
+	struct avdtp_stream *stream;
+	unsigned int cb_id;
+	guint dc_id;
+	guint retry_id;
+	avdtp_session_state_t session_state;
+	avdtp_state_t stream_state;
+	sink_state_t state;
+	struct pending_request *connect;
+	struct pending_request *disconnect;
+	DBusConnection *conn;
+};
+
+struct sink_state_callback {
+	sink_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+static GSList *sink_callbacks = NULL;
+
+static unsigned int avdtp_callback_id = 0;
+
+static const char *state2str(sink_state_t state)
+{
+	switch (state) {
+	case SINK_STATE_DISCONNECTED:
+		return "disconnected";
+	case SINK_STATE_CONNECTING:
+		return "connecting";
+	case SINK_STATE_CONNECTED:
+		return "connected";
+	case SINK_STATE_PLAYING:
+		return "playing";
+	default:
+		error("Invalid sink state %d", state);
+		return NULL;
+	}
+}
+
+static void sink_set_state(struct audio_device *dev, sink_state_t new_state)
+{
+	struct sink *sink = dev->sink;
+	const char *state_str;
+	sink_state_t old_state = sink->state;
+	GSList *l;
+
+	sink->state = new_state;
+
+	state_str = state2str(new_state);
+	if (state_str)
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_SINK_INTERFACE, "State",
+					DBUS_TYPE_STRING, &state_str);
+
+	for (l = sink_callbacks; l != NULL; l = l->next) {
+		struct sink_state_callback *cb = l->data;
+		cb->cb(dev, old_state, new_state, cb->user_data);
+	}
+}
+
+static void avdtp_state_callback(struct audio_device *dev,
+					struct avdtp *session,
+					avdtp_session_state_t old_state,
+					avdtp_session_state_t new_state,
+					void *user_data)
+{
+	struct sink *sink = dev->sink;
+
+	if (sink == NULL)
+		return;
+
+	switch (new_state) {
+	case AVDTP_SESSION_STATE_DISCONNECTED:
+		if (sink->state != SINK_STATE_CONNECTING) {
+			gboolean value = FALSE;
+			g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_SINK_INTERFACE, "Disconnected",
+					DBUS_TYPE_INVALID);
+			emit_property_changed(dev->conn, dev->path,
+					AUDIO_SINK_INTERFACE, "Connected",
+					DBUS_TYPE_BOOLEAN, &value);
+			if (sink->dc_id) {
+				device_remove_disconnect_watch(dev->btd_dev,
+								sink->dc_id);
+				sink->dc_id = 0;
+			}
+		}
+		sink_set_state(dev, SINK_STATE_DISCONNECTED);
+		break;
+	case AVDTP_SESSION_STATE_CONNECTING:
+		sink_set_state(dev, SINK_STATE_CONNECTING);
+		break;
+	case AVDTP_SESSION_STATE_CONNECTED:
+		break;
+	}
+
+	sink->session_state = new_state;
+}
+
+static void pending_request_free(struct audio_device *dev,
+					struct pending_request *pending)
+{
+	if (pending->conn)
+		dbus_connection_unref(pending->conn);
+	if (pending->msg)
+		dbus_message_unref(pending->msg);
+	if (pending->id)
+		a2dp_cancel(dev, pending->id);
+
+	g_free(pending);
+}
+
+static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
+				void *user_data)
+{
+	struct audio_device *device = user_data;
+	struct sink *sink = device->sink;
+
+	debug("Sink: disconnect %s", device->path);
+
+	avdtp_close(sink->session, sink->stream);
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+					avdtp_state_t old_state,
+					avdtp_state_t new_state,
+					struct avdtp_error *err,
+					void *user_data)
+{
+	struct audio_device *dev = user_data;
+	struct sink *sink = dev->sink;
+	gboolean value;
+
+	if (err)
+		return;
+
+	switch (new_state) {
+	case AVDTP_STATE_IDLE:
+		if (sink->disconnect) {
+			DBusMessage *reply;
+			struct pending_request *p;
+
+			p = sink->disconnect;
+			sink->disconnect = NULL;
+
+			reply = dbus_message_new_method_return(p->msg);
+			g_dbus_send_message(p->conn, reply);
+			pending_request_free(dev, p);
+		}
+
+		if (sink->dc_id) {
+			device_remove_disconnect_watch(dev->btd_dev,
+							sink->dc_id);
+			sink->dc_id = 0;
+		}
+
+		if (sink->session) {
+			avdtp_unref(sink->session);
+			sink->session = NULL;
+		}
+		sink->stream = NULL;
+		sink->cb_id = 0;
+		break;
+	case AVDTP_STATE_OPEN:
+		if (old_state == AVDTP_STATE_CONFIGURED &&
+				sink->state == SINK_STATE_CONNECTING) {
+			value = TRUE;
+			g_dbus_emit_signal(dev->conn, dev->path,
+						AUDIO_SINK_INTERFACE,
+						"Connected",
+						DBUS_TYPE_INVALID);
+			emit_property_changed(dev->conn, dev->path,
+						AUDIO_SINK_INTERFACE,
+						"Connected",
+						DBUS_TYPE_BOOLEAN, &value);
+			sink->dc_id = device_add_disconnect_watch(dev->btd_dev,
+								disconnect_cb,
+								dev, NULL);
+		} else if (old_state == AVDTP_STATE_STREAMING) {
+			value = FALSE;
+			g_dbus_emit_signal(dev->conn, dev->path,
+						AUDIO_SINK_INTERFACE,
+						"Stopped",
+						DBUS_TYPE_INVALID);
+			emit_property_changed(dev->conn, dev->path,
+						AUDIO_SINK_INTERFACE,
+						"Playing",
+						DBUS_TYPE_BOOLEAN, &value);
+		}
+		sink_set_state(dev, SINK_STATE_CONNECTED);
+		break;
+	case AVDTP_STATE_STREAMING:
+		value = TRUE;
+		g_dbus_emit_signal(dev->conn, dev->path, AUDIO_SINK_INTERFACE,
+					"Playing", DBUS_TYPE_INVALID);
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_SINK_INTERFACE, "Playing",
+					DBUS_TYPE_BOOLEAN, &value);
+		sink_set_state(dev, SINK_STATE_PLAYING);
+		break;
+	case AVDTP_STATE_CONFIGURED:
+	case AVDTP_STATE_CLOSING:
+	case AVDTP_STATE_ABORTING:
+	default:
+		break;
+	}
+
+	sink->stream_state = new_state;
+}
+
+static DBusHandlerResult error_failed(DBusConnection *conn,
+					DBusMessage *msg, const char * desc)
+{
+	return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc);
+}
+
+static gboolean stream_setup_retry(gpointer user_data)
+{
+	struct sink *sink = user_data;
+	struct pending_request *pending = sink->connect;
+
+	sink->retry_id = 0;
+
+	if (sink->stream_state >= AVDTP_STATE_OPEN) {
+		debug("Stream successfully created, after XCASE connect:connect");
+		if (pending->msg) {
+			DBusMessage *reply;
+			reply = dbus_message_new_method_return(pending->msg);
+			g_dbus_send_message(pending->conn, reply);
+		}
+	} else {
+		debug("Stream setup failed, after XCASE connect:connect");
+		if (pending->msg)
+			error_failed(pending->conn, pending->msg, "Stream setup failed");
+	}
+
+	sink->connect = NULL;
+	pending_request_free(sink->dev, pending);
+
+	return FALSE;
+}
+
+static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err, void *user_data)
+{
+	struct sink *sink = user_data;
+	struct pending_request *pending;
+
+	pending = sink->connect;
+
+	pending->id = 0;
+
+	if (stream) {
+		debug("Stream successfully created");
+
+		if (pending->msg) {
+			DBusMessage *reply;
+			reply = dbus_message_new_method_return(pending->msg);
+			g_dbus_send_message(pending->conn, reply);
+		}
+
+		sink->connect = NULL;
+		pending_request_free(sink->dev, pending);
+
+		return;
+	}
+
+	avdtp_unref(sink->session);
+	sink->session = NULL;
+	if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+			&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
+		debug("connect:connect XCASE detected");
+		sink->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+							stream_setup_retry,
+							sink);
+	} else {
+		if (pending->msg)
+			error_failed(pending->conn, pending->msg, "Stream setup failed");
+		sink->connect = NULL;
+		pending_request_free(sink->dev, pending);
+		debug("Stream setup failed : %s", avdtp_strerror(err));
+	}
+}
+
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+	switch (freq) {
+	case SBC_SAMPLING_FREQ_16000:
+	case SBC_SAMPLING_FREQ_32000:
+		return 53;
+	case SBC_SAMPLING_FREQ_44100:
+		switch (mode) {
+		case SBC_CHANNEL_MODE_MONO:
+		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+			return 31;
+		case SBC_CHANNEL_MODE_STEREO:
+		case SBC_CHANNEL_MODE_JOINT_STEREO:
+			return 53;
+		default:
+			error("Invalid channel mode %u", mode);
+			return 53;
+		}
+	case SBC_SAMPLING_FREQ_48000:
+		switch (mode) {
+		case SBC_CHANNEL_MODE_MONO:
+		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+			return 29;
+		case SBC_CHANNEL_MODE_STEREO:
+		case SBC_CHANNEL_MODE_JOINT_STEREO:
+			return 51;
+		default:
+			error("Invalid channel mode %u", mode);
+			return 51;
+		}
+	default:
+		error("Invalid sampling freq %u", freq);
+		return 53;
+	}
+}
+
+static gboolean select_sbc_params(struct sbc_codec_cap *cap,
+					struct sbc_codec_cap *supported)
+{
+	unsigned int max_bitpool, min_bitpool;
+
+	memset(cap, 0, sizeof(struct sbc_codec_cap));
+
+	cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	cap->cap.media_codec_type = A2DP_CODEC_SBC;
+
+	if (supported->frequency & SBC_SAMPLING_FREQ_44100)
+		cap->frequency = SBC_SAMPLING_FREQ_44100;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
+		cap->frequency = SBC_SAMPLING_FREQ_48000;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
+		cap->frequency = SBC_SAMPLING_FREQ_32000;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
+		cap->frequency = SBC_SAMPLING_FREQ_16000;
+	else {
+		error("No supported frequencies");
+		return FALSE;
+	}
+
+	if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+		cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
+		cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+		cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
+		cap->channel_mode = SBC_CHANNEL_MODE_MONO;
+	else {
+		error("No supported channel modes");
+		return FALSE;
+	}
+
+	if (supported->block_length & SBC_BLOCK_LENGTH_16)
+		cap->block_length = SBC_BLOCK_LENGTH_16;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_12)
+		cap->block_length = SBC_BLOCK_LENGTH_12;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_8)
+		cap->block_length = SBC_BLOCK_LENGTH_8;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_4)
+		cap->block_length = SBC_BLOCK_LENGTH_4;
+	else {
+		error("No supported block lengths");
+		return FALSE;
+	}
+
+	if (supported->subbands & SBC_SUBBANDS_8)
+		cap->subbands = SBC_SUBBANDS_8;
+	else if (supported->subbands & SBC_SUBBANDS_4)
+		cap->subbands = SBC_SUBBANDS_4;
+	else {
+		error("No supported subbands");
+		return FALSE;
+	}
+
+	if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
+		cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
+	else if (supported->allocation_method & SBC_ALLOCATION_SNR)
+		cap->allocation_method = SBC_ALLOCATION_SNR;
+
+	min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
+	max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
+							supported->max_bitpool);
+
+	cap->min_bitpool = min_bitpool;
+	cap->max_bitpool = max_bitpool;
+
+	return TRUE;
+}
+
+static gboolean select_capabilities(struct avdtp *session,
+					struct avdtp_remote_sep *rsep,
+					GSList **caps)
+{
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct sbc_codec_cap sbc_cap;
+
+	media_codec = avdtp_get_codec(rsep);
+	if (!media_codec)
+		return FALSE;
+
+	select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+						sizeof(sbc_cap));
+
+	*caps = g_slist_append(*caps, media_codec);
+
+
+	return TRUE;
+}
+
+static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
+				void *user_data)
+{
+	struct sink *sink = user_data;
+	struct pending_request *pending;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_remote_sep *rsep;
+	struct a2dp_sep *sep;
+	GSList *caps = NULL;
+	int id;
+
+	pending = sink->connect;
+
+	if (err) {
+		avdtp_unref(sink->session);
+		sink->session = NULL;
+		if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+				&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
+			debug("connect:connect XCASE detected");
+			sink->retry_id =
+				g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+							stream_setup_retry,
+							sink);
+		} else
+			goto failed;
+		return;
+	}
+
+	debug("Discovery complete");
+
+	if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO,
+				A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
+		error("No matching ACP and INT SEPs found");
+		goto failed;
+	}
+
+	if (!select_capabilities(session, rsep, &caps)) {
+		error("Unable to select remote SEP capabilities");
+		goto failed;
+	}
+
+	sep = a2dp_get(session, rsep);
+	if (!sep) {
+		error("Unable to get a local source SEP");
+		goto failed;
+	}
+
+	id = a2dp_config(sink->session, sep, stream_setup_complete, caps, sink);
+	if (id == 0)
+		goto failed;
+
+	pending->id = id;
+	return;
+
+failed:
+	if (pending->msg)
+		error_failed(pending->conn, pending->msg, "Stream setup failed");
+	pending_request_free(sink->dev, pending);
+	sink->connect = NULL;
+	avdtp_unref(sink->session);
+	sink->session = NULL;
+}
+
+gboolean sink_setup_stream(struct sink *sink, struct avdtp *session)
+{
+	if (sink->connect || sink->disconnect)
+		return FALSE;
+
+	if (session && !sink->session)
+		sink->session = avdtp_ref(session);
+
+	if (!sink->session)
+		return FALSE;
+
+	avdtp_set_auto_disconnect(sink->session, FALSE);
+
+	if (avdtp_discover(sink->session, discovery_complete, sink) < 0)
+		return FALSE;
+
+	sink->connect = g_new0(struct pending_request, 1);
+
+	return TRUE;
+}
+
+static DBusMessage *sink_connect(DBusConnection *conn,
+				DBusMessage *msg, void *data)
+{
+	struct audio_device *dev = data;
+	struct sink *sink = dev->sink;
+	struct pending_request *pending;
+
+	if (!sink->session)
+		sink->session = avdtp_get(&dev->src, &dev->dst);
+
+	if (!sink->session)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Unable to get a session");
+
+	if (sink->connect || sink->disconnect)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(EBUSY));
+
+	if (sink->stream_state >= AVDTP_STATE_OPEN)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".AlreadyConnected",
+						"Device Already Connected");
+
+	if (!sink_setup_stream(sink, NULL))
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Failed to create a stream");
+
+	dev->auto_connect = FALSE;
+
+	pending = sink->connect;
+
+	pending->conn = dbus_connection_ref(conn);
+	pending->msg = dbus_message_ref(msg);
+
+	debug("stream creation in progress");
+
+	return NULL;
+}
+
+static DBusMessage *sink_disconnect(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct sink *sink = device->sink;
+	struct pending_request *pending;
+	int err;
+
+	if (!sink->session)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	if (sink->connect || sink->disconnect)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(EBUSY));
+
+	if (sink->stream_state < AVDTP_STATE_OPEN) {
+		DBusMessage *reply = dbus_message_new_method_return(msg);
+		if (!reply)
+			return NULL;
+		avdtp_unref(sink->session);
+		sink->session = NULL;
+		return reply;
+	}
+
+	err = avdtp_close(sink->session, sink->stream);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(-err));
+
+	pending = g_new0(struct pending_request, 1);
+	pending->conn = dbus_connection_ref(conn);
+	pending->msg = dbus_message_ref(msg);
+	sink->disconnect = pending;
+
+	return NULL;
+}
+
+static DBusMessage *sink_suspend(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct sink *sink = device->sink;
+	struct pending_request *pending;
+	int err;
+
+	if (!sink->session)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	if (sink->connect || sink->disconnect)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(EBUSY));
+
+	if (sink->state < AVDTP_STATE_OPEN) {
+		DBusMessage *reply = dbus_message_new_method_return(msg);
+		if (!reply)
+			return NULL;
+		avdtp_unref(sink->session);
+		sink->session = NULL;
+		return reply;
+	}
+
+	err = avdtp_suspend(sink->session, sink->stream);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(-err));
+
+	return NULL;
+}
+
+static DBusMessage *sink_resume(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct sink *sink = device->sink;
+	struct pending_request *pending;
+	int err;
+
+	if (!sink->session)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	if (sink->connect || sink->disconnect)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(EBUSY));
+
+	if (sink->state < AVDTP_STATE_OPEN) {
+		DBusMessage *reply = dbus_message_new_method_return(msg);
+		if (!reply)
+			return NULL;
+		avdtp_unref(sink->session);
+		sink->session = NULL;
+		return reply;
+	}
+
+	err = avdtp_start(sink->session, sink->stream);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(-err));
+
+	return NULL;
+}
+
+static DBusMessage *sink_is_connected(DBusConnection *conn,
+					DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct sink *sink = device->sink;
+	DBusMessage *reply;
+	dbus_bool_t connected;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	connected = (sink->stream_state >= AVDTP_STATE_CONFIGURED);
+
+	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+					DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static DBusMessage *sink_get_properties(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct sink *sink = device->sink;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	const char *state;
+	gboolean value;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* Playing */
+	value = (sink->stream_state == AVDTP_STATE_STREAMING);
+	dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value);
+
+	/* Connected */
+	value = (sink->stream_state >= AVDTP_STATE_CONFIGURED);
+	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+	/* State */
+	state = state2str(sink->state);
+	if (state)
+		dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static GDBusMethodTable sink_methods[] = {
+	{ "Connect",		"",	"",	sink_connect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Disconnect",		"",	"",	sink_disconnect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Suspend",        "", "", sink_suspend,
+                        G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Resume",         "", "", sink_resume,
+                        G_DBUS_METHOD_FLAG_ASYNC },
+	{ "IsConnected",	"",	"b",	sink_is_connected,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "GetProperties",	"",	"a{sv}",sink_get_properties },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable sink_signals[] = {
+	{ "Connected",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "Disconnected",		"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "Playing",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "Stopped",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "PropertyChanged",		"sv"	},
+	{ NULL, NULL }
+};
+
+static void sink_free(struct audio_device *dev)
+{
+	struct sink *sink = dev->sink;
+
+	if (sink->cb_id)
+		avdtp_stream_remove_cb(sink->session, sink->stream,
+					sink->cb_id);
+
+	if (sink->dc_id)
+		device_remove_disconnect_watch(dev->btd_dev, sink->dc_id);
+
+	if (sink->session)
+		avdtp_unref(sink->session);
+
+	if (sink->connect)
+		pending_request_free(dev, sink->connect);
+
+	if (sink->disconnect)
+		pending_request_free(dev, sink->disconnect);
+
+	if (sink->retry_id)
+		g_source_remove(sink->retry_id);
+
+	g_free(sink);
+	dev->sink = NULL;
+}
+
+static void path_unregister(void *data)
+{
+	struct audio_device *dev = data;
+
+	debug("Unregistered interface %s on path %s",
+		AUDIO_SINK_INTERFACE, dev->path);
+
+	sink_free(dev);
+}
+
+void sink_unregister(struct audio_device *dev)
+{
+	g_dbus_unregister_interface(dev->conn, dev->path,
+		AUDIO_SINK_INTERFACE);
+}
+
+struct sink *sink_init(struct audio_device *dev)
+{
+	struct sink *sink;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_SINK_INTERFACE,
+					sink_methods, sink_signals, NULL,
+					dev, path_unregister))
+		return NULL;
+
+	debug("Registered interface %s on path %s",
+		AUDIO_SINK_INTERFACE, dev->path);
+
+	if (avdtp_callback_id == 0)
+		avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback,
+									NULL);
+
+	sink = g_new0(struct sink, 1);
+
+	sink->dev = dev;
+
+	return sink;
+}
+
+gboolean sink_is_active(struct audio_device *dev)
+{
+	struct sink *sink = dev->sink;
+
+	if (sink->session)
+		return TRUE;
+
+	return FALSE;
+}
+
+avdtp_state_t sink_get_state(struct audio_device *dev)
+{
+	struct sink *sink = dev->sink;
+
+	return sink->stream_state;
+}
+
+gboolean sink_new_stream(struct audio_device *dev, struct avdtp *session,
+				struct avdtp_stream *stream)
+{
+	struct sink *sink = dev->sink;
+
+	if (sink->stream)
+		return FALSE;
+
+	if (!sink->session)
+		sink->session = avdtp_ref(session);
+
+	sink->stream = stream;
+
+	sink->cb_id = avdtp_stream_add_cb(session, stream,
+						stream_state_changed, dev);
+
+	return TRUE;
+}
+
+gboolean sink_shutdown(struct sink *sink)
+{
+	if (!sink->stream)
+		return FALSE;
+
+	if (avdtp_close(sink->session, sink->stream) < 0)
+		return FALSE;
+
+	return TRUE;
+}
+
+unsigned int sink_add_state_cb(sink_state_cb cb, void *user_data)
+{
+	struct sink_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct sink_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	sink_callbacks = g_slist_append(sink_callbacks, state_cb);
+
+	return state_cb->id;
+}
+
+gboolean sink_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = sink_callbacks; l != NULL; l = l->next) {
+		struct sink_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			sink_callbacks = g_slist_remove(sink_callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff --git a/audio/sink.h b/audio/sink.h
new file mode 100644
index 0000000..3f822bc
--- /dev/null
+++ b/audio/sink.h
@@ -0,0 +1,49 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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
index 0000000..1cec1f6
--- /dev/null
+++ b/audio/source.c
@@ -0,0 +1,800 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 "logging.h"
+
+#include "device.h"
+#include "avdtp.h"
+#include "a2dp.h"
+#include "error.h"
+#include "source.h"
+#include "dbus-common.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#define STREAM_SETUP_RETRY_TIMER 2
+
+struct pending_request {
+	DBusConnection *conn;
+	DBusMessage *msg;
+	unsigned int id;
+};
+
+struct source {
+	struct audio_device *dev;
+	struct avdtp *session;
+	struct avdtp_stream *stream;
+	unsigned int cb_id;
+	guint dc_id;
+	guint retry_id;
+	avdtp_session_state_t session_state;
+	avdtp_state_t stream_state;
+	source_state_t state;
+	struct pending_request *connect;
+	struct pending_request *disconnect;
+	DBusConnection *conn;
+};
+
+struct source_state_callback {
+	source_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+static GSList *source_callbacks = NULL;
+
+static unsigned int avdtp_callback_id = 0;
+
+static const char *state2str(source_state_t state)
+{
+	switch (state) {
+	case SOURCE_STATE_DISCONNECTED:
+		return "disconnected";
+	case SOURCE_STATE_CONNECTING:
+		return "connecting";
+	case SOURCE_STATE_CONNECTED:
+		return "connected";
+	case SOURCE_STATE_PLAYING:
+		return "playing";
+	default:
+		error("Invalid source state %d", state);
+		return NULL;
+	}
+}
+
+static void source_set_state(struct audio_device *dev, source_state_t new_state)
+{
+	struct source *source = dev->source;
+	const char *state_str;
+	source_state_t old_state = source->state;
+	GSList *l;
+
+	source->state = new_state;
+
+	state_str = state2str(new_state);
+	if (state_str)
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_SOURCE_INTERFACE, "State",
+					DBUS_TYPE_STRING, &state_str);
+
+	for (l = source_callbacks; l != NULL; l = l->next) {
+		struct source_state_callback *cb = l->data;
+		cb->cb(dev, old_state, new_state, cb->user_data);
+	}
+}
+
+static void avdtp_state_callback(struct audio_device *dev,
+					struct avdtp *session,
+					avdtp_session_state_t old_state,
+					avdtp_session_state_t new_state,
+					void *user_data)
+{
+	struct source *source = dev->source;
+
+	if (source == NULL)
+		return;
+
+	switch (new_state) {
+	case AVDTP_SESSION_STATE_DISCONNECTED:
+		if (source->state != SOURCE_STATE_CONNECTING &&
+				source->dc_id) {
+			device_remove_disconnect_watch(dev->btd_dev,
+							source->dc_id);
+			source->dc_id = 0;
+		}
+		source_set_state(dev, SOURCE_STATE_DISCONNECTED);
+		break;
+	case AVDTP_SESSION_STATE_CONNECTING:
+		source_set_state(dev, SOURCE_STATE_CONNECTING);
+		break;
+	case AVDTP_SESSION_STATE_CONNECTED:
+		break;
+	}
+
+	source->session_state = new_state;
+}
+
+static void pending_request_free(struct audio_device *dev,
+					struct pending_request *pending)
+{
+	if (pending->conn)
+		dbus_connection_unref(pending->conn);
+	if (pending->msg)
+		dbus_message_unref(pending->msg);
+	if (pending->id)
+		a2dp_cancel(dev, pending->id);
+
+	g_free(pending);
+}
+
+static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
+				void *user_data)
+{
+	struct audio_device *device = user_data;
+	struct source *source = device->source;
+
+	debug("Source: disconnect %s", device->path);
+
+	avdtp_close(source->session, source->stream);
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+					avdtp_state_t old_state,
+					avdtp_state_t new_state,
+					struct avdtp_error *err,
+					void *user_data)
+{
+	struct audio_device *dev = user_data;
+	struct source *source = dev->source;
+
+	if (err)
+		return;
+
+	switch (new_state) {
+	case AVDTP_STATE_IDLE:
+		if (source->disconnect) {
+			DBusMessage *reply;
+			struct pending_request *p;
+
+			p = source->disconnect;
+			source->disconnect = NULL;
+
+			reply = dbus_message_new_method_return(p->msg);
+			g_dbus_send_message(p->conn, reply);
+			pending_request_free(dev, p);
+		}
+
+		if (source->dc_id) {
+			device_remove_disconnect_watch(dev->btd_dev,
+							source->dc_id);
+			source->dc_id = 0;
+		}
+
+		if (source->session) {
+			avdtp_unref(source->session);
+			source->session = NULL;
+		}
+		source->stream = NULL;
+		source->cb_id = 0;
+		break;
+	case AVDTP_STATE_OPEN:
+		if (old_state == AVDTP_STATE_CONFIGURED &&
+				source->state == SOURCE_STATE_CONNECTING) {
+			source->dc_id = device_add_disconnect_watch(dev->btd_dev,
+								disconnect_cb,
+								dev, NULL);
+		}
+		source_set_state(dev, SOURCE_STATE_CONNECTED);
+		break;
+	case AVDTP_STATE_STREAMING:
+		source_set_state(dev, SOURCE_STATE_PLAYING);
+		break;
+	case AVDTP_STATE_CONFIGURED:
+	case AVDTP_STATE_CLOSING:
+	case AVDTP_STATE_ABORTING:
+	default:
+		break;
+	}
+
+	source->stream_state = new_state;
+}
+
+static DBusHandlerResult error_failed(DBusConnection *conn,
+					DBusMessage *msg, const char * desc)
+{
+	return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc);
+}
+
+static gboolean stream_setup_retry(gpointer user_data)
+{
+	struct source *source = user_data;
+	struct pending_request *pending = source->connect;
+
+	source->retry_id = 0;
+
+	if (source->stream_state >= AVDTP_STATE_OPEN) {
+		debug("Stream successfully created, after XCASE connect:connect");
+		if (pending->msg) {
+			DBusMessage *reply;
+			reply = dbus_message_new_method_return(pending->msg);
+			g_dbus_send_message(pending->conn, reply);
+		}
+	} else {
+		debug("Stream setup failed, after XCASE connect:connect");
+		if (pending->msg)
+			error_failed(pending->conn, pending->msg, "Stream setup failed");
+	}
+
+	source->connect = NULL;
+	pending_request_free(source->dev, pending);
+
+	return FALSE;
+}
+
+static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err, void *user_data)
+{
+	struct source *source = user_data;
+	struct pending_request *pending;
+
+	pending = source->connect;
+
+	pending->id = 0;
+
+	if (stream) {
+		debug("Stream successfully created");
+
+		if (pending->msg) {
+			DBusMessage *reply;
+			reply = dbus_message_new_method_return(pending->msg);
+			g_dbus_send_message(pending->conn, reply);
+		}
+
+		source->connect = NULL;
+		pending_request_free(source->dev, pending);
+
+		return;
+	}
+
+	avdtp_unref(source->session);
+	source->session = NULL;
+	if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+			&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
+		debug("connect:connect XCASE detected");
+		source->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+							stream_setup_retry,
+							source);
+	} else {
+		if (pending->msg)
+			error_failed(pending->conn, pending->msg, "Stream setup failed");
+		source->connect = NULL;
+		pending_request_free(source->dev, pending);
+		debug("Stream setup failed : %s", avdtp_strerror(err));
+	}
+}
+
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+	switch (freq) {
+	case SBC_SAMPLING_FREQ_16000:
+	case SBC_SAMPLING_FREQ_32000:
+		return 53;
+	case SBC_SAMPLING_FREQ_44100:
+		switch (mode) {
+		case SBC_CHANNEL_MODE_MONO:
+		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+			return 31;
+		case SBC_CHANNEL_MODE_STEREO:
+		case SBC_CHANNEL_MODE_JOINT_STEREO:
+			return 53;
+		default:
+			error("Invalid channel mode %u", mode);
+			return 53;
+		}
+	case SBC_SAMPLING_FREQ_48000:
+		switch (mode) {
+		case SBC_CHANNEL_MODE_MONO:
+		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+			return 29;
+		case SBC_CHANNEL_MODE_STEREO:
+		case SBC_CHANNEL_MODE_JOINT_STEREO:
+			return 51;
+		default:
+			error("Invalid channel mode %u", mode);
+			return 51;
+		}
+	default:
+		error("Invalid sampling freq %u", freq);
+		return 53;
+	}
+}
+
+static gboolean select_sbc_params(struct sbc_codec_cap *cap,
+					struct sbc_codec_cap *supported)
+{
+	unsigned int max_bitpool, min_bitpool;
+
+	memset(cap, 0, sizeof(struct sbc_codec_cap));
+
+	cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	cap->cap.media_codec_type = A2DP_CODEC_SBC;
+
+	if (supported->frequency & SBC_SAMPLING_FREQ_44100)
+		cap->frequency = SBC_SAMPLING_FREQ_44100;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
+		cap->frequency = SBC_SAMPLING_FREQ_48000;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
+		cap->frequency = SBC_SAMPLING_FREQ_32000;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
+		cap->frequency = SBC_SAMPLING_FREQ_16000;
+	else {
+		error("No supported frequencies");
+		return FALSE;
+	}
+
+	if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+		cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
+		cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+		cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
+		cap->channel_mode = SBC_CHANNEL_MODE_MONO;
+	else {
+		error("No supported channel modes");
+		return FALSE;
+	}
+
+	if (supported->block_length & SBC_BLOCK_LENGTH_16)
+		cap->block_length = SBC_BLOCK_LENGTH_16;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_12)
+		cap->block_length = SBC_BLOCK_LENGTH_12;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_8)
+		cap->block_length = SBC_BLOCK_LENGTH_8;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_4)
+		cap->block_length = SBC_BLOCK_LENGTH_4;
+	else {
+		error("No supported block lengths");
+		return FALSE;
+	}
+
+	if (supported->subbands & SBC_SUBBANDS_8)
+		cap->subbands = SBC_SUBBANDS_8;
+	else if (supported->subbands & SBC_SUBBANDS_4)
+		cap->subbands = SBC_SUBBANDS_4;
+	else {
+		error("No supported subbands");
+		return FALSE;
+	}
+
+	if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
+		cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
+	else if (supported->allocation_method & SBC_ALLOCATION_SNR)
+		cap->allocation_method = SBC_ALLOCATION_SNR;
+
+	min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
+	max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
+							supported->max_bitpool);
+
+	cap->min_bitpool = min_bitpool;
+	cap->max_bitpool = max_bitpool;
+
+	return TRUE;
+}
+
+static gboolean select_capabilities(struct avdtp *session,
+					struct avdtp_remote_sep *rsep,
+					GSList **caps)
+{
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct sbc_codec_cap sbc_cap;
+
+	media_codec = avdtp_get_codec(rsep);
+	if (!media_codec)
+		return FALSE;
+
+	select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+						sizeof(sbc_cap));
+
+	*caps = g_slist_append(*caps, media_codec);
+
+
+	return TRUE;
+}
+
+static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
+				void *user_data)
+{
+	struct source *source = user_data;
+	struct pending_request *pending;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_remote_sep *rsep;
+	struct a2dp_sep *sep;
+	GSList *caps = NULL;
+	int id;
+
+	pending = source->connect;
+
+	if (err) {
+		avdtp_unref(source->session);
+		source->session = NULL;
+		if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+				&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
+			debug("connect:connect XCASE detected");
+			source->retry_id =
+				g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+							stream_setup_retry,
+							source);
+		} else
+			goto failed;
+		return;
+	}
+
+	debug("Discovery complete");
+
+	if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SOURCE, AVDTP_MEDIA_TYPE_AUDIO,
+				A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
+		error("No matching ACP and INT SEPs found");
+		goto failed;
+	}
+
+	if (!select_capabilities(session, rsep, &caps)) {
+		error("Unable to select remote SEP capabilities");
+		goto failed;
+	}
+
+	sep = a2dp_get(session, rsep);
+	if (!sep) {
+		error("Unable to get a local sink SEP");
+		goto failed;
+	}
+
+	id = a2dp_config(source->session, sep, stream_setup_complete, caps,
+				source);
+	if (id == 0)
+		goto failed;
+
+	pending->id = id;
+	return;
+
+failed:
+	if (pending->msg)
+		error_failed(pending->conn, pending->msg, "Stream setup failed");
+	pending_request_free(source->dev, pending);
+	source->connect = NULL;
+	avdtp_unref(source->session);
+	source->session = NULL;
+}
+
+gboolean source_setup_stream(struct source *source, struct avdtp *session)
+{
+	if (source->connect || source->disconnect)
+		return FALSE;
+
+	if (session && !source->session)
+		source->session = avdtp_ref(session);
+
+	if (!source->session)
+		return FALSE;
+
+	avdtp_set_auto_disconnect(source->session, FALSE);
+
+	if (avdtp_discover(source->session, discovery_complete, source) < 0)
+		return FALSE;
+
+	source->connect = g_new0(struct pending_request, 1);
+
+	return TRUE;
+}
+
+static DBusMessage *source_connect(DBusConnection *conn,
+				DBusMessage *msg, void *data)
+{
+	struct audio_device *dev = data;
+	struct source *source = dev->source;
+	struct pending_request *pending;
+
+	if (!source->session)
+		source->session = avdtp_get(&dev->src, &dev->dst);
+
+	if (!source->session)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Unable to get a session");
+
+	if (source->connect || source->disconnect)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(EBUSY));
+
+	if (source->stream_state >= AVDTP_STATE_OPEN)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".AlreadyConnected",
+						"Device Already Connected");
+
+	if (!source_setup_stream(source, NULL))
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Failed to create a stream");
+
+	dev->auto_connect = FALSE;
+
+	pending = source->connect;
+
+	pending->conn = dbus_connection_ref(conn);
+	pending->msg = dbus_message_ref(msg);
+
+	debug("stream creation in progress");
+
+	return NULL;
+}
+
+static DBusMessage *source_disconnect(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct source *source = device->source;
+	struct pending_request *pending;
+	int err;
+
+	if (!source->session)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	if (source->connect || source->disconnect)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(EBUSY));
+
+	if (source->stream_state < AVDTP_STATE_OPEN) {
+		DBusMessage *reply = dbus_message_new_method_return(msg);
+		if (!reply)
+			return NULL;
+		avdtp_unref(source->session);
+		source->session = NULL;
+		return reply;
+	}
+
+	err = avdtp_close(source->session, source->stream);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(-err));
+
+	pending = g_new0(struct pending_request, 1);
+	pending->conn = dbus_connection_ref(conn);
+	pending->msg = dbus_message_ref(msg);
+	source->disconnect = pending;
+
+	return NULL;
+}
+
+static DBusMessage *source_get_properties(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct source *source = device->source;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	const char *state;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* State */
+	state = state2str(source->state);
+	if (state)
+		dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static GDBusMethodTable source_methods[] = {
+	{ "Connect",		"",	"",	source_connect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Disconnect",		"",	"",	source_disconnect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "GetProperties",	"",	"a{sv}",source_get_properties },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable source_signals[] = {
+	{ "PropertyChanged",		"sv"	},
+	{ NULL, NULL }
+};
+
+static void source_free(struct audio_device *dev)
+{
+	struct source *source = dev->source;
+
+	if (source->cb_id)
+		avdtp_stream_remove_cb(source->session, source->stream,
+					source->cb_id);
+
+	if (source->dc_id)
+		device_remove_disconnect_watch(dev->btd_dev, source->dc_id);
+
+	if (source->session)
+		avdtp_unref(source->session);
+
+	if (source->connect)
+		pending_request_free(dev, source->connect);
+
+	if (source->disconnect)
+		pending_request_free(dev, source->disconnect);
+
+	if (source->retry_id)
+		g_source_remove(source->retry_id);
+
+	g_free(source);
+	dev->source = NULL;
+}
+
+static void path_unregister(void *data)
+{
+	struct audio_device *dev = data;
+
+	debug("Unregistered interface %s on path %s",
+		AUDIO_SOURCE_INTERFACE, dev->path);
+
+	source_free(dev);
+}
+
+void source_unregister(struct audio_device *dev)
+{
+	g_dbus_unregister_interface(dev->conn, dev->path,
+		AUDIO_SOURCE_INTERFACE);
+}
+
+struct source *source_init(struct audio_device *dev)
+{
+	struct source *source;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_SOURCE_INTERFACE,
+					source_methods, source_signals, NULL,
+					dev, path_unregister))
+		return NULL;
+
+	debug("Registered interface %s on path %s",
+		AUDIO_SOURCE_INTERFACE, dev->path);
+
+	if (avdtp_callback_id == 0)
+		avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback,
+									NULL);
+
+	source = g_new0(struct source, 1);
+
+	source->dev = dev;
+
+	return source;
+}
+
+gboolean source_is_active(struct audio_device *dev)
+{
+	struct source *source = dev->source;
+
+	if (source->session)
+		return TRUE;
+
+	return FALSE;
+}
+
+avdtp_state_t source_get_state(struct audio_device *dev)
+{
+	struct source *source = dev->source;
+
+	return source->stream_state;
+}
+
+gboolean source_new_stream(struct audio_device *dev, struct avdtp *session,
+				struct avdtp_stream *stream)
+{
+	struct source *source = dev->source;
+
+	if (source->stream)
+		return FALSE;
+
+	if (!source->session)
+		source->session = avdtp_ref(session);
+
+	source->stream = stream;
+
+	source->cb_id = avdtp_stream_add_cb(session, stream,
+						stream_state_changed, dev);
+
+	return TRUE;
+}
+
+gboolean source_shutdown(struct source *source)
+{
+	if (!source->stream)
+		return FALSE;
+
+	if (avdtp_close(source->session, source->stream) < 0)
+		return FALSE;
+
+	return TRUE;
+}
+
+unsigned int source_add_state_cb(source_state_cb cb, void *user_data)
+{
+	struct source_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct source_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	source_callbacks = g_slist_append(source_callbacks, state_cb);
+
+	return state_cb->id;
+}
+
+gboolean source_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = source_callbacks; l != NULL; l = l->next) {
+		struct source_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			source_callbacks = g_slist_remove(source_callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff --git a/audio/source.h b/audio/source.h
new file mode 100644
index 0000000..8250814
--- /dev/null
+++ b/audio/source.h
@@ -0,0 +1,50 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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
index 0000000..deb604c
--- /dev/null
+++ b/audio/telephony-dummy.c
@@ -0,0 +1,417 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 "logging.h"
+#include "telephony.h"
+
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+static char *subscriber_number = NULL;
+static char *active_call_number = NULL;
+static int active_call_status = 0;
+static int active_call_dir = 0;
+
+static gboolean events_enabled = FALSE;
+
+/* Response and hold state
+ * -1 = none
+ *  0 = incoming call is put on hold in the AG
+ *  1 = held incoming call is accepted in the AG
+ *  2 = held incoming call is rejected in the AG
+ */
+static int response_and_hold = -1;
+
+static struct indicator dummy_indicators[] =
+{
+	{ "battchg",	"0-5",	5,	TRUE },
+	{ "signal",	"0-5",	5,	TRUE },
+	{ "service",	"0,1",	1,	TRUE },
+	{ "call",	"0,1",	0,	TRUE },
+	{ "callsetup",	"0-3",	0,	TRUE },
+	{ "callheld",	"0-2",	0,	FALSE },
+	{ "roam",	"0,1",	0,	TRUE },
+	{ NULL }
+};
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments",
+					"Invalid arguments in method call");
+}
+
+void telephony_device_connected(void *telephony_device)
+{
+	debug("telephony-dummy: device %p connected", telephony_device);
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+	debug("telephony-dummy: device %p disconnected", telephony_device);
+	events_enabled = FALSE;
+}
+
+void telephony_event_reporting_req(void *telephony_device, int ind)
+{
+	events_enabled = ind == 1 ? TRUE : FALSE;
+
+	telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_response_and_hold_req(void *telephony_device, int rh)
+{
+	response_and_hold = rh;
+
+	telephony_response_and_hold_ind(response_and_hold);
+
+	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+	telephony_last_dialed_number_rsp(telephony_device, CME_ERROR_NONE);
+
+	/* Notify outgoing call set-up successfully initiated */
+	telephony_update_indicator(dummy_indicators, "callsetup",
+					EV_CALLSETUP_OUTGOING);
+	telephony_update_indicator(dummy_indicators, "callsetup",
+					EV_CALLSETUP_ALERTING);
+
+	active_call_status = CALL_STATUS_ALERTING;
+	active_call_dir = CALL_DIR_OUTGOING;
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+	g_free(active_call_number);
+	active_call_number = NULL;
+
+	telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
+
+	if (telephony_get_indicator(dummy_indicators, "callsetup") > 0)
+		telephony_update_indicator(dummy_indicators, "callsetup",
+						EV_CALLSETUP_INACTIVE);
+	else
+		telephony_update_indicator(dummy_indicators, "call",
+						EV_CALL_INACTIVE);
+}
+
+void telephony_answer_call_req(void *telephony_device)
+{
+	telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+
+	telephony_update_indicator(dummy_indicators, "call", EV_CALL_ACTIVE);
+	telephony_update_indicator(dummy_indicators, "callsetup",
+					EV_CALLSETUP_INACTIVE);
+
+	active_call_status = CALL_STATUS_ACTIVE;
+}
+
+void telephony_dial_number_req(void *telephony_device, const char *number)
+{
+	g_free(active_call_number);
+	active_call_number = g_strdup(number);
+
+	debug("telephony-dummy: dial request to %s", active_call_number);
+
+	telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+
+	/* Notify outgoing call set-up successfully initiated */
+	telephony_update_indicator(dummy_indicators, "callsetup",
+					EV_CALLSETUP_OUTGOING);
+	telephony_update_indicator(dummy_indicators, "callsetup",
+					EV_CALLSETUP_ALERTING);
+
+	active_call_status = CALL_STATUS_ALERTING;
+	active_call_dir = CALL_DIR_OUTGOING;
+}
+
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+	debug("telephony-dummy: transmit dtmf: %c", tone);
+	telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+	debug("telephony-dummy: subscriber number request");
+	if (subscriber_number)
+		telephony_subscriber_number_ind(subscriber_number,
+						NUMBER_TYPE_TELEPHONY,
+						SUBSCRIBER_SERVICE_VOICE);
+	telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_list_current_calls_req(void *telephony_device)
+{
+	debug("telephony-dummy: list current calls request");
+	if (active_call_number)
+		telephony_list_current_call_ind(1, active_call_dir,
+						active_call_status,
+						CALL_MODE_VOICE,
+						CALL_MULTIPARTY_NO,
+						active_call_number,
+						NUMBER_TYPE_TELEPHONY);
+	telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_operator_selection_req(void *telephony_device)
+{
+	telephony_operator_selection_ind(OPERATOR_MODE_AUTO, "DummyOperator");
+	telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_call_hold_req(void *telephony_device, const char *cmd)
+{
+	debug("telephony-dymmy: got call hold request %s", cmd);
+	telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
+{
+	debug("telephony-dummy: got %s NR and EC request",
+			enable ? "enable" : "disable");
+
+	telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+	debug("telephony-dummy: got key press request for %s", keys);
+	telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+/* D-Bus method handlers */
+static DBusMessage *outgoing_call(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	const char *number;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+						DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	debug("telephony-dummy: outgoing call to %s", number);
+
+	g_free(active_call_number);
+	active_call_number = g_strdup(number);
+
+	telephony_update_indicator(dummy_indicators, "callsetup",
+					EV_CALLSETUP_OUTGOING);
+	telephony_update_indicator(dummy_indicators, "callsetup",
+					EV_CALLSETUP_ALERTING);
+
+	active_call_status = CALL_STATUS_ALERTING;
+	active_call_dir = CALL_DIR_OUTGOING;
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *incoming_call(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	const char *number;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+						DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	debug("telephony-dummy: incoming call to %s", number);
+
+	g_free(active_call_number);
+	active_call_number = g_strdup(number);
+
+	telephony_update_indicator(dummy_indicators, "callsetup",
+					EV_CALLSETUP_INCOMING);
+
+	active_call_status = CALL_STATUS_INCOMING;
+	active_call_dir = CALL_DIR_INCOMING;
+
+	telephony_incoming_call_ind(number, NUMBER_TYPE_TELEPHONY);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *cancel_call(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	debug("telephony-dummy: cancel call");
+
+	g_free(active_call_number);
+	active_call_number = NULL;
+
+	if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) {
+		telephony_update_indicator(dummy_indicators, "callsetup",
+						EV_CALLSETUP_INACTIVE);
+		telephony_calling_stopped_ind();
+	}
+
+	if (telephony_get_indicator(dummy_indicators, "call") > 0)
+		telephony_update_indicator(dummy_indicators, "call",
+						EV_CALL_INACTIVE);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *signal_strength(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	dbus_uint32_t strength;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &strength,
+						DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	if (strength > 5)
+		return invalid_args(msg);
+
+	telephony_update_indicator(dummy_indicators, "signal", strength);
+
+	debug("telephony-dummy: signal strength set to %u", strength);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *battery_level(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	dbus_uint32_t level;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &level,
+						DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	if (level > 5)
+		return invalid_args(msg);
+
+	telephony_update_indicator(dummy_indicators, "battchg", level);
+
+	debug("telephony-dummy: battery level set to %u", level);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *roaming_status(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	dbus_bool_t roaming;
+	int val;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &roaming,
+						DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	val = roaming ? EV_ROAM_ACTIVE : EV_ROAM_INACTIVE;
+
+	telephony_update_indicator(dummy_indicators, "roam", val);
+
+	debug("telephony-dummy: roaming status set to %d", val);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *registration_status(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	dbus_bool_t registration;
+	int val;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &registration,
+						DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	val = registration ? EV_SERVICE_PRESENT : EV_SERVICE_NONE;
+
+	telephony_update_indicator(dummy_indicators, "service", val);
+
+	debug("telephony-dummy: registration status set to %d", val);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_subscriber_number(DBusConnection *conn,
+						DBusMessage *msg,
+						void *data)
+{
+	const char *number;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+						DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	g_free(subscriber_number);
+	subscriber_number = g_strdup(number);
+
+	debug("telephony-dummy: subscriber number set to %s", number);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static GDBusMethodTable dummy_methods[] = {
+	{ "OutgoingCall",	"s",	"",	outgoing_call		},
+	{ "IncomingCall",	"s",	"",	incoming_call		},
+	{ "CancelCall",		"",	"",	cancel_call		},
+	{ "SignalStrength",	"u",	"",	signal_strength		},
+	{ "BatteryLevel",	"u",	"",	battery_level		},
+	{ "RoamingStatus",	"b",	"",	roaming_status		},
+	{ "RegistrationStatus",	"b",	"",	registration_status	},
+	{ "SetSubscriberNumber","s",	"",	set_subscriber_number	},
+	{ }
+};
+
+static DBusConnection *connection = NULL;
+
+int telephony_init(void)
+{
+	uint32_t features = AG_FEATURE_REJECT_A_CALL |
+				AG_FEATURE_ENHANCED_CALL_STATUS |
+				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;
+
+	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+	g_dbus_register_interface(connection, "/org/bluez/test",
+					"org.bluez.TelephonyTest",
+					dummy_methods, NULL,
+					NULL, NULL, NULL);
+
+	telephony_ready_ind(features, dummy_indicators, response_and_hold,
+				chld_str);
+
+	return 0;
+}
+
+void telephony_exit(void)
+{
+	dbus_connection_unref(connection);
+	connection = NULL;
+}
diff --git a/audio/telephony-maemo.c b/audio/telephony-maemo.c
new file mode 100644
index 0000000..f302cdf
--- /dev/null
+++ b/audio/telephony-maemo.c
@@ -0,0 +1,2061 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2008-2009  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 "logging.h"
+#include "telephony.h"
+
+/* libcsnet D-Bus definitions */
+#define NETWORK_BUS_NAME		"com.nokia.phone.net"
+#define NETWORK_INTERFACE		"Phone.Net"
+#define NETWORK_PATH			"/com/nokia/phone/net"
+
+/* Mask bits for supported services */
+#define NETWORK_MASK_GPRS_SUPPORT	0x01
+#define NETWORK_MASK_CS_SERVICES	0x02
+#define NETWORK_MASK_EGPRS_SUPPORT	0x04
+#define NETWORK_MASK_HSDPA_AVAIL	0x08
+#define NETWORK_MASK_HSUPA_AVAIL	0x10
+
+/* network get cell info: cell type */
+#define NETWORK_UNKNOWN_CELL		0
+#define NETWORK_GSM_CELL		1
+#define NETWORK_WCDMA_CELL		2
+
+enum net_registration_status {
+	NETWORK_REG_STATUS_HOME = 0x00,
+	NETWORK_REG_STATUS_ROAM,
+	NETWORK_REG_STATUS_ROAM_BLINK,
+	NETWORK_REG_STATUS_NOSERV,
+	NETWORK_REG_STATUS_NOSERV_SEARCHING,
+	NETWORK_REG_STATUS_NOSERV_NOTSEARCHING,
+	NETWORK_REG_STATUS_NOSERV_NOSIM,
+	NETWORK_REG_STATUS_POWER_OFF = 0x08,
+	NETWORK_REG_STATUS_NSPS,
+	NETWORK_REG_STATUS_NSPS_NO_COVERAGE,
+	NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW
+};
+
+enum network_types {
+	NETWORK_GSM_HOME_PLMN = 0,
+	NETWORK_GSM_PREFERRED_PLMN,
+	NETWORK_GSM_FORBIDDEN_PLMN,
+	NETWORK_GSM_OTHER_PLMN,
+	NETWORK_GSM_NO_PLMN_AVAIL
+};
+
+enum network_alpha_tag_name_type {
+	NETWORK_HARDCODED_LATIN_OPER_NAME = 0,
+	NETWORK_HARDCODED_USC2_OPER_NAME,
+	NETWORK_NITZ_SHORT_OPER_NAME,
+	NETWORK_NITZ_FULL_OPER_NAME,
+};
+
+#define TELEPHONY_MAEMO_PATH		"/com/nokia/MaemoTelephony"
+#define TELEPHONY_MAEMO_INTERFACE	"com.nokia.MaemoTelephony"
+
+#define CALLERID_BASE		"/var/lib/bluetooth/maemo-callerid-"
+#define ALLOWED_FLAG_FILE	"/var/lib/bluetooth/maemo-callerid-allowed"
+#define RESTRICTED_FLAG_FILE	"/var/lib/bluetooth/maemo-callerid-restricted"
+#define NONE_FLAG_FILE		"/var/lib/bluetooth/maemo-callerid-none"
+
+static uint32_t callerid = 0;
+
+/* CSD CALL plugin D-Bus definitions */
+#define CSD_CALL_BUS_NAME	"com.nokia.csd.Call"
+#define CSD_CALL_INTERFACE	"com.nokia.csd.Call"
+#define CSD_CALL_INSTANCE	"com.nokia.csd.Call.Instance"
+#define CSD_CALL_CONFERENCE	"com.nokia.csd.Call.Conference"
+#define CSD_CALL_PATH		"/com/nokia/csd/call"
+
+/* Call status values as exported by the CSD CALL plugin */
+#define CSD_CALL_STATUS_IDLE			0
+#define CSD_CALL_STATUS_CREATE			1
+#define CSD_CALL_STATUS_COMING			2
+#define CSD_CALL_STATUS_PROCEEDING		3
+#define CSD_CALL_STATUS_MO_ALERTING		4
+#define CSD_CALL_STATUS_MT_ALERTING		5
+#define CSD_CALL_STATUS_WAITING			6
+#define CSD_CALL_STATUS_ANSWERED		7
+#define CSD_CALL_STATUS_ACTIVE			8
+#define CSD_CALL_STATUS_MO_RELEASE		9
+#define CSD_CALL_STATUS_MT_RELEASE		10
+#define CSD_CALL_STATUS_HOLD_INITIATED		11
+#define CSD_CALL_STATUS_HOLD			12
+#define CSD_CALL_STATUS_RETRIEVE_INITIATED	13
+#define CSD_CALL_STATUS_RECONNECT_PENDING	14
+#define CSD_CALL_STATUS_TERMINATED		15
+#define CSD_CALL_STATUS_SWAP_INITIATED		16
+
+#define CALL_FLAG_NONE				0
+#define CALL_FLAG_PRESENTATION_ALLOWED		0x01
+#define CALL_FLAG_PRESENTATION_RESTRICTED	0x02
+
+/* SIM Phonebook D-Bus definitions */
+#define SIM_PHONEBOOK_BUS_NAME			"com.nokia.phone.SIM"
+#define SIM_PHONEBOOK_INTERFACE			"Phone.Sim.Phonebook"
+#define SIM_PHONEBOOK_PATH			"/com/nokia/phone/SIM/phonebook"
+
+#define PHONEBOOK_INDEX_FIRST_ENTRY		0xFFFF
+#define PHONEBOOK_INDEX_NEXT_FREE_LOCATION	0xFFFE
+
+enum sim_phonebook_type {
+	SIM_PHONEBOOK_TYPE_ADN = 0x0,
+	SIM_PHONEBOOK_TYPE_SDN,
+	SIM_PHONEBOOK_TYPE_FDN,
+	SIM_PHONEBOOK_TYPE_VMBX,
+	SIM_PHONEBOOK_TYPE_MBDN,
+	SIM_PHONEBOOK_TYPE_EN,
+	SIM_PHONEBOOK_TYPE_MSISDN
+};
+
+enum sim_phonebook_location_type {
+	SIM_PHONEBOOK_LOCATION_EXACT = 0x0,
+	SIM_PHONEBOOK_LOCATION_NEXT
+};
+
+struct csd_call {
+	char *object_path;
+	int status;
+	gboolean originating;
+	gboolean emergency;
+	gboolean on_hold;
+	gboolean conference;
+	char *number;
+	gboolean setup;
+};
+
+static struct {
+	uint8_t status;
+	uint16_t lac;
+	uint32_t cell_id;
+	uint32_t operator_code;
+	uint32_t country_code;
+	uint8_t network_type;
+	uint8_t supported_services;
+	uint16_t signals_bar;
+	char *operator_name;
+} net = {
+	.status = NETWORK_REG_STATUS_NOSERV,
+	.lac = 0,
+	.cell_id = 0,
+	.operator_code = 0,
+	.country_code = 0,
+	.network_type = NETWORK_GSM_NO_PLMN_AVAIL,
+	.supported_services = 0,
+	.signals_bar = 0,
+	.operator_name = NULL,
+};
+
+static guint csd_watch = 0;
+
+static DBusConnection *connection = NULL;
+
+static GSList *calls = NULL;
+
+/* Reference count for determining the call indicator status */
+static GSList *active_calls = NULL;
+
+static char *msisdn = NULL;	/* Subscriber number */
+static char *vmbx = NULL;	/* Voice mailbox number */
+
+/* HAL battery namespace key values */
+static int battchg_cur = -1;	/* "battery.charge_level.current" */
+static int battchg_last = -1;	/* "battery.charge_level.last_full" */
+static int battchg_design = -1;	/* "battery.charge_level.design" */
+
+static gboolean events_enabled = FALSE;
+
+/* Supported set of call hold operations */
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+
+/* Response and hold state
+ * -1 = none
+ *  0 = incoming call is put on hold in the AG
+ *  1 = held incoming call is accepted in the AG
+ *  2 = held incoming call is rejected in the AG
+ */
+static int response_and_hold = -1;
+
+static char *last_dialed_number = NULL;
+
+/* Timer for tracking call creation requests */
+static guint create_request_timer = 0;
+
+static struct indicator maemo_indicators[] =
+{
+	{ "battchg",	"0-5",	5,	TRUE },
+	{ "signal",	"0-5",	5,	TRUE },
+	{ "service",	"0,1",	1,	TRUE },
+	{ "call",	"0,1",	0,	TRUE },
+	{ "callsetup",	"0-3",	0,	TRUE },
+	{ "callheld",	"0-2",	0,	FALSE },
+	{ "roam",	"0,1",	0,	TRUE },
+	{ NULL }
+};
+
+static char *call_status_str[] = {
+	"IDLE",
+	"CREATE",
+	"COMING",
+	"PROCEEDING",
+	"MO_ALERTING",
+	"MT_ALERTING",
+	"WAITING",
+	"ANSWERED",
+	"ACTIVE",
+	"MO_RELEASE",
+	"MT_RELEASE",
+	"HOLD_INITIATED",
+	"HOLD",
+	"RETRIEVE_INITIATED",
+	"RECONNECT_PENDING",
+	"TERMINATED",
+	"SWAP_INITIATED",
+	"???"
+};
+
+static struct csd_call *find_call(const char *path)
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct csd_call *call = l->data;
+
+		if (g_str_equal(call->object_path, path))
+			return call;
+	}
+
+	return NULL;
+}
+
+static struct csd_call *find_non_held_call(void)
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct csd_call *call = l->data;
+
+		if (call->status == CSD_CALL_STATUS_IDLE)
+			continue;
+
+		if (call->status != CSD_CALL_STATUS_HOLD)
+			return call;
+	}
+
+	return NULL;
+}
+
+static struct csd_call *find_non_idle_call(void)
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct csd_call *call = l->data;
+
+		if (call->status != CSD_CALL_STATUS_IDLE)
+			return call;
+	}
+
+	return NULL;
+}
+
+static struct csd_call *find_call_with_status(int status)
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct csd_call *call = l->data;
+
+		if (call->status == status)
+			return call;
+	}
+
+	return NULL;
+}
+
+static int release_call(struct csd_call *call)
+{
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+						call->object_path,
+						CSD_CALL_INSTANCE,
+						"Release");
+	if (!msg) {
+		error("Unable to allocate new D-Bus message");
+		return -ENOMEM;
+	}
+
+	g_dbus_send_message(connection, msg);
+
+	return 0;
+}
+
+static int answer_call(struct csd_call *call)
+{
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+						call->object_path,
+						CSD_CALL_INSTANCE,
+						"Answer");
+	if (!msg) {
+		error("Unable to allocate new D-Bus message");
+		return -ENOMEM;
+	}
+
+	g_dbus_send_message(connection, msg);
+
+	return 0;
+}
+
+static int split_call(struct csd_call *call)
+{
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+						call->object_path,
+						CSD_CALL_INSTANCE,
+						"Split");
+	if (!msg) {
+		error("Unable to allocate new D-Bus message");
+		return -ENOMEM;
+	}
+
+	g_dbus_send_message(connection, msg);
+
+	return 0;
+}
+
+static int unhold_call(struct csd_call *call)
+{
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+						CSD_CALL_INTERFACE,
+						"Unhold");
+	if (!msg) {
+		error("Unable to allocate new D-Bus message");
+		return -ENOMEM;
+	}
+
+	g_dbus_send_message(connection, msg);
+
+	return 0;
+}
+
+static int hold_call(struct csd_call *call)
+{
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+						CSD_CALL_INTERFACE,
+						"Hold");
+	if (!msg) {
+		error("Unable to allocate new D-Bus message");
+		return -ENOMEM;
+	}
+
+	g_dbus_send_message(connection, msg);
+
+	return 0;
+}
+
+static int swap_calls(void)
+{
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+						CSD_CALL_INTERFACE,
+						"Swap");
+	if (!msg) {
+		error("Unable to allocate new D-Bus message");
+		return -ENOMEM;
+	}
+
+	g_dbus_send_message(connection, msg);
+
+	return 0;
+}
+
+static int create_conference(void)
+{
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+						CSD_CALL_INTERFACE,
+						"Conference");
+	if (!msg) {
+		error("Unable to allocate new D-Bus message");
+		return -ENOMEM;
+	}
+
+	g_dbus_send_message(connection, msg);
+
+	return 0;
+}
+
+static int call_transfer(void)
+{
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+						CSD_CALL_INTERFACE,
+						"Transfer");
+	if (!msg) {
+		error("Unable to allocate new D-Bus message");
+		return -ENOMEM;
+	}
+
+	g_dbus_send_message(connection, msg);
+
+	return 0;
+}
+
+static int number_type(const char *number)
+{
+	if (number == NULL)
+		return NUMBER_TYPE_TELEPHONY;
+
+	if (number[0] == '+' || strncmp(number, "00", 2) == 0)
+		return NUMBER_TYPE_INTERNATIONAL;
+
+	return NUMBER_TYPE_TELEPHONY;
+}
+
+void telephony_device_connected(void *telephony_device)
+{
+	struct csd_call *coming;
+
+	debug("telephony-maemo: device %p connected", telephony_device);
+
+	coming = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+	if (coming) {
+		if (find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+			telephony_call_waiting_ind(coming->number,
+						number_type(coming->number));
+		else
+			telephony_incoming_call_ind(coming->number,
+						number_type(coming->number));
+	}
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+	debug("telephony-maemo: device %p disconnected", telephony_device);
+	events_enabled = FALSE;
+}
+
+void telephony_event_reporting_req(void *telephony_device, int ind)
+{
+	events_enabled = ind == 1 ? TRUE : FALSE;
+
+	telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_response_and_hold_req(void *telephony_device, int rh)
+{
+	response_and_hold = rh;
+
+	telephony_response_and_hold_ind(response_and_hold);
+
+	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+	debug("telephony-maemo: last dialed number request");
+
+	if (last_dialed_number)
+		telephony_dial_number_req(telephony_device,
+						last_dialed_number);
+	else
+		telephony_last_dialed_number_rsp(telephony_device,
+						CME_ERROR_NOT_ALLOWED);
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+	struct csd_call *call;
+
+	call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+	if (!call)
+		call = find_non_idle_call();
+
+	if (!call) {
+		error("No active call");
+		telephony_terminate_call_rsp(telephony_device,
+						CME_ERROR_NOT_ALLOWED);
+		return;
+	}
+
+	if (release_call(call) < 0)
+		telephony_terminate_call_rsp(telephony_device,
+						CME_ERROR_AG_FAILURE);
+	else
+		telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_answer_call_req(void *telephony_device)
+{
+	struct csd_call *call;
+
+	call = find_call_with_status(CSD_CALL_STATUS_COMING);
+	if (!call)
+		call = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+
+	if (!call)
+		call = find_call_with_status(CSD_CALL_STATUS_PROCEEDING);
+
+	if (!call)
+		call = find_call_with_status(CSD_CALL_STATUS_WAITING);
+
+	if (!call) {
+		telephony_answer_call_rsp(telephony_device,
+						CME_ERROR_NOT_ALLOWED);
+		return;
+	}
+
+	if (answer_call(call) < 0)
+		telephony_answer_call_rsp(telephony_device,
+						CME_ERROR_AG_FAILURE);
+	else
+		telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static int send_method_call(const char *dest, const char *path,
+				const char *interface, const char *method,
+				DBusPendingCallNotifyFunction cb,
+				void *user_data, int type, ...)
+{
+	DBusMessage *msg;
+	DBusPendingCall *call;
+	va_list args;
+
+	msg = dbus_message_new_method_call(dest, path, interface, method);
+	if (!msg) {
+		error("Unable to allocate new D-Bus %s message", method);
+		return -ENOMEM;
+	}
+
+	va_start(args, type);
+
+	if (!dbus_message_append_args_valist(msg, type, args)) {
+		dbus_message_unref(msg);
+		va_end(args);
+		return -EIO;
+	}
+
+	va_end(args);
+
+	if (!cb) {
+		g_dbus_send_message(connection, msg);
+		return 0;
+	}
+
+	if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) {
+		error("Sending %s failed", method);
+		dbus_message_unref(msg);
+		return -EIO;
+	}
+
+	dbus_pending_call_set_notify(call, cb, user_data, NULL);
+	dbus_pending_call_unref(call);
+	dbus_message_unref(msg);
+
+	return 0;
+}
+
+static const char *memory_dial_lookup(int location)
+{
+	if (location == 1)
+		return vmbx;
+	else
+		return NULL;
+}
+
+void telephony_dial_number_req(void *telephony_device, const char *number)
+{
+	uint32_t flags = callerid;
+	int ret;
+
+	debug("telephony-maemo: dial request to %s", number);
+
+	if (strncmp(number, "*31#", 4) == 0) {
+		number += 4;
+		flags = CALL_FLAG_PRESENTATION_ALLOWED;
+	} else if (strncmp(number, "#31#", 4) == 0) {
+		number += 4;
+		flags = CALL_FLAG_PRESENTATION_RESTRICTED;
+	} else if (number[0] == '>') {
+		const char *location = &number[1];
+
+		number = memory_dial_lookup(strtol(&number[1], NULL, 0));
+		if (!number) {
+			error("No number at memory location %s", location);
+			telephony_dial_number_rsp(telephony_device,
+						CME_ERROR_INVALID_INDEX);
+			return;
+		}
+	}
+
+	ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+				CSD_CALL_INTERFACE, "CreateWith",
+				NULL, NULL,
+				DBUS_TYPE_STRING, &number,
+				DBUS_TYPE_UINT32, &flags,
+				DBUS_TYPE_INVALID);
+	if (ret < 0) {
+		telephony_dial_number_rsp(telephony_device,
+						CME_ERROR_AG_FAILURE);
+		return;
+	}
+
+	telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+	int ret;
+	char buf[2] = { tone, '\0' }, *buf_ptr = buf;
+
+	debug("telephony-maemo: transmit dtmf: %s", buf);
+
+	ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+				CSD_CALL_INTERFACE, "SendDTMF",
+				NULL, NULL,
+				DBUS_TYPE_STRING, &buf_ptr,
+				DBUS_TYPE_INVALID);
+	if (ret < 0) {
+		telephony_transmit_dtmf_rsp(telephony_device,
+						CME_ERROR_AG_FAILURE);
+		return;
+	}
+
+	telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+	debug("telephony-maemo: subscriber number request");
+	if (msisdn)
+		telephony_subscriber_number_ind(msisdn,
+						number_type(msisdn),
+						SUBSCRIBER_SERVICE_VOICE);
+	telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static int csd_status_to_hfp(struct csd_call *call)
+{
+	switch (call->status) {
+	case CSD_CALL_STATUS_IDLE:
+	case CSD_CALL_STATUS_MO_RELEASE:
+	case CSD_CALL_STATUS_MT_RELEASE:
+	case CSD_CALL_STATUS_TERMINATED:
+		return -1;
+	case CSD_CALL_STATUS_CREATE:
+		return CALL_STATUS_DIALING;
+	case CSD_CALL_STATUS_WAITING:
+		return CALL_STATUS_WAITING;
+	case CSD_CALL_STATUS_PROCEEDING:
+		/* PROCEEDING can happen in outgoing/incoming */
+		if (call->originating)
+			return CALL_STATUS_DIALING;
+		else
+			return CALL_STATUS_INCOMING;
+	case CSD_CALL_STATUS_COMING:
+		return CALL_STATUS_INCOMING;
+	case CSD_CALL_STATUS_MO_ALERTING:
+		return CALL_STATUS_ALERTING;
+	case CSD_CALL_STATUS_MT_ALERTING:
+		return CALL_STATUS_INCOMING;
+	case CSD_CALL_STATUS_ANSWERED:
+	case CSD_CALL_STATUS_ACTIVE:
+	case CSD_CALL_STATUS_RECONNECT_PENDING:
+	case CSD_CALL_STATUS_SWAP_INITIATED:
+	case CSD_CALL_STATUS_HOLD_INITIATED:
+		return CALL_STATUS_ACTIVE;
+	case CSD_CALL_STATUS_RETRIEVE_INITIATED:
+	case CSD_CALL_STATUS_HOLD:
+		return CALL_STATUS_HELD;
+	default:
+		return -1;
+	}
+}
+
+void telephony_list_current_calls_req(void *telephony_device)
+{
+	GSList *l;
+	int i;
+
+	debug("telephony-maemo: list current calls request");
+
+	for (l = calls, i = 1; l != NULL; l = l->next, i++) {
+		struct csd_call *call = l->data;
+		int status, direction, multiparty;
+
+		status = csd_status_to_hfp(call);
+		if (status < 0)
+			continue;
+
+		direction = call->originating ?
+				CALL_DIR_OUTGOING : CALL_DIR_INCOMING;
+
+		multiparty = call->conference ?
+				CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO;
+
+		telephony_list_current_call_ind(i, direction, status,
+						CALL_MODE_VOICE, multiparty,
+						call->number,
+						number_type(call->number));
+	}
+
+	telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_operator_selection_req(void *telephony_device)
+{
+	telephony_operator_selection_ind(OPERATOR_MODE_AUTO,
+				net.operator_name ? net.operator_name : "");
+	telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static void foreach_call_with_status(int status,
+					int (*func)(struct csd_call *call))
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct csd_call *call = l->data;
+
+		if (call->status == status)
+			func(call);
+	}
+}
+
+void telephony_call_hold_req(void *telephony_device, const char *cmd)
+{
+	const char *idx;
+	struct csd_call *call;
+	int err = 0;
+
+	debug("telephony-maemo: got call hold request %s", cmd);
+
+	if (strlen(cmd) > 1)
+		idx = &cmd[1];
+	else
+		idx = NULL;
+
+	if (idx)
+		call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1);
+	else
+		call = NULL;
+
+	switch (cmd[0]) {
+	case '0':
+		foreach_call_with_status(CSD_CALL_STATUS_HOLD, release_call);
+		foreach_call_with_status(CSD_CALL_STATUS_WAITING,
+								release_call);
+		break;
+	case '1':
+		if (idx) {
+			if (call)
+				err = release_call(call);
+			break;
+		}
+		foreach_call_with_status(CSD_CALL_STATUS_ACTIVE, release_call);
+		call = find_call_with_status(CSD_CALL_STATUS_WAITING);
+		if (call)
+			err = answer_call(call);
+		break;
+	case '2':
+		if (idx) {
+			if (call)
+				err = split_call(call);
+		} else {
+			struct csd_call *held, *wait;
+
+			call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+			held = find_call_with_status(CSD_CALL_STATUS_HOLD);
+			wait = find_call_with_status(CSD_CALL_STATUS_WAITING);
+
+			if (wait)
+				err = answer_call(wait);
+			else if (call && held)
+				err = swap_calls();
+			else {
+				if (call)
+					err = hold_call(call);
+				if (held)
+					err = unhold_call(held);
+			}
+		}
+		break;
+	case '3':
+		if (find_call_with_status(CSD_CALL_STATUS_HOLD) ||
+				find_call_with_status(CSD_CALL_STATUS_WAITING))
+			err = create_conference();
+		break;
+	case '4':
+		err = call_transfer();
+		break;
+	default:
+		debug("Unknown call hold request");
+		break;
+	}
+
+	if (err)
+		telephony_call_hold_rsp(telephony_device,
+					CME_ERROR_AG_FAILURE);
+	else
+		telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
+{
+	debug("telephony-maemo: got %s NR and EC request",
+			enable ? "enable" : "disable");
+	telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+	struct csd_call *active, *waiting;
+	int err;
+
+	debug("telephony-maemo: got key press request for %s", keys);
+
+	waiting = find_call_with_status(CSD_CALL_STATUS_COMING);
+	if (!waiting)
+		waiting = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+	if (!waiting)
+		waiting = find_call_with_status(CSD_CALL_STATUS_PROCEEDING);
+
+	active = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+
+	if (waiting)
+		err = answer_call(waiting);
+	else if (active)
+		err = release_call(active);
+	else
+		err = 0;
+
+	if (err < 0)
+		telephony_key_press_rsp(telephony_device,
+							CME_ERROR_AG_FAILURE);
+	else
+		telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static void handle_incoming_call(DBusMessage *msg)
+{
+	const char *number, *call_path;
+	struct csd_call *call;
+
+	if (!dbus_message_get_args(msg, NULL,
+					DBUS_TYPE_OBJECT_PATH, &call_path,
+					DBUS_TYPE_STRING, &number,
+					DBUS_TYPE_INVALID)) {
+		error("Unexpected parameters in Call.Coming() signal");
+		return;
+	}
+
+	call = find_call(call_path);
+	if (!call) {
+		error("Didn't find any matching call object for %s",
+				call_path);
+		return;
+	}
+
+	debug("Incoming call to %s from number %s", call_path, number);
+
+	g_free(call->number);
+	call->number = g_strdup(number);
+
+	telephony_update_indicator(maemo_indicators, "callsetup",
+					EV_CALLSETUP_INCOMING);
+
+	if (find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+		telephony_call_waiting_ind(call->number,
+						number_type(call->number));
+	else
+		telephony_incoming_call_ind(call->number,
+						number_type(call->number));
+}
+
+static void handle_outgoing_call(DBusMessage *msg)
+{
+	const char *number, *call_path;
+	struct csd_call *call;
+
+	if (!dbus_message_get_args(msg, NULL,
+					DBUS_TYPE_OBJECT_PATH, &call_path,
+					DBUS_TYPE_STRING, &number,
+					DBUS_TYPE_INVALID)) {
+		error("Unexpected parameters in Call.Created() signal");
+		return;
+	}
+
+	call = find_call(call_path);
+	if (!call) {
+		error("Didn't find any matching call object for %s",
+				call_path);
+		return;
+	}
+
+	debug("Outgoing call from %s to number %s", call_path, number);
+
+	g_free(call->number);
+	call->number = g_strdup(number);
+
+	g_free(last_dialed_number);
+	last_dialed_number = g_strdup(number);
+
+	if (create_request_timer) {
+		g_source_remove(create_request_timer);
+		create_request_timer = 0;
+	}
+}
+
+static gboolean create_timeout(gpointer user_data)
+{
+	telephony_update_indicator(maemo_indicators, "callsetup",
+					EV_CALLSETUP_INACTIVE);
+	create_request_timer = 0;
+	return FALSE;
+}
+
+static void handle_create_requested(DBusMessage *msg)
+{
+	debug("Call.CreateRequested()");
+
+	if (create_request_timer)
+		g_source_remove(create_request_timer);
+
+	create_request_timer = g_timeout_add_seconds(5, create_timeout, NULL);
+
+	telephony_update_indicator(maemo_indicators, "callsetup",
+					EV_CALLSETUP_OUTGOING);
+}
+
+static void handle_call_status(DBusMessage *msg, const char *call_path)
+{
+	struct csd_call *call;
+	dbus_uint32_t status, cause_type, cause;
+	int callheld = telephony_get_indicator(maemo_indicators, "callheld");
+
+	if (!dbus_message_get_args(msg, NULL,
+					DBUS_TYPE_UINT32, &status,
+					DBUS_TYPE_UINT32, &cause_type,
+					DBUS_TYPE_UINT32, &cause,
+					DBUS_TYPE_INVALID)) {
+		error("Unexpected paramters in Instance.CallStatus() signal");
+		return;
+	}
+
+	call = find_call(call_path);
+	if (!call) {
+		error("Didn't find any matching call object for %s",
+				call_path);
+		return;
+	}
+
+	if (status > 16) {
+		error("Invalid call status %u", status);
+		return;
+	}
+
+	debug("Call %s changed from %s to %s", call_path,
+		call_status_str[call->status], call_status_str[status]);
+
+	if (call->status == (int) status) {
+		debug("Ignoring CSD Call state change to existing state");
+		return;
+	}
+
+	call->status = (int) status;
+
+	switch (status) {
+	case CSD_CALL_STATUS_IDLE:
+		if (call->setup) {
+			telephony_update_indicator(maemo_indicators,
+							"callsetup",
+							EV_CALLSETUP_INACTIVE);
+			if (!call->originating)
+				telephony_calling_stopped_ind();
+		}
+
+		g_free(call->number);
+		call->number = NULL;
+		call->originating = FALSE;
+		call->emergency = FALSE;
+		call->on_hold = FALSE;
+		call->conference = FALSE;
+		call->setup = FALSE;
+		break;
+	case CSD_CALL_STATUS_CREATE:
+		call->originating = TRUE;
+		call->setup = TRUE;
+		break;
+	case CSD_CALL_STATUS_COMING:
+		call->originating = FALSE;
+		call->setup = TRUE;
+		break;
+	case CSD_CALL_STATUS_PROCEEDING:
+		break;
+	case CSD_CALL_STATUS_MO_ALERTING:
+		telephony_update_indicator(maemo_indicators, "callsetup",
+						EV_CALLSETUP_ALERTING);
+		break;
+	case CSD_CALL_STATUS_MT_ALERTING:
+		break;
+	case CSD_CALL_STATUS_WAITING:
+		break;
+	case CSD_CALL_STATUS_ANSWERED:
+		break;
+	case CSD_CALL_STATUS_ACTIVE:
+		if (call->on_hold) {
+			call->on_hold = FALSE;
+			if (find_call_with_status(CSD_CALL_STATUS_HOLD))
+				telephony_update_indicator(maemo_indicators,
+							"callheld",
+							EV_CALLHELD_MULTIPLE);
+			else
+				telephony_update_indicator(maemo_indicators,
+							"callheld",
+							EV_CALLHELD_NONE);
+		} else {
+			if (!g_slist_find(active_calls, call))
+				active_calls = g_slist_prepend(active_calls, call);
+			if (g_slist_length(active_calls) == 1)
+				telephony_update_indicator(maemo_indicators,
+								"call",
+								EV_CALL_ACTIVE);
+			/* Upgrade callheld status if necessary */
+			if (callheld == EV_CALLHELD_ON_HOLD)
+				telephony_update_indicator(maemo_indicators,
+							"callheld",
+							EV_CALLHELD_MULTIPLE);
+			telephony_update_indicator(maemo_indicators,
+							"callsetup",
+							EV_CALLSETUP_INACTIVE);
+			if (!call->originating)
+				telephony_calling_stopped_ind();
+			call->setup = FALSE;
+		}
+		break;
+	case CSD_CALL_STATUS_MO_RELEASE:
+	case CSD_CALL_STATUS_MT_RELEASE:
+		active_calls = g_slist_remove(active_calls, call);
+		if (g_slist_length(active_calls) == 0)
+			telephony_update_indicator(maemo_indicators, "call",
+							EV_CALL_INACTIVE);
+		break;
+	case CSD_CALL_STATUS_HOLD_INITIATED:
+		break;
+	case CSD_CALL_STATUS_HOLD:
+		call->on_hold = TRUE;
+		if (find_non_held_call())
+			telephony_update_indicator(maemo_indicators,
+							"callheld",
+							EV_CALLHELD_MULTIPLE);
+		else
+			telephony_update_indicator(maemo_indicators,
+							"callheld",
+							EV_CALLHELD_ON_HOLD);
+		break;
+	case CSD_CALL_STATUS_RETRIEVE_INITIATED:
+		break;
+	case CSD_CALL_STATUS_RECONNECT_PENDING:
+		break;
+	case CSD_CALL_STATUS_TERMINATED:
+		if (call->on_hold &&
+				!find_call_with_status(CSD_CALL_STATUS_HOLD))
+			telephony_update_indicator(maemo_indicators,
+							"callheld",
+							EV_CALLHELD_NONE);
+		else if (callheld == EV_CALLHELD_MULTIPLE &&
+				find_call_with_status(CSD_CALL_STATUS_HOLD))
+			telephony_update_indicator(maemo_indicators,
+							"callheld",
+							EV_CALLHELD_ON_HOLD);
+		break;
+	case CSD_CALL_STATUS_SWAP_INITIATED:
+		break;
+	default:
+		error("Unknown call status %u", status);
+		break;
+	}
+}
+
+static void handle_conference(DBusMessage *msg, gboolean joined)
+{
+	const char *path;
+	struct csd_call *call;
+
+	if (!dbus_message_get_args(msg, NULL,
+					DBUS_TYPE_OBJECT_PATH, &path,
+					DBUS_TYPE_INVALID)) {
+		error("Unexpected parameters in Conference.%s",
+					dbus_message_get_member(msg));
+		return;
+	}
+
+	call = find_call(path);
+	if (!call) {
+		error("Conference signal for unknown call %s", path);
+		return;
+	}
+
+	debug("Call %s %s the conference", path, joined ? "joined" : "left");
+
+	call->conference = joined;
+}
+
+static void get_operator_name_reply(DBusPendingCall *pending_call,
+					void *user_data)
+{
+	DBusMessage *reply;
+	DBusError err;
+	const char *name;
+	dbus_int32_t net_err;
+
+	reply = dbus_pending_call_steal_reply(pending_call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("get_operator_name failed: %s, %s",
+			err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_error_init(&err);
+	if (!dbus_message_get_args(reply, &err,
+					DBUS_TYPE_STRING, &name,
+					DBUS_TYPE_INT32, &net_err,
+					DBUS_TYPE_INVALID)) {
+		error("Unexpected get_operator_name reply parameters: %s, %s",
+			err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	if (net_err != 0) {
+		error("get_operator_name failed with code %d", net_err);
+		goto done;
+	}
+
+	if (strlen(name) == 0)
+		goto done;
+
+	g_free(net.operator_name);
+	net.operator_name = g_strdup(name);
+
+	debug("telephony-maemo: operator name updated: %s", name);
+
+done:
+	dbus_message_unref(reply);
+}
+
+static void resolve_operator_name(uint32_t operator, uint32_t country)
+{
+	uint8_t name_type = NETWORK_HARDCODED_LATIN_OPER_NAME;
+
+	send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
+				NETWORK_INTERFACE, "get_operator_name",
+				get_operator_name_reply, NULL,
+				DBUS_TYPE_BYTE, &name_type,
+				DBUS_TYPE_UINT32, &operator,
+				DBUS_TYPE_UINT32, &country,
+				DBUS_TYPE_INVALID);
+}
+
+static void update_registration_status(uint8_t status, uint16_t lac,
+					uint32_t cell_id,
+					uint32_t operator_code,
+					uint32_t country_code,
+					uint8_t network_type,
+					uint8_t supported_services)
+{
+	if (net.status != status) {
+		switch (status) {
+		case NETWORK_REG_STATUS_HOME:
+			telephony_update_indicator(maemo_indicators, "roam",
+							EV_ROAM_INACTIVE);
+			if (net.status >= NETWORK_REG_STATUS_NOSERV)
+				telephony_update_indicator(maemo_indicators,
+							"service",
+							EV_SERVICE_PRESENT);
+			break;
+		case NETWORK_REG_STATUS_ROAM:
+		case NETWORK_REG_STATUS_ROAM_BLINK:
+			telephony_update_indicator(maemo_indicators, "roam",
+							EV_ROAM_ACTIVE);
+			if (net.status >= NETWORK_REG_STATUS_NOSERV)
+				telephony_update_indicator(maemo_indicators,
+							"service",
+							EV_SERVICE_PRESENT);
+			break;
+		case NETWORK_REG_STATUS_NOSERV:
+		case NETWORK_REG_STATUS_NOSERV_SEARCHING:
+		case NETWORK_REG_STATUS_NOSERV_NOTSEARCHING:
+		case NETWORK_REG_STATUS_NOSERV_NOSIM:
+		case NETWORK_REG_STATUS_POWER_OFF:
+		case NETWORK_REG_STATUS_NSPS:
+		case NETWORK_REG_STATUS_NSPS_NO_COVERAGE:
+		case NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW:
+			if (net.status < NETWORK_REG_STATUS_NOSERV)
+				telephony_update_indicator(maemo_indicators,
+							"service",
+							EV_SERVICE_NONE);
+			break;
+		}
+
+		net.status = status;
+	}
+
+	net.lac = lac;
+	net.cell_id = cell_id;
+
+	if (net.operator_code != operator_code ||
+			net.country_code != country_code) {
+		g_free(net.operator_name);
+		net.operator_name = NULL;
+		resolve_operator_name(operator_code, country_code);
+		net.operator_code = operator_code;
+		net.country_code = country_code;
+	}
+
+	net.network_type = network_type;
+	net.supported_services = supported_services;
+}
+
+static void handle_registration_status_change(DBusMessage *msg)
+{
+	uint8_t status;
+	dbus_uint16_t lac, network_type, supported_services;
+	dbus_uint32_t cell_id, operator_code, country_code;
+
+	if (!dbus_message_get_args(msg, NULL,
+					DBUS_TYPE_BYTE, &status,
+					DBUS_TYPE_UINT16, &lac,
+					DBUS_TYPE_UINT32, &cell_id,
+					DBUS_TYPE_UINT32, &operator_code,
+					DBUS_TYPE_UINT32, &country_code,
+					DBUS_TYPE_BYTE, &network_type,
+					DBUS_TYPE_BYTE, &supported_services,
+					DBUS_TYPE_INVALID)) {
+		error("Unexpected parameters in registration_status_change");
+		return;
+	}
+
+	update_registration_status(status, lac, cell_id, operator_code,
+					country_code, network_type,
+					supported_services);
+}
+
+static void update_signal_strength(uint8_t signals_bar)
+{
+	int signal;
+
+	if (signals_bar > 100) {
+		debug("signals_bar greater than expected: %u", signals_bar);
+		signals_bar = 100;
+	}
+
+	if (net.signals_bar == signals_bar)
+		return;
+
+	/* A simple conversion from 0-100 to 0-5 (used by HFP) */
+	signal = (signals_bar + 20) / 21;
+
+	telephony_update_indicator(maemo_indicators, "signal", signal);
+
+	net.signals_bar = signals_bar;
+
+	debug("Signal strength updated: %u/100, %d/5", signals_bar, signal);
+}
+
+static void handle_signal_strength_change(DBusMessage *msg)
+{
+	uint8_t signals_bar, rssi_in_dbm;
+
+	if (!dbus_message_get_args(msg, NULL,
+					DBUS_TYPE_BYTE, &signals_bar,
+					DBUS_TYPE_BYTE, &rssi_in_dbm,
+					DBUS_TYPE_INVALID)) {
+		error("Unexpected parameters in signal_strength_change");
+		return;
+	}
+
+	update_signal_strength(signals_bar);
+}
+
+static gboolean iter_get_basic_args(DBusMessageIter *iter,
+					int first_arg_type, ...)
+{
+	int type;
+	va_list ap;
+
+	va_start(ap, first_arg_type);
+
+	for (type = first_arg_type; type != DBUS_TYPE_INVALID;
+			type = va_arg(ap, int)) {
+		void *value = va_arg(ap, void *);
+		int real_type = dbus_message_iter_get_arg_type(iter);
+
+		if (real_type != type) {
+			error("iter_get_basic_args: expected %c but got %c",
+					(char) type, (char) real_type);
+			break;
+		}
+
+		dbus_message_iter_get_basic(iter, value);
+		dbus_message_iter_next(iter);
+	}
+
+	va_end(ap);
+
+	return type == DBUS_TYPE_INVALID ? TRUE : FALSE;
+}
+
+static void hal_battery_level_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	dbus_int32_t level;
+	int *value = user_data;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("hald replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_get_args(reply, NULL,
+				DBUS_TYPE_INT32, &level,
+				DBUS_TYPE_INVALID);
+
+	*value = (int) level;
+
+	if (value == &battchg_last)
+		debug("telephony-maemo: battery.charge_level.last_full is %d",
+				*value);
+	else if (value == &battchg_design)
+		debug("telephony-maemo: battery.charge_level.design is %d",
+				*value);
+	else
+		debug("telephony-maemo: battery.charge_level.current is %d",
+				*value);
+
+	if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) {
+		int new, max;
+
+		if (battchg_last > 0)
+			max = battchg_last;
+		else
+			max = battchg_design;
+
+		new = battchg_cur * 5 / max;
+
+		telephony_update_indicator(maemo_indicators, "battchg", new);
+	}
+done:
+	dbus_message_unref(reply);
+}
+
+static void hal_get_integer(const char *path, const char *key, void *user_data)
+{
+	send_method_call("org.freedesktop.Hal", path,
+				"org.freedesktop.Hal.Device",
+				"GetPropertyInteger",
+				hal_battery_level_reply, user_data,
+				DBUS_TYPE_STRING, &key,
+				DBUS_TYPE_INVALID);
+}
+
+static void handle_hal_property_modified(DBusMessage *msg)
+{
+	DBusMessageIter iter, array;
+	dbus_int32_t num_changes;
+	const char *path;
+
+	path = dbus_message_get_path(msg);
+
+	dbus_message_iter_init(msg, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
+		error("Unexpected signature in hal PropertyModified signal");
+		return;
+	}
+
+	dbus_message_iter_get_basic(&iter, &num_changes);
+	dbus_message_iter_next(&iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in hal PropertyModified signal");
+		return;
+	}
+
+	dbus_message_iter_recurse(&iter, &array);
+
+	while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
+		DBusMessageIter prop;
+		const char *name;
+		dbus_bool_t added, removed;
+
+		dbus_message_iter_recurse(&array, &prop);
+
+		if (!iter_get_basic_args(&prop,
+					DBUS_TYPE_STRING, &name,
+					DBUS_TYPE_BOOLEAN, &added,
+					DBUS_TYPE_BOOLEAN, &removed,
+					DBUS_TYPE_INVALID)) {
+			error("Invalid hal PropertyModified parameters");
+			break;
+		}
+
+		if (g_str_equal(name, "battery.charge_level.last_full"))
+			hal_get_integer(path, name, &battchg_last);
+		else if (g_str_equal(name, "battery.charge_level.current"))
+			hal_get_integer(path, name, &battchg_cur);
+		else if (g_str_equal(name, "battery.charge_level.design"))
+			hal_get_integer(path, name, &battchg_design);
+
+		dbus_message_iter_next(&array);
+	}
+}
+
+static DBusHandlerResult signal_filter(DBusConnection *conn,
+						DBusMessage *msg, void *data)
+{
+	const char *path = dbus_message_get_path(msg);
+
+	if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming"))
+		handle_incoming_call(msg);
+	else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created"))
+		handle_outgoing_call(msg);
+	else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE,
+							"CreateRequested"))
+		handle_create_requested(msg);
+	else if (dbus_message_is_signal(msg, CSD_CALL_INSTANCE, "CallStatus"))
+		handle_call_status(msg, path);
+	else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Joined"))
+		handle_conference(msg, TRUE);
+	else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Left"))
+		handle_conference(msg, FALSE);
+	else if (dbus_message_is_signal(msg, NETWORK_INTERFACE,
+					"registration_status_change"))
+		handle_registration_status_change(msg);
+	else if (dbus_message_is_signal(msg, NETWORK_INTERFACE,
+					"signal_strength_change"))
+		handle_signal_strength_change(msg);
+	else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device",
+					"PropertyModified"))
+		handle_hal_property_modified(msg);
+
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static void csd_call_free(struct csd_call *call)
+{
+	if (!call)
+		return;
+
+	g_free(call->object_path);
+	g_free(call->number);
+
+	g_free(call);
+}
+
+static void parse_call_list(DBusMessageIter *iter)
+{
+	do {
+		DBusMessageIter call_iter;
+		struct csd_call *call;
+		const char *object_path, *number;
+		dbus_uint32_t status;
+		dbus_bool_t originating, terminating, emerg, on_hold, conf;
+
+		if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) {
+			error("Unexpected signature in GetCallInfoAll reply");
+			break;
+		}
+
+		dbus_message_iter_recurse(iter, &call_iter);
+
+		if (!iter_get_basic_args(&call_iter,
+					DBUS_TYPE_OBJECT_PATH, &object_path,
+					DBUS_TYPE_UINT32, &status,
+					DBUS_TYPE_BOOLEAN, &originating,
+					DBUS_TYPE_BOOLEAN, &terminating,
+					DBUS_TYPE_BOOLEAN, &emerg,
+					DBUS_TYPE_BOOLEAN, &on_hold,
+					DBUS_TYPE_BOOLEAN, &conf,
+					DBUS_TYPE_STRING, &number,
+					DBUS_TYPE_INVALID)) {
+			error("Parsing call D-Bus parameters failed");
+			break;
+		}
+
+		call = find_call(object_path);
+		if (!call) {
+			call = g_new0(struct csd_call, 1);
+			call->object_path = g_strdup(object_path);
+			call->status = (int) status;
+			calls = g_slist_append(calls, call);
+			debug("telephony-maemo: new csd call instance at %s",
+								object_path);
+		}
+
+		if (call->status == CSD_CALL_STATUS_IDLE)
+			continue;
+
+		/* CSD gives incorrect call_hold property sometimes */
+		if ((call->status != CSD_CALL_STATUS_HOLD && on_hold) ||
+				(call->status == CSD_CALL_STATUS_HOLD &&
+								!on_hold)) {
+			error("Conflicting call status and on_hold property!");
+			on_hold = call->status == CSD_CALL_STATUS_HOLD;
+		}
+
+		call->originating = originating;
+		call->on_hold = on_hold;
+		call->conference = conf;
+		g_free(call->number);
+		call->number = g_strdup(number);
+
+	} while (dbus_message_iter_next(iter));
+}
+
+static void signal_strength_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	uint8_t signals_bar, rssi_in_dbm;
+	dbus_int32_t net_err;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("Unable to get signal strength: %s, %s",
+			err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_error_init(&err);
+	if (!dbus_message_get_args(reply, NULL,
+					DBUS_TYPE_BYTE, &signals_bar,
+					DBUS_TYPE_BYTE, &rssi_in_dbm,
+					DBUS_TYPE_INT32, &net_err,
+					DBUS_TYPE_INVALID)) {
+		error("Unable to parse signal_strength reply: %s, %s",
+							err.name, err.message);
+		dbus_error_free(&err);
+		return;
+	}
+
+	if (net_err != 0) {
+		error("get_signal_strength failed with code %d", net_err);
+		return;
+	}
+
+	update_signal_strength(signals_bar);
+
+done:
+	dbus_message_unref(reply);
+}
+
+static int get_signal_strength(void)
+{
+	return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
+				NETWORK_INTERFACE, "get_signal_strength",
+				signal_strength_reply, NULL,
+				DBUS_TYPE_INVALID);
+}
+
+static void registration_status_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	uint8_t status;
+	dbus_uint16_t lac, network_type, supported_services;
+	dbus_uint32_t cell_id, operator_code, country_code;
+	dbus_int32_t net_err;
+	uint32_t features = AG_FEATURE_EC_ANDOR_NR |
+				AG_FEATURE_INBAND_RINGTONE |
+				AG_FEATURE_REJECT_A_CALL |
+				AG_FEATURE_ENHANCED_CALL_STATUS |
+				AG_FEATURE_ENHANCED_CALL_CONTROL |
+				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES |
+				AG_FEATURE_THREE_WAY_CALLING;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("Unable to get registration status: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_error_init(&err);
+	if (!dbus_message_get_args(reply, NULL,
+					DBUS_TYPE_BYTE, &status,
+					DBUS_TYPE_UINT16, &lac,
+					DBUS_TYPE_UINT32, &cell_id,
+					DBUS_TYPE_UINT32, &operator_code,
+					DBUS_TYPE_UINT32, &country_code,
+					DBUS_TYPE_BYTE, &network_type,
+					DBUS_TYPE_BYTE, &supported_services,
+					DBUS_TYPE_INT32, &net_err,
+					DBUS_TYPE_INVALID)) {
+		error("Unable to parse registration_status_change reply:"
+					" %s, %s", err.name, err.message);
+		dbus_error_free(&err);
+		return;
+	}
+
+	if (net_err != 0) {
+		error("get_registration_status failed with code %d", net_err);
+		return;
+	}
+
+	update_registration_status(status, lac, cell_id, operator_code,
+					country_code, network_type,
+					supported_services);
+
+	telephony_ready_ind(features, maemo_indicators, response_and_hold,
+				chld_str);
+
+	get_signal_strength();
+
+done:
+	dbus_message_unref(reply);
+}
+
+static int get_registration_status(void)
+{
+	return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
+				NETWORK_INTERFACE, "get_registration_status",
+				registration_status_reply, NULL,
+				DBUS_TYPE_INVALID);
+}
+
+static void call_info_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	DBusMessageIter iter, sub;;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("csd replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_iter_init(reply, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in GetCallInfoAll return");
+		goto done;
+	}
+
+	dbus_message_iter_recurse(&iter, &sub);
+
+	parse_call_list(&sub);
+
+	get_registration_status();
+
+done:
+	dbus_message_unref(reply);
+}
+
+static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	DBusMessageIter iter, sub;
+	const char *path;
+	char match_string[256];
+	int type;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("hald replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_iter_init(reply, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in GetCallInfoAll return");
+		goto done;
+	}
+
+	dbus_message_iter_recurse(&iter, &sub);
+
+	type = dbus_message_iter_get_arg_type(&sub);
+
+	if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) {
+		error("No hal device with battery capability found");
+		goto done;
+	}
+
+	dbus_message_iter_get_basic(&sub, &path);
+
+	debug("telephony-maemo: found battery device at %s", path);
+
+	snprintf(match_string, sizeof(match_string),
+			"type='signal',"
+			"path='%s',"
+			"interface='org.freedesktop.Hal.Device',"
+			"member='PropertyModified'", path);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	hal_get_integer(path, "battery.charge_level.last_full", &battchg_last);
+	hal_get_integer(path, "battery.charge_level.current", &battchg_cur);
+	hal_get_integer(path, "battery.charge_level.design", &battchg_design);
+
+done:
+	dbus_message_unref(reply);
+}
+
+static void phonebook_read_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError derr;
+	DBusMessage *reply;
+	const char *name, *number;
+	char **number_type = user_data;
+	dbus_int32_t current_location, err;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&derr);
+	if (dbus_set_error_from_message(&derr, reply)) {
+		error("SIM.Phonebook replied with an error: %s, %s",
+				derr.name, derr.message);
+		dbus_error_free(&derr);
+		goto done;
+	}
+
+	dbus_error_init(&derr);
+	dbus_message_get_args(reply, NULL,
+				DBUS_TYPE_STRING, &name,
+				DBUS_TYPE_STRING, &number,
+				DBUS_TYPE_INT32, &current_location,
+				DBUS_TYPE_INT32, &err,
+				DBUS_TYPE_INVALID);
+
+	if (dbus_error_is_set(&derr)) {
+		error("Unable to parse SIM.Phonebook.read arguments: %s, %s",
+				derr.name, derr.message);
+		dbus_error_free(&derr);
+		goto done;
+	}
+
+	if (err != 0) {
+		error("SIM.Phonebook.read failed with error %d", err);
+		if (number_type == &vmbx)
+			vmbx = g_strdup(getenv("VMBX_NUMBER"));
+		goto done;
+	}
+
+	if (number_type == &msisdn) {
+		g_free(msisdn);
+		msisdn = g_strdup(number);
+		debug("Got MSISDN %s (%s)", number, name);
+	} else {
+		g_free(vmbx);
+		vmbx = g_strdup(number);
+		debug("Got voice mailbox number %s (%s)", number, name);
+	}
+
+done:
+	dbus_message_unref(reply);
+}
+
+static gboolean csd_init(gpointer user_data)
+{
+	char match_string[128];
+	const char *battery_cap = "battery";
+	dbus_uint32_t location;
+	uint8_t pb_type, location_type;
+	int ret;
+
+	if (!dbus_connection_add_filter(connection, signal_filter,
+						NULL, NULL)) {
+		error("Can't add signal filter");
+		return FALSE;
+	}
+
+	snprintf(match_string, sizeof(match_string),
+			"type=signal,interface=%s", CSD_CALL_INTERFACE);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	snprintf(match_string, sizeof(match_string),
+			"type=signal,interface=%s", CSD_CALL_INSTANCE);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	snprintf(match_string, sizeof(match_string),
+			"type=signal,interface=%s", CSD_CALL_CONFERENCE);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	snprintf(match_string, sizeof(match_string),
+			"type=signal,interface=%s", NETWORK_INTERFACE);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+				CSD_CALL_INTERFACE, "GetCallInfoAll",
+				call_info_reply, NULL, DBUS_TYPE_INVALID);
+	if (ret < 0) {
+		error("Unable to sent GetCallInfoAll method call");
+		return FALSE;
+	}
+
+	ret = send_method_call("org.freedesktop.Hal",
+				"/org/freedesktop/Hal/Manager",
+				"org.freedesktop.Hal.Manager",
+				"FindDeviceByCapability",
+				hal_find_device_reply, NULL,
+				DBUS_TYPE_STRING, &battery_cap,
+				DBUS_TYPE_INVALID);
+	if (ret < 0) {
+		error("Unable to send HAL method call");
+		return FALSE;
+	}
+
+	pb_type = SIM_PHONEBOOK_TYPE_MSISDN;
+	location = PHONEBOOK_INDEX_FIRST_ENTRY;
+	location_type = SIM_PHONEBOOK_LOCATION_NEXT;
+
+	ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH,
+				SIM_PHONEBOOK_INTERFACE, "read",
+				phonebook_read_reply, &msisdn,
+				DBUS_TYPE_BYTE, &pb_type,
+				DBUS_TYPE_INT32, &location,
+				DBUS_TYPE_BYTE, &location_type,
+				DBUS_TYPE_INVALID);
+	if (ret < 0) {
+		error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()");
+		return FALSE;
+	}
+
+	pb_type = SIM_PHONEBOOK_TYPE_VMBX;
+	location = PHONEBOOK_INDEX_FIRST_ENTRY;
+	location_type = SIM_PHONEBOOK_LOCATION_NEXT;
+
+	ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH,
+				SIM_PHONEBOOK_INTERFACE, "read",
+				phonebook_read_reply, &vmbx,
+				DBUS_TYPE_BYTE, &pb_type,
+				DBUS_TYPE_INT32, &location,
+				DBUS_TYPE_BYTE, &location_type,
+				DBUS_TYPE_INVALID);
+	if (ret < 0) {
+		error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()");
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
+static void csd_ready(DBusConnection *conn, void *user_data)
+{
+	g_dbus_remove_watch(conn, csd_watch);
+	csd_watch = 0;
+
+	g_timeout_add_seconds(2, csd_init, NULL);
+}
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg,"org.bluez.Error.InvalidArguments",
+					"Invalid arguments in method call");
+}
+
+static uint32_t get_callflag(const char *callerid_setting)
+{
+	if (callerid_setting != NULL) {
+		if (g_str_equal(callerid_setting, "allowed"))
+			return CALL_FLAG_PRESENTATION_ALLOWED;
+		else if (g_str_equal(callerid_setting, "restricted"))
+			return CALL_FLAG_PRESENTATION_RESTRICTED;
+		else
+			return CALL_FLAG_NONE;
+	} else
+		return CALL_FLAG_NONE;
+}
+
+static void generate_flag_file(const char *filename)
+{
+	int fd;
+
+	if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS) ||
+			g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS) ||
+			g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
+		return;
+
+	fd = open(filename, O_WRONLY | O_CREAT, 0);
+	if (fd >= 0)
+		close(fd);
+}
+
+static void save_callerid_to_file(const char *callerid_setting)
+{
+	char callerid_file[FILENAME_MAX];
+
+	snprintf(callerid_file, sizeof(callerid_file), "%s%s",
+					CALLERID_BASE, callerid_setting);
+
+	if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS))
+		rename(ALLOWED_FLAG_FILE, callerid_file);
+	else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS))
+		rename(RESTRICTED_FLAG_FILE, callerid_file);
+	else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
+		rename(NONE_FLAG_FILE, callerid_file);
+	else
+		generate_flag_file(callerid_file);
+}
+
+static uint32_t callerid_from_file(void)
+{
+	if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS))
+		return CALL_FLAG_PRESENTATION_ALLOWED;
+	else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS))
+		return CALL_FLAG_PRESENTATION_RESTRICTED;
+	else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
+		return CALL_FLAG_NONE;
+	else
+		return CALL_FLAG_NONE;
+}
+
+static DBusMessage *set_callerid(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	const char *callerid_setting;
+
+	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING,
+						&callerid_setting,
+						DBUS_TYPE_INVALID) == FALSE)
+		return invalid_args(msg);
+
+	if (g_str_equal(callerid_setting, "allowed") ||
+			g_str_equal(callerid_setting, "restricted") ||
+			g_str_equal(callerid_setting, "none")) {
+		save_callerid_to_file(callerid_setting);
+		callerid = get_callflag(callerid_setting);
+		debug("telephony-maemo setting callerid flag: %s",
+							callerid_setting);
+		return dbus_message_new_method_return(msg);
+	}
+
+	error("telephony-maemo: invalid argument %s for method call"
+					" SetCallerId", callerid_setting);
+		return invalid_args(msg);
+}
+
+static GDBusMethodTable telephony_maemo_methods[] = {
+	{"SetCallerId",		"s",	"",	set_callerid,
+						G_DBUS_METHOD_FLAG_ASYNC},
+	{ }
+};
+
+int telephony_init(void)
+{
+	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+	csd_watch = g_dbus_add_service_watch(connection, CSD_CALL_BUS_NAME,
+						csd_ready, NULL, NULL, NULL);
+
+	if (dbus_bus_name_has_owner(connection, CSD_CALL_BUS_NAME, NULL))
+		csd_ready(connection, NULL);
+	else
+		info("CSD not yet available. Waiting for it...");
+
+	generate_flag_file(NONE_FLAG_FILE);
+	callerid = callerid_from_file();
+
+	if (!g_dbus_register_interface(connection, TELEPHONY_MAEMO_PATH,
+			TELEPHONY_MAEMO_INTERFACE, telephony_maemo_methods,
+			NULL, NULL, NULL, NULL)) {
+		error("telephony-maemo interface %s init failed on path %s",
+			TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
+	}
+
+	debug("telephony-maemo registering %s interface on path %s",
+			TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
+
+	return 0;
+}
+
+void telephony_exit(void)
+{
+	if (csd_watch) {
+		g_dbus_remove_watch(connection, csd_watch);
+		csd_watch = 0;
+	}
+
+	g_slist_foreach(calls, (GFunc) csd_call_free, NULL);
+	g_slist_free(calls);
+	calls = NULL;
+
+	dbus_connection_remove_filter(connection, signal_filter, NULL);
+
+	dbus_connection_unref(connection);
+	connection = NULL;
+}
diff --git a/audio/telephony-ofono.c b/audio/telephony-ofono.c
new file mode 100644
index 0000000..7c647b6
--- /dev/null
+++ b/audio/telephony-ofono.c
@@ -0,0 +1,1110 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009       Intel Corporation
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 "logging.h"
+#include "telephony.h"
+
+enum net_registration_status {
+	NETWORK_REG_STATUS_HOME = 0x00,
+	NETWORK_REG_STATUS_ROAM,
+	NETWORK_REG_STATUS_NOSERV
+};
+
+struct voice_call {
+	char *obj_path;
+	int status;
+	gboolean originating;
+	char *number;
+};
+
+static DBusConnection *connection = NULL;
+static char *modem_obj_path = NULL;
+static char *last_dialed_number = NULL;
+static GSList *calls = NULL;
+
+#define OFONO_BUS_NAME "org.ofono"
+#define OFONO_PATH "/"
+#define OFONO_MANAGER_INTERFACE "org.ofono.Manager"
+#define OFONO_NETWORKREG_INTERFACE "org.ofono.NetworkRegistration"
+#define OFONO_VCMANAGER_INTERFACE "org.ofono.VoiceCallManager"
+#define OFONO_VC_INTERFACE "org.ofono.VoiceCall"
+
+/* HAL battery namespace key values */
+static int battchg_cur = -1;    /* "battery.charge_level.current" */
+static int battchg_last = -1;   /* "battery.charge_level.last_full" */
+static int battchg_design = -1; /* "battery.charge_level.design" */
+
+static struct {
+	uint8_t status;
+	uint32_t signals_bar;
+	char *operator_name;
+} net = {
+	.status = NETWORK_REG_STATUS_NOSERV,
+	.signals_bar = 0,
+	.operator_name = NULL,
+};
+
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+static char *subscriber_number = NULL;
+
+static gboolean events_enabled = FALSE;
+
+/* Response and hold state
+ * -1 = none
+ *  0 = incoming call is put on hold in the AG
+ *  1 = held incoming call is accepted in the AG
+ *  2 = held incoming call is rejected in the AG
+ */
+static int response_and_hold = -1;
+
+static struct indicator ofono_indicators[] =
+{
+	{ "battchg",	"0-5",	5,	TRUE },
+	{ "signal",	"0-5",	5,	TRUE },
+	{ "service",	"0,1",	1,	TRUE },
+	{ "call",	"0,1",	0,	TRUE },
+	{ "callsetup",	"0-3",	0,	TRUE },
+	{ "callheld",	"0-2",	0,	FALSE },
+	{ "roam",	"0,1",	0,	TRUE },
+	{ NULL }
+};
+
+static struct voice_call *find_vc(const char *path)
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct voice_call *vc = l->data;
+
+		if (g_str_equal(vc->obj_path, path))
+			return vc;
+	}
+
+	return NULL;
+}
+
+static struct voice_call *find_vc_with_status(int status)
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct voice_call *vc = l->data;
+
+		if (vc->status == status)
+			return vc;
+	}
+
+	return NULL;
+}
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments",
+					"Invalid arguments in method call");
+}
+
+void telephony_device_connected(void *telephony_device)
+{
+	debug("telephony-ofono: device %p connected", telephony_device);
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+	debug("telephony-ofono: device %p disconnected", telephony_device);
+	events_enabled = FALSE;
+}
+
+void telephony_event_reporting_req(void *telephony_device, int ind)
+{
+	events_enabled = ind == 1 ? TRUE : FALSE;
+
+	telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_response_and_hold_req(void *telephony_device, int rh)
+{
+	response_and_hold = rh;
+
+	telephony_response_and_hold_ind(response_and_hold);
+
+	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+	debug("telephony-ofono: last dialed number request");
+
+	if (last_dialed_number)
+		telephony_dial_number_req(telephony_device, last_dialed_number);
+	else
+		telephony_last_dialed_number_rsp(telephony_device,
+				CME_ERROR_NOT_ALLOWED);
+}
+
+static int send_method_call(const char *dest, const char *path,
+                                const char *interface, const char *method,
+                                DBusPendingCallNotifyFunction cb,
+                                void *user_data, int type, ...)
+{
+	DBusMessage *msg;
+	DBusPendingCall *call;
+	va_list args;
+
+	msg = dbus_message_new_method_call(dest, path, interface, method);
+	if (!msg) {
+		error("Unable to allocate new D-Bus %s message", method);
+		return -ENOMEM;
+	}
+
+	va_start(args, type);
+
+	if (!dbus_message_append_args_valist(msg, type, args)) {
+		dbus_message_unref(msg);
+		va_end(args);
+		return -EIO;
+	}
+
+	va_end(args);
+
+	if (!cb) {
+		g_dbus_send_message(connection, msg);
+		return 0;
+	}
+
+	if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) {
+		error("Sending %s failed", method);
+		dbus_message_unref(msg);
+		return -EIO;
+	}
+
+	dbus_pending_call_set_notify(call, cb, user_data, NULL);
+	dbus_pending_call_unref(call);
+	dbus_message_unref(msg);
+
+	return 0;
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+	struct voice_call *vc;
+	int ret;
+
+	if ((vc = find_vc_with_status(CALL_STATUS_ACTIVE))) {
+	} else if ((vc = find_vc_with_status(CALL_STATUS_DIALING))) {
+	} else if ((vc = find_vc_with_status(CALL_STATUS_ALERTING))) {
+	} else if ((vc = find_vc_with_status(CALL_STATUS_INCOMING))) {
+	}
+
+	if (!vc) {
+		error("in telephony_terminate_call_req, no active call");
+		telephony_terminate_call_rsp(telephony_device,
+					CME_ERROR_NOT_ALLOWED);
+		return;
+	}
+
+	ret = send_method_call(OFONO_BUS_NAME, vc->obj_path,
+					OFONO_VC_INTERFACE,
+					"Hangup", NULL,
+					NULL, DBUS_TYPE_INVALID);
+
+	if (ret < 0) {
+		telephony_answer_call_rsp(telephony_device,
+					CME_ERROR_AG_FAILURE);
+		return;
+	}
+
+	telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_answer_call_req(void *telephony_device)
+{
+	struct voice_call *vc = find_vc_with_status(CALL_STATUS_INCOMING);
+	int ret;
+
+	if (!vc) {
+		telephony_answer_call_rsp(telephony_device,
+					CME_ERROR_NOT_ALLOWED);
+		return;
+	}
+
+	ret = send_method_call(OFONO_BUS_NAME, vc->obj_path,
+			OFONO_VC_INTERFACE,
+			"Answer", NULL,
+			NULL, DBUS_TYPE_INVALID);
+
+	if (ret < 0) {
+		telephony_answer_call_rsp(telephony_device,
+					CME_ERROR_AG_FAILURE);
+		return;
+	}
+
+	telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_dial_number_req(void *telephony_device, const char *number)
+{
+	char *clir = "default";
+	int ret;
+
+	debug("telephony-ofono: dial request to %s", number);
+
+	if (!strncmp(number, "*31#", 4)) {
+		number += 4;
+		clir = g_strdup("enabled");
+	} else if (!strncmp(number, "#31#", 4)) {
+		number += 4;
+		clir = g_strdup("disabled");
+	}
+
+	ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
+			OFONO_VCMANAGER_INTERFACE,
+                        "Dial", NULL, NULL,
+			DBUS_TYPE_STRING, &number,
+			DBUS_TYPE_STRING, &clir,
+			DBUS_TYPE_INVALID);
+
+	if (ret < 0)
+		telephony_dial_number_rsp(telephony_device,
+			CME_ERROR_AG_FAILURE);
+	else
+		telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+	char *tone_string;
+	int ret;
+
+	debug("telephony-ofono: transmit dtmf: %c", tone);
+
+	tone_string = g_strdup_printf("%c", tone);
+	ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
+			OFONO_VCMANAGER_INTERFACE,
+			"SendTones", NULL, NULL,
+			DBUS_TYPE_STRING, &tone_string,
+			DBUS_TYPE_INVALID);
+	g_free(tone_string);
+
+	if (ret < 0)
+		telephony_transmit_dtmf_rsp(telephony_device,
+			CME_ERROR_AG_FAILURE);
+	else
+		telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+	debug("telephony-ofono: subscriber number request");
+
+	if (subscriber_number)
+		telephony_subscriber_number_ind(subscriber_number,
+						NUMBER_TYPE_TELEPHONY,
+						SUBSCRIBER_SERVICE_VOICE);
+	telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_list_current_calls_req(void *telephony_device)
+{
+	GSList *l;
+	int i;
+
+	debug("telephony-ofono: list current calls request");
+
+	for (l = calls, i = 1; l != NULL; l = l->next, i++) {
+		struct voice_call *vc = l->data;
+		int direction;
+
+		direction = vc->originating ?
+				CALL_DIR_OUTGOING : CALL_DIR_INCOMING;
+
+		telephony_list_current_call_ind(i, direction, vc->status,
+					CALL_MODE_VOICE, CALL_MULTIPARTY_NO,
+					vc->number, NUMBER_TYPE_TELEPHONY);
+	}
+	telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_operator_selection_req(void *telephony_device)
+{
+	debug("telephony-ofono: operator selection request");
+
+	telephony_operator_selection_ind(OPERATOR_MODE_AUTO,
+				net.operator_name ? net.operator_name : "");
+	telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_call_hold_req(void *telephony_device, const char *cmd)
+{
+	debug("telephony-ofono: got call hold request %s", cmd);
+	telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
+{
+	debug("telephony-ofono: got %s NR and EC request",
+			enable ? "enable" : "disable");
+
+	telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+	debug("telephony-ofono: got key press request for %s", keys);
+	telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static gboolean iter_get_basic_args(DBusMessageIter *iter,
+					int first_arg_type, ...)
+{
+	int type;
+	va_list ap;
+
+	va_start(ap, first_arg_type);
+
+	for (type = first_arg_type; type != DBUS_TYPE_INVALID;
+		type = va_arg(ap, int)) {
+		void *value = va_arg(ap, void *);
+		int real_type = dbus_message_iter_get_arg_type(iter);
+
+		if (real_type != type) {
+			error("iter_get_basic_args: expected %c but got %c",
+				(char) type, (char) real_type);
+			break;
+		}
+
+		dbus_message_iter_get_basic(iter, value);
+		dbus_message_iter_next(iter);
+	}
+
+	va_end(ap);
+
+	return type == DBUS_TYPE_INVALID ? TRUE : FALSE;
+}
+
+static void handle_registration_property(const char *property, DBusMessageIter sub)
+{
+	char *status, *operator;
+	unsigned int signals_bar;
+
+	if (g_str_equal(property, "Status")) {
+		dbus_message_iter_get_basic(&sub, &status);
+		debug("Status is %s", status);
+		if (g_str_equal(status, "registered")) {
+			net.status = NETWORK_REG_STATUS_HOME;
+			telephony_update_indicator(ofono_indicators,
+						"roam", EV_ROAM_INACTIVE);
+			telephony_update_indicator(ofono_indicators,
+						"service", EV_SERVICE_PRESENT);
+		} else if (g_str_equal(status, "roaming")) {
+			net.status = NETWORK_REG_STATUS_ROAM;
+			telephony_update_indicator(ofono_indicators,
+						"roam", EV_ROAM_ACTIVE);
+			telephony_update_indicator(ofono_indicators,
+						"service", EV_SERVICE_PRESENT);
+		} else {
+			net.status = NETWORK_REG_STATUS_NOSERV;
+			telephony_update_indicator(ofono_indicators,
+						"roam", EV_ROAM_INACTIVE);
+			telephony_update_indicator(ofono_indicators,
+						"service", EV_SERVICE_NONE);
+		}
+	} else if (g_str_equal(property, "Operator")) {
+		dbus_message_iter_get_basic(&sub, &operator);
+		debug("Operator is %s", operator);
+		g_free(net.operator_name);
+		net.operator_name = g_strdup(operator);
+	} else if (g_str_equal(property, "SignalStrength")) {
+		dbus_message_iter_get_basic(&sub, &signals_bar);
+		debug("SignalStrength is %d", signals_bar);
+		net.signals_bar = signals_bar;
+		telephony_update_indicator(ofono_indicators, "signal",
+						(signals_bar + 20) / 21);
+	}
+}
+
+static void get_registration_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	DBusMessageIter iter, iter_entry;
+	uint32_t features = AG_FEATURE_EC_ANDOR_NR |
+				AG_FEATURE_REJECT_A_CALL |
+				AG_FEATURE_ENHANCED_CALL_STATUS |
+				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("ofono replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_iter_init(reply, &iter);
+
+	/* ARRAY -> ENTRY -> VARIANT*/
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in GetProperties return");
+		goto done;
+	}
+
+	dbus_message_iter_recurse(&iter, &iter_entry);
+
+	if (dbus_message_iter_get_arg_type(&iter_entry)
+					!= DBUS_TYPE_DICT_ENTRY) {
+		error("Unexpected signature in GetProperties return");
+		goto done;
+	}
+
+	while (dbus_message_iter_get_arg_type(&iter_entry)
+					!= DBUS_TYPE_INVALID) {
+		DBusMessageIter iter_property, sub;
+		char *property;
+
+		dbus_message_iter_recurse(&iter_entry, &iter_property);
+		if (dbus_message_iter_get_arg_type(&iter_property)
+					!= DBUS_TYPE_STRING) {
+			error("Unexpected signature in GetProperties return");
+			goto done;
+		}
+
+		dbus_message_iter_get_basic(&iter_property, &property);
+
+		dbus_message_iter_next(&iter_property);
+		dbus_message_iter_recurse(&iter_property, &sub);
+
+		handle_registration_property(property, sub);
+
+                dbus_message_iter_next(&iter_entry);
+        }
+
+	telephony_ready_ind(features, ofono_indicators,
+				response_and_hold, chld_str);
+
+done:
+	dbus_message_unref(reply);
+}
+
+static int get_registration_and_signal_status()
+{
+	return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+			OFONO_NETWORKREG_INTERFACE,
+			"GetProperties", get_registration_reply,
+			NULL, DBUS_TYPE_INVALID);
+}
+
+static void list_modem_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	DBusMessageIter iter, iter_entry, iter_property, iter_arrary, sub;
+	char *property, *modem_obj_path_local;
+	int ret;
+
+	debug("list_modem_reply is called\n");
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("ofono replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_iter_init(reply, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in ListModems return");
+		goto done;
+	}
+
+	dbus_message_iter_recurse(&iter, &iter_entry);
+
+	if (dbus_message_iter_get_arg_type(&iter_entry)
+					!= DBUS_TYPE_DICT_ENTRY) {
+		error("Unexpected signature in ListModems return 2, %c",
+				dbus_message_iter_get_arg_type(&iter_entry));
+		goto done;
+	}
+
+	dbus_message_iter_recurse(&iter_entry, &iter_property);
+
+	dbus_message_iter_get_basic(&iter_property, &property);
+
+	dbus_message_iter_next(&iter_property);
+	dbus_message_iter_recurse(&iter_property, &iter_arrary);
+	dbus_message_iter_recurse(&iter_arrary, &sub);
+	while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+
+		dbus_message_iter_get_basic(&sub, &modem_obj_path_local);
+		modem_obj_path = g_strdup(modem_obj_path_local);
+		debug("modem_obj_path is %p, %s\n", modem_obj_path,
+						modem_obj_path);
+		dbus_message_iter_next(&sub);
+	}
+
+	ret = get_registration_and_signal_status();
+	if (ret < 0)
+		error("get_registration_and_signal_status() failed(%d)", ret);
+done:
+	dbus_message_unref(reply);
+}
+
+static void handle_networkregistration_property_changed(DBusMessage *msg,
+					const char *call_path)
+{
+	DBusMessageIter iter, sub;
+	const char *property;
+
+	dbus_message_iter_init(msg, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+		error("Unexpected signature in networkregistration"
+					" PropertyChanged signal");
+		return;
+	}
+	dbus_message_iter_get_basic(&iter, &property);
+	debug("in handle_networkregistration_property_changed(),"
+					" the property is %s", property);
+
+	dbus_message_iter_next(&iter);
+	dbus_message_iter_recurse(&iter, &sub);
+
+	handle_registration_property(property, sub);
+}
+
+static void vc_getproperties_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusMessage *reply;
+	DBusError err;
+	DBusMessageIter iter, iter_entry;
+	const char *path = user_data;
+	struct voice_call *vc;
+
+	debug("in vc_getproperties_reply");
+
+	reply = dbus_pending_call_steal_reply(call);
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("ofono replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+        }
+
+	vc = find_vc(path);
+	if (!vc) {
+		error("in vc_getproperties_reply, vc is NULL");
+		goto done;
+	}
+
+	dbus_message_iter_init(reply, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in vc_getproperties_reply()");
+		goto done;
+        }
+
+	dbus_message_iter_recurse(&iter, &iter_entry);
+
+	if (dbus_message_iter_get_arg_type(&iter_entry)
+				!= DBUS_TYPE_DICT_ENTRY) {
+		error("Unexpected signature in vc_getproperties_reply()");
+		goto done;
+        }
+
+	while (dbus_message_iter_get_arg_type(&iter_entry)
+				!= DBUS_TYPE_INVALID) {
+		DBusMessageIter iter_property, sub;
+		char *property, *cli, *state;
+
+		dbus_message_iter_recurse(&iter_entry, &iter_property);
+		if (dbus_message_iter_get_arg_type(&iter_property)
+					!= DBUS_TYPE_STRING) {
+			error("Unexpected signature in"
+					" vc_getproperties_reply()");
+			goto done;
+                }
+
+		dbus_message_iter_get_basic(&iter_property, &property);
+
+		dbus_message_iter_next(&iter_property);
+		dbus_message_iter_recurse(&iter_property, &sub);
+		if (g_str_equal(property, "LineIdentification")) {
+			dbus_message_iter_get_basic(&sub, &cli);
+			debug("in vc_getproperties_reply(), cli is %s", cli);
+			vc->number = g_strdup(cli);
+		} else if (g_str_equal(property, "State")) {
+			dbus_message_iter_get_basic(&sub, &state);
+			debug("in vc_getproperties_reply(),"
+					" state is %s", state);
+			if (g_str_equal(state, "incoming"))
+				vc->status = CALL_STATUS_INCOMING;
+			else if (g_str_equal(state, "dialing"))
+				vc->status = CALL_STATUS_DIALING;
+			else if (g_str_equal(state, "alerting"))
+				vc->status = CALL_STATUS_ALERTING;
+			else if (g_str_equal(state, "waiting"))
+				vc->status = CALL_STATUS_WAITING;
+		}
+
+		dbus_message_iter_next(&iter_entry);
+	}
+
+	switch (vc->status) {
+	case CALL_STATUS_INCOMING:
+		printf("in CALL_STATUS_INCOMING: case\n");
+		vc->originating = FALSE;
+		telephony_update_indicator(ofono_indicators, "callsetup",
+					EV_CALLSETUP_INCOMING);
+		telephony_incoming_call_ind(vc->number, NUMBER_TYPE_TELEPHONY);
+		break;
+	case CALL_STATUS_DIALING:
+		printf("in CALL_STATUS_DIALING: case\n");
+		vc->originating = TRUE;
+		g_free(last_dialed_number);
+		last_dialed_number = g_strdup(vc->number);
+		telephony_update_indicator(ofono_indicators, "callsetup",
+					EV_CALLSETUP_OUTGOING);
+		break;
+	case CALL_STATUS_ALERTING:
+		printf("in CALL_STATUS_ALERTING: case\n");
+		vc->originating = TRUE;
+		g_free(last_dialed_number);
+		last_dialed_number = g_strdup(vc->number);
+		telephony_update_indicator(ofono_indicators, "callsetup",
+					EV_CALLSETUP_ALERTING);
+		break;
+	case CALL_STATUS_WAITING:
+		debug("in CALL_STATUS_WAITING: case");
+		vc->originating = FALSE;
+		telephony_update_indicator(ofono_indicators, "callsetup",
+					EV_CALLSETUP_INCOMING);
+		telephony_call_waiting_ind(vc->number, NUMBER_TYPE_TELEPHONY);
+		break;
+	}
+done:
+	dbus_message_unref(reply);
+}
+
+static void handle_vcmanager_property_changed(DBusMessage *msg,
+						const char *obj_path)
+{
+	DBusMessageIter iter, sub, array;
+	const char *property, *vc_obj_path = NULL;
+	struct voice_call *vc = NULL, *vc_new = NULL;
+
+	debug("in handle_vcmanager_property_changed");
+
+	dbus_message_iter_init(msg, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+		error("Unexpected signature in vcmanager"
+					" PropertyChanged signal");
+		return;
+	}
+
+	dbus_message_iter_get_basic(&iter, &property);
+	debug("in handle_vcmanager_property_changed(),"
+				" the property is %s", property);
+
+	dbus_message_iter_next(&iter);
+	dbus_message_iter_recurse(&iter, &sub);
+	if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in vcmanager"
+					" PropertyChanged signal");
+		return;
+	}
+	dbus_message_iter_recurse(&sub, &array);
+	while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
+		dbus_message_iter_get_basic(&array, &vc_obj_path);
+		vc = find_vc(vc_obj_path);
+		if (vc) {
+			debug("in handle_vcmanager_property_changed,"
+					" found an existing vc");
+		} else {
+			vc_new = g_new0(struct voice_call, 1);
+			vc_new->obj_path = g_strdup(vc_obj_path);
+			calls = g_slist_append(calls, vc_new);
+		}
+		dbus_message_iter_next(&array);
+	}
+
+	if (!vc_new)
+		return;
+
+	send_method_call(OFONO_BUS_NAME, vc_new->obj_path,
+				OFONO_VC_INTERFACE,
+				"GetProperties", vc_getproperties_reply,
+				vc_new->obj_path, DBUS_TYPE_INVALID);
+}
+
+static void vc_free(struct voice_call *vc)
+{
+	if (!vc)
+		return;
+
+	g_free(vc->obj_path);
+	g_free(vc->number);
+	g_free(vc);
+}
+
+static void handle_vc_property_changed(DBusMessage *msg, const char *obj_path)
+{
+	DBusMessageIter iter, sub;
+	const char *property, *state;
+	struct voice_call *vc = NULL;
+
+	debug("in handle_vc_property_changed, obj_path is %s", obj_path);
+
+	vc = find_vc(obj_path);
+
+	if (!vc)
+		return;
+
+	dbus_message_iter_init(msg, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+		error("Unexpected signature in vc PropertyChanged signal");
+		return;
+	}
+
+	dbus_message_iter_get_basic(&iter, &property);
+	debug("in handle_vc_property_changed(), the property is %s", property);
+
+	dbus_message_iter_next(&iter);
+	dbus_message_iter_recurse(&iter, &sub);
+	if (g_str_equal(property, "State")) {
+		dbus_message_iter_get_basic(&sub, &state);
+		debug("in handle_vc_property_changed(), State is %s", state);
+		if (g_str_equal(state, "disconnected")) {
+			printf("in disconnected case\n");
+			if (vc->status == CALL_STATUS_ACTIVE)
+				telephony_update_indicator(ofono_indicators,
+						"call", EV_CALL_INACTIVE);
+			else
+				telephony_update_indicator(ofono_indicators,
+					"callsetup", EV_CALLSETUP_INACTIVE);
+			if (vc->status == CALL_STATUS_INCOMING)
+				telephony_calling_stopped_ind();
+			calls = g_slist_remove(calls, vc);
+			vc_free(vc);
+		} else if (g_str_equal(state, "active")) {
+			telephony_update_indicator(ofono_indicators,
+							"call", EV_CALL_ACTIVE);
+			telephony_update_indicator(ofono_indicators,
+							"callsetup",
+							EV_CALLSETUP_INACTIVE);
+			if (vc->status == CALL_STATUS_INCOMING) {
+				telephony_calling_stopped_ind();
+			}
+			vc->status = CALL_STATUS_ACTIVE;
+			debug("vc status is CALL_STATUS_ACTIVE");
+		} else if (g_str_equal(state, "alerting")) {
+			telephony_update_indicator(ofono_indicators,
+					 "callsetup", EV_CALLSETUP_ALERTING);
+			vc->status = CALL_STATUS_ALERTING;
+			debug("vc status is CALL_STATUS_ALERTING");
+		} else if (g_str_equal(state, "incoming")) {
+			/* state change from waiting to incoming */
+			telephony_update_indicator(ofono_indicators,
+					"callsetup", EV_CALLSETUP_INCOMING);
+			telephony_incoming_call_ind(vc->number,
+						NUMBER_TYPE_TELEPHONY);
+			vc->status = CALL_STATUS_INCOMING;
+			debug("vc status is CALL_STATUS_INCOMING");
+		}
+	}
+}
+
+static void hal_battery_level_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusMessage *reply;
+	DBusError err;
+	dbus_int32_t level;
+	int *value = user_data;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("hald replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_get_args(reply, NULL,
+				DBUS_TYPE_INT32, &level,
+				DBUS_TYPE_INVALID);
+
+	*value = (int) level;
+
+	if (value == &battchg_last)
+		debug("telephony-ofono: battery.charge_level.last_full"
+					" is %d", *value);
+	else if (value == &battchg_design)
+		debug("telephony-ofono: battery.charge_level.design"
+					" is %d", *value);
+	else
+		debug("telephony-ofono: battery.charge_level.current"
+					" is %d", *value);
+
+	if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) {
+		int new, max;
+
+		if (battchg_last > 0)
+			max = battchg_last;
+		else
+			max = battchg_design;
+
+		new = battchg_cur * 5 / max;
+
+		telephony_update_indicator(ofono_indicators, "battchg", new);
+	}
+done:
+	dbus_message_unref(reply);
+}
+
+static void hal_get_integer(const char *path, const char *key, void *user_data)
+{
+	send_method_call("org.freedesktop.Hal", path,
+			"org.freedesktop.Hal.Device",
+			"GetPropertyInteger",
+			hal_battery_level_reply, user_data,
+			DBUS_TYPE_STRING, &key,
+			DBUS_TYPE_INVALID);
+}
+
+static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusMessage *reply;
+	DBusError err;
+	DBusMessageIter iter, sub;
+	int type;
+	const char *path;
+	char match_string[256];
+
+	debug("begin of hal_find_device_reply()");
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("hald replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_iter_init(reply, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in hal_find_device_reply()");
+		goto done;
+	}
+
+	dbus_message_iter_recurse(&iter, &sub);
+
+	type = dbus_message_iter_get_arg_type(&sub);
+
+	if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) {
+		error("No hal device with battery capability found");
+		goto done;
+	}
+
+	dbus_message_iter_get_basic(&sub, &path);
+
+	debug("telephony-ofono: found battery device at %s", path);
+
+	snprintf(match_string, sizeof(match_string),
+			"type='signal',"
+			"path='%s',"
+			"interface='org.freedesktop.Hal.Device',"
+			"member='PropertyModified'", path);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	hal_get_integer(path, "battery.charge_level.last_full", &battchg_last);
+	hal_get_integer(path, "battery.charge_level.current", &battchg_cur);
+	hal_get_integer(path, "battery.charge_level.design", &battchg_design);
+done:
+	dbus_message_unref(reply);
+}
+
+static void handle_hal_property_modified(DBusMessage *msg)
+{
+	const char *path;
+	DBusMessageIter iter, array;
+	dbus_int32_t num_changes;
+
+	path = dbus_message_get_path(msg);
+
+	dbus_message_iter_init(msg, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
+		error("Unexpected signature in hal PropertyModified signal");
+		return;
+	}
+
+	dbus_message_iter_get_basic(&iter, &num_changes);
+	dbus_message_iter_next(&iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in hal PropertyModified signal");
+		return;
+	}
+
+	dbus_message_iter_recurse(&iter, &array);
+
+	while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
+		DBusMessageIter prop;
+		const char *name;
+		dbus_bool_t added, removed;
+
+		dbus_message_iter_recurse(&array, &prop);
+
+		if (!iter_get_basic_args(&prop,
+					DBUS_TYPE_STRING, &name,
+					DBUS_TYPE_BOOLEAN, &added,
+					DBUS_TYPE_BOOLEAN, &removed,
+					DBUS_TYPE_INVALID)) {
+			error("Invalid hal PropertyModified parameters");
+			break;
+		}
+
+		if (g_str_equal(name, "battery.charge_level.last_full"))
+			hal_get_integer(path, name, &battchg_last);
+		else if (g_str_equal(name, "battery.charge_level.current"))
+			hal_get_integer(path, name, &battchg_cur);
+		else if (g_str_equal(name, "battery.charge_level.design"))
+			hal_get_integer(path, name, &battchg_design);
+
+		dbus_message_iter_next(&array);
+	}
+}
+
+static DBusHandlerResult signal_filter(DBusConnection *conn,
+				DBusMessage *msg, void *data)
+{
+	const char *path = dbus_message_get_path(msg);
+
+	if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	if (dbus_message_is_signal(msg, OFONO_NETWORKREG_INTERFACE,
+				"PropertyChanged"))
+		handle_networkregistration_property_changed(msg, path);
+	else if (dbus_message_is_signal(msg, OFONO_VCMANAGER_INTERFACE,
+				"PropertyChanged"))
+		handle_vcmanager_property_changed(msg, path);
+	else if (dbus_message_is_signal(msg, OFONO_VC_INTERFACE,
+				"PropertyChanged"))
+		handle_vc_property_changed(msg, path);
+	else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device",
+				"PropertyModified"))
+		handle_hal_property_modified(msg);
+
+	debug("signal_filter is called, path is %s\n", path);
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int telephony_init(void)
+{
+	const char *battery_cap = "battery";
+	char match_string[128];
+	int ret;
+
+	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+	if (!dbus_connection_add_filter(connection, signal_filter,
+					NULL, NULL)) {
+		error("telephony-ofono: Can't add signal filter");
+		return -EIO;
+	}
+
+	snprintf(match_string, sizeof(match_string), "type=signal,interface=%s",
+				OFONO_NETWORKREG_INTERFACE);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	snprintf(match_string, sizeof(match_string), "type=signal,interface=%s",
+				OFONO_VCMANAGER_INTERFACE);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	snprintf(match_string, sizeof(match_string), "type=signal,interface=%s",
+				OFONO_VC_INTERFACE);
+	dbus_bus_add_match(connection, match_string, NULL);
+
+	ret = send_method_call(OFONO_BUS_NAME, OFONO_PATH,
+				OFONO_MANAGER_INTERFACE, "GetProperties",
+				list_modem_reply, NULL, DBUS_TYPE_INVALID);
+	if (ret < 0)
+		return ret;
+
+	ret = send_method_call("org.freedesktop.Hal",
+				"/org/freedesktop/Hal/Manager",
+				"org.freedesktop.Hal.Manager",
+				"FindDeviceByCapability",
+				hal_find_device_reply, NULL,
+				DBUS_TYPE_STRING, &battery_cap,
+				DBUS_TYPE_INVALID);
+	if (ret < 0)
+		return ret;
+
+	debug("telephony_init() successfully");
+
+	return ret;
+}
+
+void telephony_exit(void)
+{
+	g_free(net.operator_name);
+
+	g_free(modem_obj_path);
+	g_free(last_dialed_number);
+
+	g_slist_foreach(calls, (GFunc) vc_free, NULL);
+	g_slist_free(calls);
+	calls = NULL;
+
+	dbus_connection_remove_filter(connection, signal_filter, NULL);
+
+	dbus_connection_unref(connection);
+	connection = NULL;
+}
diff --git a/audio/telephony.h b/audio/telephony.h
new file mode 100644
index 0000000..9bc5ee2
--- /dev/null
+++ b/audio/telephony.h
@@ -0,0 +1,234 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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>
+
+/* HFP feature bits */
+#define AG_FEATURE_THREE_WAY_CALLING		0x0001
+#define AG_FEATURE_EC_ANDOR_NR			0x0002
+#define AG_FEATURE_VOICE_RECOGNITION		0x0004
+#define AG_FEATURE_INBAND_RINGTONE		0x0008
+#define AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG	0x0010
+#define AG_FEATURE_REJECT_A_CALL		0x0020
+#define AG_FEATURE_ENHANCED_CALL_STATUS		0x0040
+#define AG_FEATURE_ENHANCED_CALL_CONTROL	0x0080
+#define AG_FEATURE_EXTENDED_ERROR_RESULT_CODES	0x0100
+
+#define HF_FEATURE_EC_ANDOR_NR			0x0001
+#define HF_FEATURE_CALL_WAITING_AND_3WAY	0x0002
+#define HF_FEATURE_CLI_PRESENTATION		0x0004
+#define HF_FEATURE_VOICE_RECOGNITION		0x0008
+#define HF_FEATURE_REMOTE_VOLUME_CONTROL	0x0010
+#define HF_FEATURE_ENHANCED_CALL_STATUS		0x0020
+#define HF_FEATURE_ENHANCED_CALL_CONTROL	0x0040
+
+/* Indicator event values */
+#define EV_SERVICE_NONE			0
+#define EV_SERVICE_PRESENT		1
+
+#define EV_CALL_INACTIVE		0
+#define EV_CALL_ACTIVE			1
+
+#define EV_CALLSETUP_INACTIVE		0
+#define EV_CALLSETUP_INCOMING		1
+#define EV_CALLSETUP_OUTGOING		2
+#define EV_CALLSETUP_ALERTING		3
+
+#define EV_CALLHELD_NONE		0
+#define EV_CALLHELD_MULTIPLE		1
+#define EV_CALLHELD_ON_HOLD		2
+
+#define EV_ROAM_INACTIVE		0
+#define EV_ROAM_ACTIVE			1
+
+/* Call parameters */
+#define CALL_DIR_OUTGOING		0
+#define CALL_DIR_INCOMING		1
+
+#define CALL_STATUS_ACTIVE		0
+#define CALL_STATUS_HELD		1
+#define CALL_STATUS_DIALING		2
+#define CALL_STATUS_ALERTING		3
+#define CALL_STATUS_INCOMING		4
+#define CALL_STATUS_WAITING		5
+
+#define CALL_MODE_VOICE			0
+#define CALL_MODE_DATA			1
+#define CALL_MODE_FAX			2
+
+#define CALL_MULTIPARTY_NO		0
+#define CALL_MULTIPARTY_YES		1
+
+/* Subscriber number parameters */
+#define SUBSCRIBER_SERVICE_VOICE	4
+#define SUBSCRIBER_SERVICE_FAX		5
+
+/* Operator selection mode values */
+#define OPERATOR_MODE_AUTO		0
+#define OPERATOR_MODE_MANUAL		1
+#define OPERATOR_MODE_DEREGISTER	2
+#define OPERATOR_MODE_MANUAL_AUTO	4
+
+/* Some common number types */
+#define NUMBER_TYPE_UNKNOWN		128
+#define NUMBER_TYPE_TELEPHONY		129
+#define NUMBER_TYPE_INTERNATIONAL	145
+#define NUMBER_TYPE_NATIONAL		161
+#define NUMBER_TYPE_VOIP		255
+
+/* Extended Audio Gateway Error Result Codes */
+typedef enum {
+	CME_ERROR_NONE			= -1,
+	CME_ERROR_AG_FAILURE		= 0,
+	CME_ERROR_NO_PHONE_CONNECTION	= 1,
+	CME_ERROR_NOT_ALLOWED		= 3,
+	CME_ERROR_NOT_SUPPORTED		= 4,
+	CME_ERROR_PH_SIM_PIN_REQUIRED	= 5,
+	CME_ERROR_SIM_NOT_INSERTED	= 10,
+	CME_ERROR_SIM_PIN_REQUIRED	= 11,
+	CME_ERROR_SIM_PUK_REQUIRED	= 12,
+	CME_ERROR_SIM_FAILURE		= 13,
+	CME_ERROR_SIM_BUSY		= 14,
+	CME_ERROR_INCORRECT_PASSWORD	= 16,
+	CME_ERROR_SIM_PIN2_REQUIRED	= 17,
+	CME_ERROR_SIM_PUK2_REQUIRED	= 18,
+	CME_ERROR_MEMORY_FULL		= 20,
+	CME_ERROR_INVALID_INDEX		= 21,
+	CME_ERROR_MEMORY_FAILURE	= 23,
+	CME_ERROR_TEXT_STRING_TOO_LONG	= 24,
+	CME_ERROR_INVALID_TEXT_STRING	= 25,
+	CME_ERROR_DIAL_STRING_TOO_LONG	= 26,
+	CME_ERROR_INVALID_DIAL_STRING	= 27,
+	CME_ERROR_NO_NETWORK_SERVICE	= 30,
+	CME_ERROR_NETWORK_TIMEOUT	= 31,
+	CME_ERROR_NETWORK_NOT_ALLOWED	= 32,
+} cme_error_t;
+
+struct indicator {
+	const char *desc;
+	const char *range;
+	int val;
+	gboolean ignore_redundant;
+};
+
+/* Notify telephony-*.c of connected/disconnected devices. Implemented by
+ * telephony-*.c
+ */
+void telephony_device_connected(void *telephony_device);
+void telephony_device_disconnected(void *telephony_device);
+
+/* HF requests (sent by the handsfree device). These are implemented by
+ * telephony-*.c
+ */
+void telephony_event_reporting_req(void *telephony_device, int ind);
+void telephony_response_and_hold_req(void *telephony_device, int rh);
+void telephony_last_dialed_number_req(void *telephony_device);
+void telephony_terminate_call_req(void *telephony_device);
+void telephony_answer_call_req(void *telephony_device);
+void telephony_dial_number_req(void *telephony_device, const char *number);
+void telephony_transmit_dtmf_req(void *telephony_device, char tone);
+void telephony_subscriber_number_req(void *telephony_device);
+void telephony_list_current_calls_req(void *telephony_device);
+void telephony_operator_selection_req(void *telephony_device);
+void telephony_call_hold_req(void *telephony_device, const char *cmd);
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable);
+void telephony_key_press_req(void *telephony_device, const char *keys);
+
+/* AG responses to HF requests. These are implemented by headset.c */
+int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err);
+int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err);
+int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_terminate_call_rsp(void *telephony_device, cme_error_t err);
+int telephony_answer_call_rsp(void *telephony_device, cme_error_t err);
+int telephony_dial_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err);
+int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err);
+int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err);
+int telephony_call_hold_rsp(void *telephony_device, cme_error_t err);
+int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err);
+int telephony_key_press_rsp(void *telephony_device, cme_error_t err);
+
+/* Event indications by AG. These are implemented by headset.c */
+int telephony_event_ind(int index);
+int telephony_response_and_hold_ind(int rh);
+int telephony_incoming_call_ind(const char *number, int type);
+int telephony_calling_stopped_ind(void);
+int telephony_ready_ind(uint32_t features, const struct indicator *indicators,
+			int rh, const char *chld);
+int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
+					int mprty, const char *number,
+					int type);
+int telephony_subscriber_number_ind(const char *number, int type,
+					int service);
+int telephony_call_waiting_ind(const char *number, int type);
+int telephony_operator_selection_ind(int mode, const char *oper);
+
+/* Helper function for quick indicator updates */
+static inline int telephony_update_indicator(struct indicator *indicators,
+						const char *desc,
+						int new_val)
+{
+	int i;
+	struct indicator *ind = NULL;
+
+	for (i = 0; indicators[i].desc != NULL; i++) {
+		if (g_str_equal(indicators[i].desc, desc)) {
+			ind = &indicators[i];
+			break;
+		}
+	}
+
+	if (!ind)
+		return -ENOENT;
+
+	debug("Telephony indicator \"%s\" %d->%d", desc, ind->val, new_val);
+
+	if (ind->ignore_redundant && ind->val == new_val) {
+		debug("Ignoring no-change indication");
+		return 0;
+	}
+
+	ind->val = new_val;
+
+	return telephony_event_ind(i);
+}
+
+static inline int telephony_get_indicator(const struct indicator *indicators,
+						const char *desc)
+{
+	int i;
+
+	for (i = 0; indicators[i].desc != NULL; i++) {
+		if (g_str_equal(indicators[i].desc, desc))
+			return indicators[i].val;
+	}
+
+	return -ENOENT;
+}
+
+int telephony_init(void);
+void telephony_exit(void);
diff --git a/audio/unix.c b/audio/unix.c
new file mode 100644
index 0000000..b04cc59
--- /dev/null
+++ b/audio/unix.c
@@ -0,0 +1,1772 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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 <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 "logging.h"
+#include "ipc.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "sink.h"
+#include "gateway.h"
+#include "unix.h"
+#include "glib-helper.h"
+
+#define check_nul(str) (str[sizeof(str) - 1] == '\0')
+
+typedef enum {
+	TYPE_NONE,
+	TYPE_HEADSET,
+	TYPE_GATEWAY,
+	TYPE_SINK,
+	TYPE_SOURCE
+} service_type_t;
+
+typedef void (*notify_cb_t) (struct audio_device *dev, void *data);
+
+struct a2dp_data {
+	struct avdtp *session;
+	struct avdtp_stream *stream;
+	struct a2dp_sep *sep;
+};
+
+struct headset_data {
+	gboolean locked;
+};
+
+struct unix_client {
+	struct audio_device *dev;
+	GSList *caps;
+	service_type_t type;
+	char *interface;
+	uint8_t seid;
+	union {
+		struct a2dp_data a2dp;
+		struct headset_data hs;
+	} d;
+	int sock;
+	int lock;
+	int data_fd; /* To be deleted once two phase configuration is fully implemented */
+	unsigned int req_id;
+	unsigned int cb_id;
+	gboolean (*cancel) (struct audio_device *dev, unsigned int id);
+};
+
+static GSList *clients = NULL;
+
+static int unix_sock = -1;
+
+static void client_free(struct unix_client *client)
+{
+	debug("client_free(%p)", client);
+
+	if (client->cancel && client->dev && client->req_id > 0)
+		client->cancel(client->dev, client->req_id);
+
+	if (client->sock >= 0)
+		close(client->sock);
+
+	if (client->caps) {
+		g_slist_foreach(client->caps, (GFunc) g_free, NULL);
+		g_slist_free(client->caps);
+	}
+
+	g_free(client->interface);
+	g_free(client);
+}
+
+/* Pass file descriptor through local domain sockets (AF_LOCAL, formerly
+ * AF_UNIX) and the sendmsg() system call with the cmsg_type field of a "struct
+ * cmsghdr" set to SCM_RIGHTS and the data being an integer value equal to the
+ * handle of the file descriptor to be passed. */
+static int unix_sendmsg_fd(int sock, int fd)
+{
+	char cmsg_b[CMSG_SPACE(sizeof(int))], m = 'm';
+	struct cmsghdr *cmsg;
+	struct iovec iov = { &m, sizeof(m) };
+	struct msghdr msgh;
+
+	memset(&msgh, 0, sizeof(msgh));
+	msgh.msg_iov = &iov;
+	msgh.msg_iovlen = 1;
+	msgh.msg_control = &cmsg_b;
+	msgh.msg_controllen = CMSG_LEN(sizeof(int));
+
+	cmsg = CMSG_FIRSTHDR(&msgh);
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+	/* Initialize the payload */
+	memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+	return sendmsg(sock, &msgh, MSG_NOSIGNAL);
+}
+
+static void unix_ipc_sendmsg(struct unix_client *client,
+					const bt_audio_msg_header_t *msg)
+{
+	const char *type = bt_audio_strtype(msg->type);
+	const char *name = bt_audio_strname(msg->name);
+
+	debug("Audio API: %s -> %s", type, name);
+
+	if (send(client->sock, msg, msg->length, 0) < 0)
+		error("Error %s(%d)", strerror(errno), errno);
+}
+
+static void unix_ipc_error(struct unix_client *client, uint8_t name, int err)
+{
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	bt_audio_error_t *rsp = (void *) buf;
+
+	if (!g_slist_find(clients, client))
+		return;
+
+	memset(buf, 0, sizeof(buf));
+	rsp->h.type = BT_ERROR;
+	rsp->h.name = name;
+	rsp->h.length = sizeof(*rsp);
+
+	rsp->posix_errno = err;
+
+	unix_ipc_sendmsg(client, &rsp->h);
+}
+
+static service_type_t select_service(struct audio_device *dev, const char *interface)
+{
+	if (!interface) {
+		if (dev->sink && avdtp_is_connected(&dev->src, &dev->dst))
+			return TYPE_SINK;
+		else if (dev->headset && headset_is_active(dev))
+			return TYPE_HEADSET;
+		else if (dev->sink)
+			return TYPE_SINK;
+		else if (dev->headset)
+			return TYPE_HEADSET;
+	} else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink)
+		return TYPE_SINK;
+	else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset)
+		return TYPE_HEADSET;
+	else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway)
+		return TYPE_GATEWAY;
+
+	return TYPE_NONE;
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+					avdtp_state_t old_state,
+					avdtp_state_t new_state,
+					struct avdtp_error *err,
+					void *user_data)
+{
+	struct unix_client *client = user_data;
+	struct a2dp_data *a2dp = &client->d.a2dp;
+
+	switch (new_state) {
+	case AVDTP_STATE_IDLE:
+		if (a2dp->sep) {
+			a2dp_sep_unlock(a2dp->sep, a2dp->session);
+			a2dp->sep = NULL;
+		}
+		if (a2dp->session) {
+			avdtp_unref(a2dp->session);
+			a2dp->session = NULL;
+		}
+		a2dp->stream = NULL;
+		client->cb_id = 0;
+		break;
+	default:
+		break;
+	}
+}
+
+static uint8_t headset_generate_capability(struct audio_device *dev,
+						codec_capabilities_t *codec)
+{
+	pcm_capabilities_t *pcm;
+
+	codec->seid = BT_A2DP_SEID_RANGE + 1;
+	codec->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+	codec->type = BT_HFP_CODEC_PCM;
+	codec->length = sizeof(*pcm);
+
+	pcm = (void *) codec;
+	pcm->sampling_rate = 8000;
+	if (dev->headset) {
+		if (headset_get_nrec(dev))
+			pcm->flags |= BT_PCM_FLAG_NREC;
+		if (!headset_get_sco_hci(dev))
+			pcm->flags |= BT_PCM_FLAG_PCM_ROUTING;
+		codec->configured = headset_is_active(dev);
+		codec->lock = headset_get_lock(dev);
+	} else {
+		pcm->flags |= BT_PCM_FLAG_NREC;
+		codec->configured = TRUE;
+		codec->lock = 0;
+	}
+
+	return codec->length;
+}
+
+static void headset_discovery_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_get_capabilities_rsp *rsp = (void *) buf;
+	uint8_t length;
+
+	client->req_id = 0;
+
+	if (!dev)
+		goto failed;
+
+	memset(buf, 0, sizeof(buf));
+
+	length = headset_generate_capability(dev, (void *) rsp->data);
+
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_GET_CAPABILITIES;
+	rsp->h.length = sizeof(*rsp) + length;
+
+	ba2str(&dev->src, rsp->source);
+	ba2str(&dev->dst, rsp->destination);
+	strncpy(rsp->object, dev->path, sizeof(rsp->object));
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	return;
+
+failed:
+	error("discovery failed");
+	unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+}
+
+static void headset_setup_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+
+	client->req_id = 0;
+
+	if (!dev)
+		goto failed;
+
+	memset(buf, 0, sizeof(buf));
+
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_SET_CONFIGURATION;
+	rsp->h.length = sizeof(*rsp);
+
+	rsp->link_mtu = 48;
+
+	client->data_fd = headset_get_sco_fd(dev);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	return;
+
+failed:
+	error("config failed");
+	unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+}
+
+static void gateway_setup_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+
+	if (!dev) {
+		unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+		return;
+	}
+
+	client->req_id = 0;
+
+	memset(buf, 0, sizeof(buf));
+
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_SET_CONFIGURATION;
+	rsp->h.length = sizeof(*rsp);
+
+	rsp->link_mtu = 48;
+
+	client->data_fd = gateway_get_sco_fd(dev);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+}
+
+static void headset_resume_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_start_stream_rsp *rsp = (void *) buf;
+	struct bt_new_stream_ind *ind = (void *) buf;
+
+	client->req_id = 0;
+
+	if (!dev)
+		goto failed;
+
+	client->data_fd = headset_get_sco_fd(dev);
+	if (client->data_fd < 0) {
+		error("Unable to get a SCO fd");
+		goto failed;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_START_STREAM;
+	rsp->h.length = sizeof(*rsp);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	memset(buf, 0, sizeof(buf));
+	ind->h.type = BT_INDICATION;
+	ind->h.name = BT_NEW_STREAM;
+	ind->h.length = sizeof(*ind);
+
+	unix_ipc_sendmsg(client, &ind->h);
+
+	if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) {
+		error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno);
+		goto failed;
+	}
+
+	return;
+
+failed:
+	error("headset_resume_complete: resume failed");
+	unix_ipc_error(client, BT_START_STREAM, EIO);
+}
+
+static void gateway_resume_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_start_stream_rsp *rsp = (void *) buf;
+	struct bt_new_stream_ind *ind = (void *) buf;
+
+	memset(buf, 0, sizeof(buf));
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_START_STREAM;
+	rsp->h.length = sizeof(*rsp);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	memset(buf, 0, sizeof(buf));
+	ind->h.type = BT_INDICATION;
+	ind->h.name = BT_NEW_STREAM;
+	ind->h.length = sizeof(*ind);
+
+	unix_ipc_sendmsg(client, &ind->h);
+
+	client->data_fd = gateway_get_sco_fd(dev);
+	if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) {
+		error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno);
+		unix_ipc_error(client, BT_START_STREAM, EIO);
+	}
+
+	client->req_id = 0;
+}
+
+static void headset_suspend_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_stop_stream_rsp *rsp = (void *) buf;
+
+	if (!dev)
+		goto failed;
+
+	memset(buf, 0, sizeof(buf));
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_STOP_STREAM;
+	rsp->h.length = sizeof(*rsp);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	return;
+
+failed:
+	error("suspend failed");
+	unix_ipc_error(client, BT_STOP_STREAM, EIO);
+}
+
+static void print_mpeg12(struct mpeg_codec_cap *mpeg)
+{
+	debug("Media Codec: MPEG12"
+		" Channel Modes: %s%s%s%s"
+		" Frequencies: %s%s%s%s%s%s"
+		" Layers: %s%s%s"
+		" CRC: %s",
+		mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO ? "Mono " : "",
+		mpeg->channel_mode & MPEG_CHANNEL_MODE_DUAL_CHANNEL ?
+		"DualChannel " : "",
+		mpeg->channel_mode & MPEG_CHANNEL_MODE_STEREO ? "Stereo " : "",
+		mpeg->channel_mode & MPEG_CHANNEL_MODE_JOINT_STEREO ?
+		"JointStereo " : "",
+		mpeg->frequency & MPEG_SAMPLING_FREQ_16000 ? "16Khz " : "",
+		mpeg->frequency & MPEG_SAMPLING_FREQ_22050 ? "22.05Khz " : "",
+		mpeg->frequency & MPEG_SAMPLING_FREQ_24000 ? "24Khz " : "",
+		mpeg->frequency & MPEG_SAMPLING_FREQ_32000 ? "32Khz " : "",
+		mpeg->frequency & MPEG_SAMPLING_FREQ_44100 ? "44.1Khz " : "",
+		mpeg->frequency & MPEG_SAMPLING_FREQ_48000 ? "48Khz " : "",
+		mpeg->layer & MPEG_LAYER_MP1 ? "1 " : "",
+		mpeg->layer & MPEG_LAYER_MP2 ? "2 " : "",
+		mpeg->layer & MPEG_LAYER_MP3 ? "3 " : "",
+		mpeg->crc ? "Yes" : "No");
+}
+
+static void print_sbc(struct sbc_codec_cap *sbc)
+{
+	debug("Media Codec: SBC"
+		" Channel Modes: %s%s%s%s"
+		" Frequencies: %s%s%s%s"
+		" Subbands: %s%s"
+		" Blocks: %s%s%s%s"
+		" Bitpool: %d-%d",
+		sbc->channel_mode & SBC_CHANNEL_MODE_MONO ? "Mono " : "",
+		sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL ?
+		"DualChannel " : "",
+		sbc->channel_mode & SBC_CHANNEL_MODE_STEREO ? "Stereo " : "",
+		sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO ? "JointStereo" : "",
+		sbc->frequency & SBC_SAMPLING_FREQ_16000 ? "16Khz " : "",
+		sbc->frequency & SBC_SAMPLING_FREQ_32000 ? "32Khz " : "",
+		sbc->frequency & SBC_SAMPLING_FREQ_44100 ? "44.1Khz " : "",
+		sbc->frequency & SBC_SAMPLING_FREQ_48000 ? "48Khz " : "",
+		sbc->subbands & SBC_SUBBANDS_4 ? "4 " : "",
+		sbc->subbands & SBC_SUBBANDS_8 ? "8 " : "",
+		sbc->block_length & SBC_BLOCK_LENGTH_4 ? "4 " : "",
+		sbc->block_length & SBC_BLOCK_LENGTH_8 ? "8 " : "",
+		sbc->block_length & SBC_BLOCK_LENGTH_12 ? "12 " : "",
+		sbc->block_length & SBC_BLOCK_LENGTH_16 ? "16 " : "",
+		sbc->min_bitpool, sbc->max_bitpool);
+}
+
+static int a2dp_append_codec(struct bt_get_capabilities_rsp *rsp,
+				struct avdtp_service_capability *cap,
+				uint8_t seid,
+				uint8_t configured,
+				uint8_t lock)
+{
+	struct avdtp_media_codec_capability *codec_cap = (void *) cap->data;
+	codec_capabilities_t *codec = (void *) rsp + rsp->h.length;
+	size_t space_left;
+
+	if (rsp->h.length > BT_SUGGESTED_BUFFER_SIZE)
+		return -ENOMEM;
+
+	space_left = BT_SUGGESTED_BUFFER_SIZE - rsp->h.length;
+
+	/* endianess prevent direct cast */
+	if (codec_cap->media_codec_type == A2DP_CODEC_SBC) {
+		struct sbc_codec_cap *sbc_cap = (void *) codec_cap;
+		sbc_capabilities_t *sbc = (void *) codec;
+
+		if (space_left < sizeof(sbc_capabilities_t))
+			return -ENOMEM;
+
+		codec->length = sizeof(sbc_capabilities_t);
+
+		sbc->channel_mode = sbc_cap->channel_mode;
+		sbc->frequency = sbc_cap->frequency;
+		sbc->allocation_method = sbc_cap->allocation_method;
+		sbc->subbands = sbc_cap->subbands;
+		sbc->block_length = sbc_cap->block_length;
+		sbc->min_bitpool = sbc_cap->min_bitpool;
+		sbc->max_bitpool = sbc_cap->max_bitpool;
+
+		print_sbc(sbc_cap);
+		codec->type = BT_A2DP_SBC_SINK;
+	} else if (codec_cap->media_codec_type == A2DP_CODEC_MPEG12) {
+		struct mpeg_codec_cap *mpeg_cap = (void *) codec_cap;
+		mpeg_capabilities_t *mpeg = (void *) codec;
+
+		if (space_left < sizeof(mpeg_capabilities_t))
+			return -ENOMEM;
+
+		codec->length = sizeof(mpeg_capabilities_t);
+
+		mpeg->channel_mode = mpeg_cap->channel_mode;
+		mpeg->crc = mpeg_cap->crc;
+		mpeg->layer = mpeg_cap->layer;
+		mpeg->frequency = mpeg_cap->frequency;
+		mpeg->mpf = mpeg_cap->mpf;
+		mpeg->bitrate = mpeg_cap->bitrate;
+
+		print_mpeg12(mpeg_cap);
+		codec->type = BT_A2DP_MPEG12_SINK;
+	} else {
+		size_t codec_length, type_length, total_length;
+
+		codec_length = cap->length - (sizeof(struct avdtp_service_capability)
+				+ sizeof(struct avdtp_media_codec_capability));
+		type_length = sizeof(codec_cap->media_codec_type);
+		total_length = type_length + codec_length +
+				sizeof(codec_capabilities_t);
+
+		if (space_left < total_length)
+			return -ENOMEM;
+
+		codec->length = total_length;
+		memcpy(codec->data, &codec_cap->media_codec_type, type_length);
+		memcpy(codec->data + type_length, codec_cap->data,
+			codec_length);
+		codec->type = BT_A2DP_UNKNOWN_SINK;
+	}
+
+	codec->seid = seid;
+	codec->configured = configured;
+	codec->lock = lock;
+	rsp->h.length += codec->length;
+
+	debug("Append %s seid %d - length %d - total %d",
+		configured ? "configured" : "", seid, codec->length,
+		rsp->h.length);
+
+	return 0;
+}
+
+static void a2dp_discovery_complete(struct avdtp *session, GSList *seps,
+					struct avdtp_error *err,
+					void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_get_capabilities_rsp *rsp = (void *) buf;
+	struct a2dp_data *a2dp = &client->d.a2dp;
+	GSList *l;
+
+	if (!g_slist_find(clients, client)) {
+		debug("Client disconnected during discovery");
+		return;
+	}
+
+	if (err)
+		goto failed;
+
+	memset(buf, 0, sizeof(buf));
+	client->req_id = 0;
+
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_GET_CAPABILITIES;
+	rsp->h.length = sizeof(*rsp);
+	ba2str(&client->dev->src, rsp->source);
+	ba2str(&client->dev->dst, rsp->destination);
+	strncpy(rsp->object, client->dev->path, sizeof(rsp->object));
+
+	for (l = seps; l; l = g_slist_next(l)) {
+		struct avdtp_remote_sep *rsep = l->data;
+		struct a2dp_sep *sep;
+		struct avdtp_service_capability *cap;
+		struct avdtp_stream *stream;
+		uint8_t type, seid, configured = 0, lock = 0;
+		GSList *cl;
+
+		type = avdtp_get_type(rsep);
+
+		if (type != AVDTP_SEP_TYPE_SINK)
+			continue;
+
+		cap = avdtp_get_codec(rsep);
+
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		seid = avdtp_get_seid(rsep);
+
+		if (client->seid != 0 && client->seid != seid)
+			continue;
+
+		stream = avdtp_get_stream(rsep);
+		if (stream) {
+			configured = 1;
+			if (client->seid == seid)
+				cap = avdtp_stream_get_codec(stream);
+		}
+
+		for (cl = clients; cl; cl = cl->next) {
+			struct unix_client *c = cl->data;
+			struct a2dp_data *ca2dp = &c->d.a2dp;
+
+			if (ca2dp && ca2dp->session == session &&
+					c->seid == seid) {
+				lock = c->lock;
+				break;
+			}
+		}
+
+		sep = a2dp_get_sep(session, stream);
+		if (sep && a2dp_sep_get_lock(sep))
+			lock = BT_WRITE_LOCK;
+
+		a2dp_append_codec(rsp, cap, seid, configured, lock);
+	}
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	return;
+
+failed:
+	error("discovery failed");
+	unix_ipc_error(client, BT_GET_CAPABILITIES, EIO);
+
+	if (a2dp->sep) {
+		a2dp_sep_unlock(a2dp->sep, a2dp->session);
+		a2dp->sep = NULL;
+	}
+
+	avdtp_unref(a2dp->session);
+	a2dp->session = NULL;
+	a2dp->stream = NULL;
+}
+
+static void a2dp_config_complete(struct avdtp *session, struct a2dp_sep *sep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err,
+					void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+	struct a2dp_data *a2dp = &client->d.a2dp;
+	uint16_t imtu, omtu;
+	GSList *caps;
+
+	client->req_id = 0;
+
+	if (err)
+		goto failed;
+
+	memset(buf, 0, sizeof(buf));
+
+	if (!stream)
+		goto failed;
+
+	if (client->cb_id > 0)
+		avdtp_stream_remove_cb(a2dp->session, a2dp->stream,
+								client->cb_id);
+
+	a2dp->sep = sep;
+	a2dp->stream = stream;
+
+	if (!avdtp_stream_get_transport(stream, &client->data_fd, &imtu, &omtu,
+					&caps)) {
+		error("Unable to get stream transport");
+		goto failed;
+	}
+
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_SET_CONFIGURATION;
+	rsp->h.length = sizeof(*rsp);
+
+	/* FIXME: Use imtu when fd_opt is CFG_FD_OPT_READ */
+	rsp->link_mtu = omtu;
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	client->cb_id = avdtp_stream_add_cb(session, stream,
+						stream_state_changed, client);
+
+	return;
+
+failed:
+	error("config failed");
+
+	unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+
+	avdtp_unref(a2dp->session);
+
+	a2dp->session = NULL;
+	a2dp->stream = NULL;
+	a2dp->sep = NULL;
+}
+
+static void a2dp_resume_complete(struct avdtp *session,
+				struct avdtp_error *err, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_start_stream_rsp *rsp = (void *) buf;
+	struct bt_new_stream_ind *ind = (void *) buf;
+	struct a2dp_data *a2dp = &client->d.a2dp;
+
+	if (err)
+		goto failed;
+
+	memset(buf, 0, sizeof(buf));
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_START_STREAM;
+	rsp->h.length = sizeof(*rsp);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	memset(buf, 0, sizeof(buf));
+	ind->h.type = BT_RESPONSE;
+	ind->h.name = BT_NEW_STREAM;
+	rsp->h.length = sizeof(*ind);
+
+	unix_ipc_sendmsg(client, &ind->h);
+
+	if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) {
+		error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno);
+		goto failed;
+	}
+
+	return;
+
+failed:
+	error("resume failed");
+
+	unix_ipc_error(client, BT_START_STREAM, EIO);
+
+	if (client->cb_id > 0) {
+		avdtp_stream_remove_cb(a2dp->session, a2dp->stream,
+					client->cb_id);
+		client->cb_id = 0;
+	}
+
+	if (a2dp->sep) {
+		a2dp_sep_unlock(a2dp->sep, a2dp->session);
+		a2dp->sep = NULL;
+	}
+
+	avdtp_unref(a2dp->session);
+	a2dp->session = NULL;
+	a2dp->stream = NULL;
+}
+
+static void a2dp_suspend_complete(struct avdtp *session,
+				struct avdtp_error *err, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_stop_stream_rsp *rsp = (void *) buf;
+	struct a2dp_data *a2dp = &client->d.a2dp;
+
+	if (err)
+		goto failed;
+
+	memset(buf, 0, sizeof(buf));
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_STOP_STREAM;
+	rsp->h.length = sizeof(*rsp);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	return;
+
+failed:
+	error("suspend failed");
+
+	unix_ipc_error(client, BT_STOP_STREAM, EIO);
+
+	if (a2dp->sep) {
+		a2dp_sep_unlock(a2dp->sep, a2dp->session);
+		a2dp->sep = NULL;
+	}
+
+	avdtp_unref(a2dp->session);
+	a2dp->session = NULL;
+	a2dp->stream = NULL;
+}
+
+static void start_discovery(struct audio_device *dev, struct unix_client *client)
+{
+	struct a2dp_data *a2dp;
+	int err = 0;
+
+	switch (client->type) {
+	case TYPE_SINK:
+		a2dp = &client->d.a2dp;
+
+		if (!a2dp->session)
+			a2dp->session = avdtp_get(&dev->src, &dev->dst);
+
+		if (!a2dp->session) {
+			error("Unable to get a session");
+			goto failed;
+		}
+
+		err = avdtp_discover(a2dp->session, a2dp_discovery_complete,
+					client);
+		if (err) {
+			if (a2dp->session) {
+				avdtp_unref(a2dp->session);
+				a2dp->session = NULL;
+			}
+			goto failed;
+		}
+		break;
+
+	case TYPE_HEADSET:
+	case TYPE_GATEWAY:
+		headset_discovery_complete(dev, client);
+		break;
+
+	default:
+		error("No known services for device");
+		goto failed;
+	}
+
+	client->dev = dev;
+
+	return;
+
+failed:
+	unix_ipc_error(client, BT_GET_CAPABILITIES, err ? : EIO);
+}
+
+static void open_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_open_rsp *rsp = (void *) buf;
+
+	memset(buf, 0, sizeof(buf));
+
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_OPEN;
+	rsp->h.length = sizeof(*rsp);
+
+	ba2str(&dev->src, rsp->source);
+	ba2str(&dev->dst, rsp->destination);
+	strncpy(rsp->object, dev->path, sizeof(rsp->object));
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	return;
+}
+
+static void start_open(struct audio_device *dev, struct unix_client *client)
+{
+	struct a2dp_data *a2dp;
+	struct headset_data *hs;
+	struct avdtp_remote_sep *rsep;
+	gboolean unref_avdtp_on_fail = FALSE;
+
+	switch (client->type) {
+	case TYPE_SINK:
+	case TYPE_SOURCE:
+		a2dp = &client->d.a2dp;
+
+		if (!a2dp->session) {
+			a2dp->session = avdtp_get(&dev->src, &dev->dst);
+			unref_avdtp_on_fail = TRUE;
+		}
+
+		if (!a2dp->session) {
+			error("Unable to get a session");
+			goto failed;
+		}
+
+		if (a2dp->sep) {
+			error("Client already has an opened session");
+			goto failed;
+		}
+
+		rsep = avdtp_get_remote_sep(a2dp->session, client->seid);
+		if (!rsep) {
+			error("Invalid seid %d", client->seid);
+			goto failed;
+		}
+
+		a2dp->sep = a2dp_get(a2dp->session, rsep);
+		if (!a2dp->sep) {
+			error("seid %d not available or locked", client->seid);
+			goto failed;
+		}
+
+		if (!a2dp_sep_lock(a2dp->sep, a2dp->session)) {
+			error("Unable to open seid %d", client->seid);
+			a2dp->sep = NULL;
+			goto failed;
+		}
+
+		break;
+
+	case TYPE_HEADSET:
+		hs = &client->d.hs;
+
+		if (hs->locked) {
+			error("Client already has an opened session");
+			goto failed;
+		}
+
+		hs->locked = headset_lock(dev, client->lock);
+		if (!hs->locked) {
+			error("Unable to open seid %d", client->seid);
+			goto failed;
+		}
+		break;
+
+        case TYPE_GATEWAY:
+                break;
+	default:
+		error("No known services for device");
+		goto failed;
+	}
+
+	client->dev = dev;
+
+	open_complete(dev, client);
+
+	return;
+
+failed:
+	if (unref_avdtp_on_fail && a2dp->session) {
+		avdtp_unref(a2dp->session);
+		a2dp->session = NULL;
+	}
+	unix_ipc_error(client, BT_OPEN, EINVAL);
+}
+
+static void start_config(struct audio_device *dev, struct unix_client *client)
+{
+	struct a2dp_data *a2dp;
+	struct headset_data *hs;
+	unsigned int id;
+
+	switch (client->type) {
+	case TYPE_SINK:
+	case TYPE_SOURCE:
+		a2dp = &client->d.a2dp;
+
+		if (!a2dp->session)
+			a2dp->session = avdtp_get(&dev->src, &dev->dst);
+
+		if (!a2dp->session) {
+			error("Unable to get a session");
+			goto failed;
+		}
+
+		if (!a2dp->sep) {
+			error("seid %d not opened", client->seid);
+			goto failed;
+		}
+
+		id = a2dp_config(a2dp->session, a2dp->sep, a2dp_config_complete,
+					client->caps, client);
+		client->cancel = a2dp_cancel;
+		break;
+
+	case TYPE_HEADSET:
+		hs = &client->d.hs;
+
+		if (!hs->locked) {
+			error("seid %d not opened", client->seid);
+			goto failed;
+		}
+
+		id = headset_config_stream(dev, TRUE, headset_setup_complete,
+						client);
+		client->cancel = headset_cancel_stream;
+		break;
+	case TYPE_GATEWAY:
+		if (gateway_config_stream(dev, gateway_setup_complete, client) >= 0) {
+			client->cancel = gateway_cancel_stream;
+			id = 1;
+		} else
+			id = 0;
+		break;
+
+	default:
+		error("No known services for device");
+		goto failed;
+	}
+
+	if (id == 0) {
+		error("config failed");
+		goto failed;
+	}
+
+	client->req_id = id;
+
+	return;
+
+failed:
+	unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+}
+
+static void start_resume(struct audio_device *dev, struct unix_client *client)
+{
+	struct a2dp_data *a2dp;
+	struct headset_data *hs;
+	unsigned int id;
+	gboolean unref_avdtp_on_fail = FALSE;
+
+	switch (client->type) {
+	case TYPE_SINK:
+	case TYPE_SOURCE:
+		a2dp = &client->d.a2dp;
+
+		if (!a2dp->session) {
+			a2dp->session = avdtp_get(&dev->src, &dev->dst);
+			unref_avdtp_on_fail = TRUE;
+		}
+
+		if (!a2dp->session) {
+			error("Unable to get a session");
+			goto failed;
+		}
+
+		if (!a2dp->sep) {
+			error("seid not opened");
+			goto failed;
+		}
+
+		id = a2dp_resume(a2dp->session, a2dp->sep, a2dp_resume_complete,
+					client);
+		client->cancel = a2dp_cancel;
+
+		break;
+
+	case TYPE_HEADSET:
+		hs = &client->d.hs;
+
+		if (!hs->locked) {
+			error("seid not opened");
+			goto failed;
+		}
+
+		id = headset_request_stream(dev, headset_resume_complete,
+						client);
+		client->cancel = headset_cancel_stream;
+		break;
+
+	case TYPE_GATEWAY:
+		if (gateway_request_stream(dev, gateway_resume_complete, client))
+			id = 1;
+		else
+			id = 0;
+		client->cancel = gateway_cancel_stream;
+		break;
+
+	default:
+		error("No known services for device");
+		goto failed;
+	}
+
+	if (id == 0) {
+		error("start_resume: resume failed");
+		goto failed;
+	}
+
+	client->req_id = id;
+
+	return;
+
+failed:
+	if (unref_avdtp_on_fail && a2dp->session) {
+		avdtp_unref(a2dp->session);
+		a2dp->session = NULL;
+	}
+	unix_ipc_error(client, BT_START_STREAM, EIO);
+}
+
+static void start_suspend(struct audio_device *dev, struct unix_client *client)
+{
+	struct a2dp_data *a2dp;
+	struct headset_data *hs;
+	unsigned int id;
+	gboolean unref_avdtp_on_fail = FALSE;
+
+	switch (client->type) {
+	case TYPE_SINK:
+	case TYPE_SOURCE:
+		a2dp = &client->d.a2dp;
+
+		if (!a2dp->session) {
+			a2dp->session = avdtp_get(&dev->src, &dev->dst);
+			unref_avdtp_on_fail = TRUE;
+		}
+
+		if (!a2dp->session) {
+			error("Unable to get a session");
+			goto failed;
+		}
+
+		if (!a2dp->sep) {
+			error("Unable to get a sep");
+			goto failed;
+		}
+
+		id = a2dp_suspend(a2dp->session, a2dp->sep,
+					a2dp_suspend_complete, client);
+		client->cancel = a2dp_cancel;
+		break;
+
+	case TYPE_HEADSET:
+		hs = &client->d.hs;
+
+		if (!hs->locked) {
+			error("seid not opened");
+			goto failed;
+		}
+
+		id = headset_suspend_stream(dev, headset_suspend_complete,
+						client);
+		client->cancel = headset_cancel_stream;
+		break;
+
+	case TYPE_GATEWAY:
+		gateway_suspend_stream(dev);
+		client->cancel = gateway_cancel_stream;
+		headset_suspend_complete(dev, client);
+		id = 1;
+		break;
+
+	default:
+		error("No known services for device");
+		goto failed;
+	}
+
+	if (id == 0) {
+		error("suspend failed");
+		goto failed;
+	}
+
+	return;
+
+failed:
+	if (unref_avdtp_on_fail && a2dp->session) {
+		avdtp_unref(a2dp->session);
+		a2dp->session = NULL;
+	}
+	unix_ipc_error(client, BT_STOP_STREAM, EIO);
+}
+
+static void close_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_close_rsp *rsp = (void *) buf;
+
+	memset(buf, 0, sizeof(buf));
+
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_CLOSE;
+	rsp->h.length = sizeof(*rsp);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	return;
+}
+
+static void start_close(struct audio_device *dev, struct unix_client *client,
+			gboolean reply)
+{
+	struct a2dp_data *a2dp;
+	struct headset_data *hs;
+
+	if (!client->dev)
+		goto failed;
+
+	switch (client->type) {
+	case TYPE_HEADSET:
+		hs = &client->d.hs;
+
+		if (client->dev && hs->locked) {
+			headset_unlock(client->dev, client->lock);
+			hs->locked = FALSE;
+		}
+		break;
+        case TYPE_GATEWAY:
+                break;
+	case TYPE_SOURCE:
+	case TYPE_SINK:
+		a2dp = &client->d.a2dp;
+
+		if (client->cb_id > 0)
+			avdtp_stream_remove_cb(a2dp->session, a2dp->stream,
+								client->cb_id);
+		if (a2dp->sep) {
+			a2dp_sep_unlock(a2dp->sep, a2dp->session);
+			a2dp->sep = NULL;
+		}
+		if (a2dp->session) {
+			avdtp_unref(a2dp->session);
+			a2dp->session = NULL;
+		}
+		break;
+	default:
+		error("No known services for device");
+		goto failed;
+	}
+
+	if (!reply)
+		return;
+
+	close_complete(dev, client);
+	client->dev = NULL;
+
+	return;
+
+failed:
+	if (reply)
+		unix_ipc_error(client, BT_STOP_STREAM, EINVAL);
+}
+
+static void handle_getcapabilities_req(struct unix_client *client,
+					struct bt_get_capabilities_req *req)
+{
+	struct audio_device *dev;
+	bdaddr_t src, dst;
+	int err = EIO;
+
+	if (!check_nul(req->source) || !check_nul(req->destination) ||
+			!check_nul(req->object)) {
+		err = EINVAL;
+		goto failed;
+	}
+
+	str2ba(req->source, &src);
+	str2ba(req->destination, &dst);
+
+	if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)
+		client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
+	else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+		client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+
+	if (!manager_find_device(req->object, &src, &dst, NULL, FALSE))
+		goto failed;
+
+	dev = manager_find_device(req->object, &src, &dst, client->interface,
+				TRUE);
+	if (!dev && (req->flags & BT_FLAG_AUTOCONNECT))
+		dev = manager_find_device(req->object, &src, &dst,
+					client->interface, FALSE);
+
+	if (!dev && req->transport == BT_CAPABILITIES_TRANSPORT_SCO) {
+		g_free(client->interface);
+		client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE);
+
+		dev = manager_find_device(req->object, &src, &dst,
+				client->interface, TRUE);
+		if (!dev && (req->flags & BT_FLAG_AUTOCONNECT))
+			dev = manager_find_device(req->object, &src, &dst,
+					client->interface, FALSE);
+	}
+
+	if (!dev) {
+		error("Unable to find a matching device");
+		goto failed;
+	}
+
+	client->type = select_service(dev, client->interface);
+	if (client->type == TYPE_NONE) {
+		error("No matching service found");
+		goto failed;
+	}
+
+	client->seid = req->seid;
+
+	start_discovery(dev, client);
+
+	return;
+
+failed:
+	unix_ipc_error(client, BT_GET_CAPABILITIES, err);
+}
+
+static int handle_sco_open(struct unix_client *client, struct bt_open_req *req)
+{
+	if (!client->interface)
+		client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
+	else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) &&
+		!g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE))
+		return -EIO;
+
+	debug("open sco - object=%s source=%s destination=%s lock=%s%s",
+			strcmp(req->object, "") ? req->object : "ANY",
+			strcmp(req->source, "") ? req->source : "ANY",
+			strcmp(req->destination, "") ? req->destination : "ANY",
+			req->lock & BT_READ_LOCK ? "read" : "",
+			req->lock & BT_WRITE_LOCK ? "write" : "");
+
+	return 0;
+}
+
+static int handle_a2dp_open(struct unix_client *client, struct bt_open_req *req)
+{
+	if (!client->interface)
+		client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+	else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE))
+		return -EIO;
+
+	debug("open a2dp - object=%s source=%s destination=%s lock=%s%s",
+			strcmp(req->object, "") ? req->object : "ANY",
+			strcmp(req->source, "") ? req->source : "ANY",
+			strcmp(req->destination, "") ? req->destination : "ANY",
+			req->lock & BT_READ_LOCK ? "read" : "",
+			req->lock & BT_WRITE_LOCK ? "write" : "");
+
+	return 0;
+}
+
+static void handle_open_req(struct unix_client *client, struct bt_open_req *req)
+{
+	struct audio_device *dev;
+	bdaddr_t src, dst;
+	int err = 0;
+
+	if (!check_nul(req->source) || !check_nul(req->destination) ||
+			!check_nul(req->object)) {
+		err = EINVAL;
+		goto failed;
+	}
+
+	str2ba(req->source, &src);
+	str2ba(req->destination, &dst);
+
+	if (req->seid > BT_A2DP_SEID_RANGE) {
+		err = handle_sco_open(client, req);
+		if (err < 0) {
+			err = -err;
+			goto failed;
+		}
+	} else {
+		err = handle_a2dp_open(client, req);
+		if (err < 0) {
+			err = -err;
+			goto failed;
+		}
+	}
+
+	if (!manager_find_device(req->object, &src, &dst, NULL, FALSE))
+		goto failed;
+
+	dev = manager_find_device(req->object, &src, &dst, client->interface,
+				TRUE);
+	if (!dev)
+		dev = manager_find_device(req->object, &src, &dst,
+					client->interface, FALSE);
+
+	if (!dev)
+		goto failed;
+
+	client->seid = req->seid;
+	client->lock = req->lock;
+
+	start_open(dev, client);
+
+	return;
+
+failed:
+	unix_ipc_error(client, BT_OPEN, err ? : EIO);
+}
+
+static int handle_sco_transport(struct unix_client *client,
+				struct bt_set_configuration_req *req)
+{
+	struct audio_device *dev = client->dev;
+
+	if (!client->interface) {
+		if (dev->headset)
+			client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
+		else if (dev->gateway)
+			client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE);
+		else
+			return -EIO;
+	} else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) &&
+			!g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE))
+		return -EIO;
+
+	return 0;
+}
+
+static int handle_a2dp_transport(struct unix_client *client,
+				struct bt_set_configuration_req *req)
+{
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct sbc_codec_cap sbc_cap;
+	struct mpeg_codec_cap mpeg_cap;
+
+	if (!client->interface)
+		client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+	else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE))
+		return -EIO;
+
+	if (client->caps) {
+		g_slist_foreach(client->caps, (GFunc) g_free, NULL);
+		g_slist_free(client->caps);
+		client->caps = NULL;
+	}
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	client->caps = g_slist_append(client->caps, media_transport);
+
+	if (req->codec.type == BT_A2DP_MPEG12_SINK) {
+		mpeg_capabilities_t *mpeg = (void *) &req->codec;
+
+		memset(&mpeg_cap, 0, sizeof(mpeg_cap));
+
+		mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+		mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12;
+		mpeg_cap.channel_mode = mpeg->channel_mode;
+		mpeg_cap.crc = mpeg->crc;
+		mpeg_cap.layer = mpeg->layer;
+		mpeg_cap.frequency = mpeg->frequency;
+		mpeg_cap.mpf = mpeg->mpf;
+		mpeg_cap.bitrate = mpeg->bitrate;
+
+		media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap,
+							sizeof(mpeg_cap));
+
+		print_mpeg12(&mpeg_cap);
+	} else if (req->codec.type == BT_A2DP_SBC_SINK) {
+		sbc_capabilities_t *sbc = (void *) &req->codec;
+
+		memset(&sbc_cap, 0, sizeof(sbc_cap));
+
+		sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+		sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC;
+		sbc_cap.channel_mode = sbc->channel_mode;
+		sbc_cap.frequency = sbc->frequency;
+		sbc_cap.allocation_method = sbc->allocation_method;
+		sbc_cap.subbands = sbc->subbands;
+		sbc_cap.block_length = sbc->block_length;
+		sbc_cap.min_bitpool = sbc->min_bitpool;
+		sbc_cap.max_bitpool = sbc->max_bitpool;
+
+		media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+							sizeof(sbc_cap));
+
+		print_sbc(&sbc_cap);
+	} else
+		return -EINVAL;
+
+	client->caps = g_slist_append(client->caps, media_codec);
+
+	return 0;
+}
+
+static void handle_setconfiguration_req(struct unix_client *client,
+					struct bt_set_configuration_req *req)
+{
+	int err = 0;
+
+	if (req->codec.seid != client->seid) {
+		error("Unable to set configuration: seid %d not opened",
+				client->seid);
+		goto failed;
+	}
+
+	if (!client->dev)
+		goto failed;
+
+	if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_SCO) {
+		err = handle_sco_transport(client, req);
+		if (err < 0) {
+			err = -err;
+			goto failed;
+		}
+	} else if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+		err = handle_a2dp_transport(client, req);
+		if (err < 0) {
+			err = -err;
+			goto failed;
+		}
+	}
+
+	start_config(client->dev, client);
+
+	return;
+
+failed:
+	unix_ipc_error(client, BT_SET_CONFIGURATION, err ? : EIO);
+}
+
+static void handle_streamstart_req(struct unix_client *client,
+					struct bt_start_stream_req *req)
+{
+	if (!client->dev)
+		goto failed;
+
+	start_resume(client->dev, client);
+
+	return;
+
+failed:
+	unix_ipc_error(client, BT_START_STREAM, EIO);
+}
+
+static void handle_streamstop_req(struct unix_client *client,
+					struct bt_stop_stream_req *req)
+{
+	if (!client->dev)
+		goto failed;
+
+	start_suspend(client->dev, client);
+
+	return;
+
+failed:
+	unix_ipc_error(client, BT_STOP_STREAM, EIO);
+}
+
+static void handle_close_req(struct unix_client *client,
+				struct bt_close_req *req)
+{
+	if (!client->dev)
+		goto failed;
+
+	start_close(client->dev, client, TRUE);
+
+	return;
+
+failed:
+	unix_ipc_error(client, BT_CLOSE, EIO);
+}
+
+static void handle_control_req(struct unix_client *client,
+					struct bt_control_req *req)
+{
+	/* FIXME: really implement that */
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+
+	memset(buf, 0, sizeof(buf));
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_CONTROL;
+	rsp->h.length = sizeof(*rsp);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+}
+
+static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	bt_audio_msg_header_t *msghdr = (void *) buf;
+	struct unix_client *client = data;
+	int len;
+	const char *type, *name;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	if (cond & (G_IO_HUP | G_IO_ERR)) {
+		debug("Unix client disconnected (fd=%d)", client->sock);
+
+		goto failed;
+	}
+
+	memset(buf, 0, sizeof(buf));
+
+	len = recv(client->sock, buf, sizeof(buf), 0);
+	if (len < 0) {
+		error("recv: %s (%d)", strerror(errno), errno);
+		goto failed;
+	}
+
+	type = bt_audio_strtype(msghdr->type);
+	name = bt_audio_strname(msghdr->name);
+
+	debug("Audio API: %s <- %s", type, name);
+
+	if (msghdr->length != len) {
+		error("Invalid message: length mismatch");
+		goto failed;
+	}
+
+	switch (msghdr->name) {
+	case BT_GET_CAPABILITIES:
+		handle_getcapabilities_req(client,
+				(struct bt_get_capabilities_req *) msghdr);
+		break;
+	case BT_OPEN:
+		handle_open_req(client,
+				(struct bt_open_req *) msghdr);
+		break;
+	case BT_SET_CONFIGURATION:
+		handle_setconfiguration_req(client,
+				(struct bt_set_configuration_req *) msghdr);
+		break;
+	case BT_START_STREAM:
+		handle_streamstart_req(client,
+				(struct bt_start_stream_req *) msghdr);
+		break;
+	case BT_STOP_STREAM:
+		handle_streamstop_req(client,
+				(struct bt_stop_stream_req *) msghdr);
+		break;
+	case BT_CLOSE:
+		handle_close_req(client,
+				(struct bt_close_req *) msghdr);
+		break;
+	case BT_CONTROL:
+		handle_control_req(client,
+				(struct bt_control_req *) msghdr);
+		break;
+	default:
+		error("Audio API: received unexpected message name %d",
+				msghdr->name);
+	}
+
+	return TRUE;
+
+failed:
+	clients = g_slist_remove(clients, client);
+	start_close(client->dev, client, FALSE);
+	client_free(client);
+	return FALSE;
+}
+
+static gboolean server_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+	struct sockaddr_un addr;
+	socklen_t addrlen;
+	int sk, cli_sk;
+	struct unix_client *client;
+	GIOChannel *io;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	if (cond & (G_IO_HUP | G_IO_ERR)) {
+		g_io_channel_close(chan);
+		return FALSE;
+	}
+
+	sk = g_io_channel_unix_get_fd(chan);
+
+	memset(&addr, 0, sizeof(addr));
+	addrlen = sizeof(addr);
+
+	cli_sk = accept(sk, (struct sockaddr *) &addr, &addrlen);
+	if (cli_sk < 0) {
+		error("accept: %s (%d)", strerror(errno), errno);
+		return TRUE;
+	}
+
+	debug("Accepted new client connection on unix socket (fd=%d)", cli_sk);
+	set_nonblocking(cli_sk);
+
+	client = g_new0(struct unix_client, 1);
+	client->sock = cli_sk;
+	clients = g_slist_append(clients, client);
+
+	io = g_io_channel_unix_new(cli_sk);
+	g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+							client_cb, client);
+	g_io_channel_unref(io);
+
+	return TRUE;
+}
+
+void unix_device_removed(struct audio_device *dev)
+{
+	GSList *l;
+
+	debug("unix_device_removed(%p)", dev);
+
+	l = clients;
+	while (l) {
+		struct unix_client *client = l->data;
+
+		l = l->next;
+
+		if (client->dev == dev) {
+			clients = g_slist_remove(clients, client);
+			start_close(client->dev, client, FALSE);
+			client_free(client);
+		}
+	}
+}
+
+int unix_init(void)
+{
+	GIOChannel *io;
+	struct sockaddr_un addr = {
+		AF_UNIX, BT_IPC_SOCKET_NAME
+	};
+
+	int sk, err;
+
+	sk = socket(PF_LOCAL, SOCK_STREAM, 0);
+	if (sk < 0) {
+		err = errno;
+		error("Can't create unix socket: %s (%d)", strerror(err), err);
+		return -err;
+	}
+
+	if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		error("Can't bind unix socket: %s (%d)", strerror(errno),
+				errno);
+		close(sk);
+		return -1;
+	}
+
+	set_nonblocking(sk);
+
+	if (listen(sk, 1) < 0) {
+		error("Can't listen on unix socket: %s (%d)",
+						strerror(errno), errno);
+		close(sk);
+		return -1;
+	}
+
+	unix_sock = sk;
+
+	io = g_io_channel_unix_new(sk);
+	g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+							server_cb, NULL);
+	g_io_channel_unref(io);
+
+	debug("Unix socket created: %d", sk);
+
+	return 0;
+}
+
+void unix_exit(void)
+{
+	g_slist_foreach(clients, (GFunc) client_free, NULL);
+	g_slist_free(clients);
+	if (unix_sock >= 0) {
+		close(unix_sock);
+		unix_sock = -1;
+	}
+}
diff --git a/audio/unix.h b/audio/unix.h
new file mode 100644
index 0000000..12cf3ef
--- /dev/null
+++ b/audio/unix.h
@@ -0,0 +1,28 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <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);
+
+int unix_init(void);
+void unix_exit(void);
diff --git a/bluez.m4 b/bluez.m4
new file mode 100644
index 0000000..0257a3f
--- /dev/null
+++ b/bluez.m4
@@ -0,0 +1,40 @@
+AC_DEFUN([AM_PATH_BLUEZ], [
+	if (test "${prefix}" = "NONE"); then
+		bluez_prefix=${ac_default_prefix}
+	else
+		bluez_prefix=${prefix}
+	fi
+
+	AC_ARG_WITH(bluez, AC_HELP_STRING([--with-bluez=DIR], [BlueZ library is installed in DIR]), [
+		if (test "${withval}" != "yes"); then
+			bluez_prefix=${withval}
+		fi
+	])
+
+	ac_save_CPPFLAGS=$CPPFLAGS
+	ac_save_LDFLAGS=$LDFLAGS
+
+	BLUEZ_CFLAGS=""
+	test -d "${bluez_prefix}/include" && BLUEZ_CFLAGS="$BLUEZ_CFLAGS -I${bluez_prefix}/include"
+
+	CPPFLAGS="$CPPFLAGS $BLUEZ_CFLAGS"
+	AC_CHECK_HEADER(bluetooth/bluetooth.h,, AC_MSG_ERROR(Bluetooth header files not found))
+
+	BLUEZ_LIBS=""
+	if (test "${ac_default_prefix}" = "${bluez_prefix}"); then
+		test -d "${libdir}" && BLUEZ_LIBS="$BLUEZ_LIBS -L${libdir}"
+	else
+		test -d "${bluez_prefix}/lib64" && BLUEZ_LIBS="$BLUEZ_LIBS -L${bluez_prefix}/lib64"
+		test -d "${bluez_prefix}/lib" && BLUEZ_LIBS="$BLUEZ_LIBS -L${bluez_prefix}/lib"
+	fi
+
+	LDFLAGS="$LDFLAGS $BLUEZ_LIBS"
+	AC_CHECK_LIB(bluetooth, hci_open_dev, BLUEZ_LIBS="$BLUEZ_LIBS -lbluetooth", AC_MSG_ERROR(Bluetooth library not found))
+	AC_CHECK_LIB(bluetooth, sdp_connect,, AC_CHECK_LIB(sdp, sdp_connect, BLUEZ_LIBS="$BLUEZ_LIBS -lsdp"))
+
+	CPPFLAGS=$ac_save_CPPFLAGS
+	LDFLAGS=$ac_save_LDFLAGS
+
+	AC_SUBST(BLUEZ_CFLAGS)
+	AC_SUBST(BLUEZ_LIBS)
+])
diff --git a/bluez.pc.in b/bluez.pc.in
new file mode 100644
index 0000000..3d6e596
--- /dev/null
+++ b/bluez.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+ 
+Name: BlueZ
+Description: Bluetooth protocol stack for Linux
+Version: @VERSION@
+Libs: -L${libdir} -lbluetooth
+Cflags: -I${includedir}
diff --git a/bootstrap b/bootstrap
new file mode 100755
index 0000000..91756f9
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+aclocal && \
+    autoheader && \
+	libtoolize --automake --copy --force && \
+	    automake --add-missing --copy && \
+		autoconf
diff --git a/bootstrap-configure b/bootstrap-configure
new file mode 100755
index 0000000..15b3ba7
--- /dev/null
+++ b/bootstrap-configure
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+if [ -f config.status ]; then
+	make maintainer-clean
+fi
+
+if [ ! -f doc/gtk-doc.make ]; then
+	gtkdocize --copy --docdir doc
+fi
+
+./bootstrap && \
+    ./configure --enable-maintainer-mode \
+		--enable-debug \
+		--prefix=/usr \
+		--mandir=/usr/share/man \
+		--sysconfdir=/etc \
+		--localstatedir=/var \
+		--libexecdir=/lib \
+		--enable-netlink \
+		--enable-tools \
+		--enable-bccmd \
+		--enable-dfutool \
+		--enable-hid2hci \
+		--enable-hidd \
+		--enable-pand \
+		--enable-dund \
+		--enable-test \
+		--enable-cups \
+		--disable-pcmcia \
+		--disable-manpages \
+		--disable-udevrules \
+		--disable-configfiles $*
diff --git a/client/Makefile.am b/client/Makefile.am
new file mode 100644
index 0000000..0274292
--- /dev/null
+++ b/client/Makefile.am
@@ -0,0 +1,2 @@
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/common/Android.mk b/common/Android.mk
new file mode 100755
index 0000000..74c0bec
--- /dev/null
+++ b/common/Android.mk
@@ -0,0 +1,34 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	btio.c \
+	glib-helper.c \
+	logging.c \
+	oui.c \
+	sdp-xml.c \
+	textfile.c \
+	test_textfile.c \
+	android_bluez.c
+
+LOCAL_CFLAGS+= \
+	-O3 \
+	-DNEED_DBUS_WATCH_GET_UNIX_FD
+
+ifeq ($(BOARD_HAVE_BLUETOOTH_BCM),true)
+LOCAL_CFLAGS += \
+	-DBOARD_HAVE_BLUETOOTH_BCM
+endif
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(call include-path-for, glib) \
+	$(call include-path-for, glib)/glib \
+	$(call include-path-for, dbus)
+
+LOCAL_MODULE:=libbluez-common-static
+
+LOCAL_STATIC_LIBRARY:= \
+	libglib_static
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/common/Makefile.am b/common/Makefile.am
new file mode 100644
index 0000000..8fe1547
--- /dev/null
+++ b/common/Makefile.am
@@ -0,0 +1,15 @@
+
+noinst_LIBRARIES = libhelper.a
+
+libhelper_a_SOURCES = oui.h oui.c textfile.h textfile.c logging.h logging.c \
+		glib-helper.h glib-helper.c sdp-xml.h sdp-xml.c btio.h btio.c
+
+noinst_PROGRAMS = test_textfile
+
+test_textfile_LDADD = libhelper.a
+
+AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+EXTRA_DIST = ppoll.h uinput.h
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/common/android_bluez.c b/common/android_bluez.c
new file mode 100644
index 0000000..69b2ea3
--- /dev/null
+++ b/common/android_bluez.c
@@ -0,0 +1,174 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009 The Android Open Source Project
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <private/android_filesystem_config.h>
+#include <sys/prctl.h>
+#include <linux/capability.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+
+/* Set UID to bluetooth w/ CAP_NET_RAW, CAP_NET_ADMIN and CAP_NET_BIND_SERVICE
+ * (Android's init.rc does not yet support applying linux capabilities) */
+void android_set_aid_and_cap() {
+	prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
+	setuid(AID_BLUETOOTH);
+
+	struct __user_cap_header_struct header;
+	struct __user_cap_data_struct cap;
+	header.version = _LINUX_CAPABILITY_VERSION;
+	header.pid = 0;
+	cap.effective = cap.permitted = 1 << CAP_NET_RAW |
+					1 << CAP_NET_ADMIN |
+					1 << CAP_NET_BIND_SERVICE;
+	cap.inheritable = 0;
+	capset(&header, &cap);
+}
+
+#ifdef BOARD_HAVE_BLUETOOTH_BCM
+static int vendor_high_priority(int fd, uint16_t handle) {
+    unsigned char hci_sleep_cmd[] = {
+        0x01,               // HCI command packet
+        0x57, 0xfc,         // HCI_Write_High_Priority_Connection
+        0x02,               // Length
+        0x00, 0x00          // Handle
+    };
+
+    hci_sleep_cmd[4] = (uint8_t)handle;
+    hci_sleep_cmd[5] = (uint8_t)(handle >> 8);
+
+    int ret = write(fd, hci_sleep_cmd, sizeof(hci_sleep_cmd));
+    if (ret < 0) {
+        error("write(): %s (%d)]", strerror(errno), errno);
+        return -1;
+    } else if (ret != sizeof(hci_sleep_cmd)) {
+        error("write(): unexpected length %d", ret);
+        return -1;
+    }
+    return 0;
+}
+
+static int get_hci_sock() {
+    int sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+    struct sockaddr_hci addr;
+    int opt;
+
+    if(sock < 0) {
+        error("Can't create raw HCI socket!");
+        return -1;
+    }
+
+    opt = 1;
+    if (setsockopt(sock, SOL_HCI, HCI_DATA_DIR, &opt, sizeof(opt)) < 0) {
+        error("Error setting data direction\n");
+        return -1;
+    }
+
+    /* Bind socket to the HCI device */
+    addr.hci_family = AF_BLUETOOTH;
+    addr.hci_dev = 0;  // hci0
+    if(bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+        error("Can't attach to device hci0. %s(%d)\n",
+             strerror(errno),
+             errno);
+        return -1;
+    }
+    return sock;
+}
+
+static int get_acl_handle(int fd, bdaddr_t *bdaddr) {
+    int i;
+    int ret = -1;
+    struct hci_conn_list_req *conn_list;
+    struct hci_conn_info *conn_info;
+    int max_conn = 10;
+
+    conn_list = malloc(max_conn * (
+            sizeof(struct hci_conn_list_req) + sizeof(struct hci_conn_info)));
+    if (!conn_list) {
+        error("Out of memory in %s\n", __FUNCTION__);
+        return -1;
+    }
+
+    conn_list->dev_id = 0;  /* hardcoded to HCI device 0 */
+    conn_list->conn_num = max_conn;
+
+    if (ioctl(fd, HCIGETCONNLIST, (void *)conn_list)) {
+        error("Failed to get connection list\n");
+        goto out;
+    }
+
+    for (i=0; i < conn_list->conn_num; i++) {
+        conn_info = &conn_list->conn_info[i];
+        if (conn_info->type == ACL_LINK &&
+                !memcmp((void *)&conn_info->bdaddr, (void *)bdaddr,
+                sizeof(bdaddr_t))) {
+            ret = conn_info->handle;
+            goto out;
+        }
+    }
+    ret = 0;
+
+out:
+    free(conn_list);
+    return ret;
+}
+
+/* Request that the ACL link to a given Bluetooth connection be high priority,
+ * for improved coexistance support
+ */
+int android_set_high_priority(bdaddr_t *ba) {
+    int ret;
+    int fd = get_hci_sock();
+    int acl_handle;
+
+    if (fd < 0)
+        return fd;
+
+    acl_handle = get_acl_handle(fd, ba);
+    if (acl_handle < 0) {
+        ret = acl_handle;
+        goto out;
+    }
+
+    ret = vendor_high_priority(fd, acl_handle);
+
+out:
+    close(fd);
+
+    return ret;
+}
+
+#else
+
+int android_set_high_priority(bdaddr_t *ba) {
+    return 0;
+}
+
+#endif
diff --git a/common/btio.c b/common/btio.c
new file mode 100644
index 0000000..d7e575d
--- /dev/null
+++ b/common/btio.c
@@ -0,0 +1,1274 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009  Nokia Corporation
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#include <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 "logging.h"
+#include "btio.h"
+
+#define ERROR_FAILED(gerr, str, err) \
+		g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_FAILED, \
+				str ": %s (%d)", strerror(err), err)
+
+#define DEFAULT_DEFER_TIMEOUT 30
+
+struct set_opts {
+	bdaddr_t src;
+	bdaddr_t dst;
+	int defer;
+	int sec_level;
+	uint8_t channel;
+	uint16_t psm;
+	uint16_t mtu;
+	uint16_t imtu;
+	uint16_t omtu;
+	int master;
+};
+
+struct connect {
+	BtIOConnect connect;
+	gpointer user_data;
+	GDestroyNotify destroy;
+};
+
+struct accept {
+	BtIOConnect connect;
+	gpointer user_data;
+	GDestroyNotify destroy;
+};
+
+struct server {
+	BtIOConnect connect;
+	BtIOConfirm confirm;
+	gpointer user_data;
+	GDestroyNotify destroy;
+};
+
+static void server_remove(struct server *server)
+{
+	if (server->destroy)
+		server->destroy(server->user_data);
+	g_free(server);
+}
+
+static void connect_remove(struct connect *conn)
+{
+	if (conn->destroy)
+		conn->destroy(conn->user_data);
+	g_free(conn);
+}
+
+static void accept_remove(struct accept *accept)
+{
+	if (accept->destroy)
+		accept->destroy(accept->user_data);
+	g_free(accept);
+}
+
+static gboolean check_nval(GIOChannel *io)
+{
+	struct pollfd fds;
+
+	memset(&fds, 0, sizeof(fds));
+	fds.fd = g_io_channel_unix_get_fd(io);
+	fds.events = POLLNVAL;
+
+	if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLNVAL))
+		return TRUE;
+
+	return FALSE;
+}
+
+static gboolean accept_cb(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	struct accept *accept = user_data;
+	GError *err = NULL;
+
+	/* If the user aborted this accept attempt */
+	if ((cond & G_IO_NVAL) || check_nval(io))
+		return FALSE;
+
+	if (cond & (G_IO_HUP | G_IO_ERR))
+		g_set_error(&err, BT_IO_ERROR, BT_IO_ERROR_DISCONNECTED,
+				"HUP or ERR on socket");
+
+	accept->connect(io, err, accept->user_data);
+
+	g_clear_error(&err);
+
+	return FALSE;
+}
+
+static gboolean connect_cb(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	struct connect *conn = user_data;
+	GError *gerr = NULL;
+
+	/* If the user aborted this connect attempt */
+	if ((cond & G_IO_NVAL) || check_nval(io))
+		return FALSE;
+
+	if (cond & G_IO_OUT) {
+		int err = 0, sock = g_io_channel_unix_get_fd(io);
+		socklen_t len = sizeof(err);
+
+		if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
+			err = errno;
+
+		if (err)
+			g_set_error(&gerr, BT_IO_ERROR,
+					BT_IO_ERROR_CONNECT_FAILED, "%s (%d)",
+					strerror(err), err);
+	} else if (cond & (G_IO_HUP | G_IO_ERR))
+		g_set_error(&gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED,
+				"HUP or ERR on socket");
+
+	conn->connect(io, gerr, conn->user_data);
+
+	if (gerr)
+		g_error_free(gerr);
+
+	return FALSE;
+}
+
+static gboolean server_cb(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	struct server *server = user_data;
+	int srv_sock, cli_sock;
+	GIOChannel *cli_io;
+
+	/* If the user closed the server */
+	if ((cond & G_IO_NVAL) || check_nval(io))
+		return FALSE;
+
+	srv_sock = g_io_channel_unix_get_fd(io);
+
+	cli_sock = accept(srv_sock, NULL, NULL);
+	if (cli_sock < 0) {
+		error("accept: %s (%d)", strerror(errno), errno);
+		return TRUE;
+	}
+
+	cli_io = g_io_channel_unix_new(cli_sock);
+
+	g_io_channel_set_close_on_unref(cli_io, TRUE);
+	g_io_channel_set_flags(cli_io, G_IO_FLAG_NONBLOCK, NULL);
+
+	if (server->confirm)
+		server->confirm(cli_io, server->user_data);
+	else
+		server->connect(cli_io, NULL, server->user_data);
+
+	g_io_channel_unref(cli_io);
+
+	return TRUE;
+}
+
+static void server_add(GIOChannel *io, BtIOConnect connect,
+				BtIOConfirm confirm, gpointer user_data,
+				GDestroyNotify destroy)
+{
+	struct server *server;
+	GIOCondition cond;
+
+	server = g_new0(struct server, 1);
+	server->connect = connect;
+	server->confirm = confirm;
+	server->user_data = user_data;
+	server->destroy = destroy;
+
+	cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+	g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, server_cb, server,
+					(GDestroyNotify) server_remove);
+}
+
+static void connect_add(GIOChannel *io, BtIOConnect connect,
+				gpointer user_data, GDestroyNotify destroy)
+{
+	struct connect *conn;
+	GIOCondition cond;
+
+	conn = g_new0(struct connect, 1);
+	conn->connect = connect;
+	conn->user_data = user_data;
+	conn->destroy = destroy;
+
+	cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+	g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, conn,
+					(GDestroyNotify) connect_remove);
+}
+
+static void accept_add(GIOChannel *io, BtIOConnect connect, gpointer user_data,
+							GDestroyNotify destroy)
+{
+	struct accept *accept;
+	GIOCondition cond;
+
+	accept = g_new0(struct accept, 1);
+	accept->connect = connect;
+	accept->user_data = user_data;
+	accept->destroy = destroy;
+
+	cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+	g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, accept_cb, accept,
+					(GDestroyNotify) accept_remove);
+}
+
+static int l2cap_bind(int sock, const bdaddr_t *src, uint16_t psm)
+{
+	struct sockaddr_l2 addr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.l2_family = AF_BLUETOOTH;
+	bacpy(&addr.l2_bdaddr, src);
+	addr.l2_psm = htobs(psm);
+
+	return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
+}
+
+static int l2cap_connect(int sock, const bdaddr_t *dst, uint16_t psm)
+{
+	int err;
+	struct sockaddr_l2 addr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.l2_family = AF_BLUETOOTH;
+	bacpy(&addr.l2_bdaddr, dst);
+	addr.l2_psm = htobs(psm);
+
+	err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
+	if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
+		return err;
+
+	return 0;
+}
+
+static int l2cap_set_master(int sock, int master)
+{
+	int flags;
+	socklen_t len;
+
+	len = sizeof(flags);
+	if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0)
+		return -errno;
+
+	if (master) {
+		if (flags & L2CAP_LM_MASTER)
+			return 0;
+		flags |= L2CAP_LM_MASTER;
+	} else {
+		if (!(flags & L2CAP_LM_MASTER))
+			return 0;
+		flags &= ~L2CAP_LM_MASTER;
+	}
+
+	if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, sizeof(flags)) < 0)
+		return -errno;
+
+	return 0;
+}
+
+static int rfcomm_set_master(int sock, int master)
+{
+	int flags;
+	socklen_t len;
+
+	len = sizeof(flags);
+	if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, &len) < 0)
+		return -errno;
+
+	if (master) {
+		if (flags & RFCOMM_LM_MASTER)
+			return 0;
+		flags |= RFCOMM_LM_MASTER;
+	} else {
+		if (!(flags & RFCOMM_LM_MASTER))
+			return 0;
+		flags &= ~RFCOMM_LM_MASTER;
+	}
+
+	if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, sizeof(flags)) < 0)
+		return -errno;
+
+	return 0;
+}
+
+static int l2cap_set_lm(int sock, int level)
+{
+	int lm_map[] = {
+		0,
+		L2CAP_LM_AUTH,
+		L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT,
+		L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT | L2CAP_LM_SECURE,
+	}, opt = lm_map[level];
+
+	if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0)
+		return -errno;
+
+	return 0;
+}
+
+static int rfcomm_set_lm(int sock, int level)
+{
+	int lm_map[] = {
+		0,
+		RFCOMM_LM_AUTH,
+		RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT,
+		RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT | RFCOMM_LM_SECURE,
+	}, opt = lm_map[level];
+
+	if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0)
+		return -errno;
+
+	return 0;
+}
+
+static gboolean set_sec_level(int sock, BtIOType type, int level, GError **err)
+{
+	struct bt_security sec;
+	int ret;
+
+	if (level < BT_SECURITY_LOW || level > BT_SECURITY_HIGH) {
+		g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+				"Valid security level range is %d-%d",
+				BT_SECURITY_LOW, BT_SECURITY_HIGH);
+		return FALSE;
+	}
+
+	memset(&sec, 0, sizeof(sec));
+	sec.level = level;
+
+	if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec,
+							sizeof(sec)) == 0)
+		return TRUE;
+
+	if (errno != ENOPROTOOPT) {
+		ERROR_FAILED(err, "setsockopt(BT_SECURITY)", errno);
+		return FALSE;
+	}
+
+	if (type == BT_IO_L2CAP)
+		ret = l2cap_set_lm(sock, level);
+	else
+		ret = rfcomm_set_lm(sock, level);
+
+	if (ret < 0) {
+		ERROR_FAILED(err, "setsockopt(LM)", -ret);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int l2cap_get_lm(int sock, int *sec_level)
+{
+	int opt;
+	socklen_t len;
+
+	len = sizeof(opt);
+	if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, &len) < 0)
+		return -errno;
+
+	*sec_level = 0;
+
+	if (opt & L2CAP_LM_AUTH)
+		*sec_level = BT_SECURITY_LOW;
+	if (opt & L2CAP_LM_ENCRYPT)
+		*sec_level = BT_SECURITY_MEDIUM;
+	if (opt & L2CAP_LM_SECURE)
+		*sec_level = BT_SECURITY_HIGH;
+
+	return 0;
+}
+
+static int rfcomm_get_lm(int sock, int *sec_level)
+{
+	int opt;
+	socklen_t len;
+
+	len = sizeof(opt);
+	if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, &len) < 0)
+		return -errno;
+
+	*sec_level = 0;
+
+	if (opt & RFCOMM_LM_AUTH)
+		*sec_level = BT_SECURITY_LOW;
+	if (opt & RFCOMM_LM_ENCRYPT)
+		*sec_level = BT_SECURITY_MEDIUM;
+	if (opt & RFCOMM_LM_SECURE)
+		*sec_level = BT_SECURITY_HIGH;
+
+	return 0;
+}
+
+static gboolean get_sec_level(int sock, BtIOType type, int *level,
+								GError **err)
+{
+	struct bt_security sec;
+	socklen_t len;
+	int ret;
+
+	memset(&sec, 0, sizeof(sec));
+	len = sizeof(sec);
+	if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) {
+		*level = sec.level;
+		return TRUE;
+	}
+
+	if (errno != ENOPROTOOPT) {
+		ERROR_FAILED(err, "getsockopt(BT_SECURITY)", errno);
+		return FALSE;
+	}
+
+	if (type == BT_IO_L2CAP)
+		ret = l2cap_get_lm(sock, level);
+	else
+		ret = rfcomm_get_lm(sock, level);
+
+	if (ret < 0) {
+		ERROR_FAILED(err, "getsockopt(LM)", -ret);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean l2cap_set(int sock, int sec_level, uint16_t imtu,
+				uint16_t omtu, int master, GError **err)
+{
+	if (imtu || omtu) {
+		struct l2cap_options l2o;
+		socklen_t len;
+
+		memset(&l2o, 0, sizeof(l2o));
+		len = sizeof(l2o);
+		if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o,
+								&len) < 0) {
+			ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno);
+			return FALSE;
+		}
+
+		if (imtu)
+			l2o.imtu = imtu;
+		if (omtu)
+			l2o.omtu = omtu;
+
+		if (setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o,
+							sizeof(l2o)) < 0) {
+			ERROR_FAILED(err, "setsockopt(L2CAP_OPTIONS)", errno);
+			return FALSE;
+		}
+	}
+
+	if (master >= 0 && l2cap_set_master(sock, master) < 0) {
+		ERROR_FAILED(err, "l2cap_set_master", errno);
+		return FALSE;
+	}
+
+	if (sec_level && !set_sec_level(sock, BT_IO_L2CAP, sec_level, err))
+		return FALSE;
+
+	return TRUE;
+}
+
+static int rfcomm_bind(int sock, const bdaddr_t *src, uint8_t channel)
+{
+	struct sockaddr_rc addr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.rc_family = AF_BLUETOOTH;
+	bacpy(&addr.rc_bdaddr, src);
+	addr.rc_channel = channel;
+
+	return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
+}
+
+static int rfcomm_connect(int sock, const bdaddr_t *dst, uint8_t channel)
+{
+	int err;
+	struct sockaddr_rc addr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.rc_family = AF_BLUETOOTH;
+	bacpy(&addr.rc_bdaddr, dst);
+	addr.rc_channel = channel;
+
+	err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
+	if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
+		return err;
+
+	return 0;
+}
+
+static gboolean rfcomm_set(int sock, int sec_level, int master, GError **err)
+{
+	if (sec_level && !set_sec_level(sock, BT_IO_RFCOMM, sec_level, err))
+		return FALSE;
+
+	if (master >= 0 && rfcomm_set_master(sock, master) < 0) {
+		ERROR_FAILED(err, "rfcomm_set_master", errno);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int sco_bind(int sock, const bdaddr_t *src)
+{
+	struct sockaddr_sco addr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sco_family = AF_BLUETOOTH;
+	bacpy(&addr.sco_bdaddr, src);
+
+	return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
+}
+
+static int sco_connect(int sock, const bdaddr_t *dst)
+{
+	struct sockaddr_sco addr;
+	int err;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sco_family = AF_BLUETOOTH;
+	bacpy(&addr.sco_bdaddr, dst);
+
+	err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
+	if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
+		return err;
+
+	return 0;
+}
+
+static gboolean sco_set(int sock, uint16_t mtu, GError **err)
+{
+	struct sco_options sco_opt;
+	socklen_t len;
+
+	if (!mtu)
+		return TRUE;
+
+	len = sizeof(sco_opt);
+	memset(&sco_opt, 0, len);
+	if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) {
+		ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno);
+		return FALSE;
+	}
+
+	sco_opt.mtu = mtu;
+	if (setsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt,
+						sizeof(sco_opt)) < 0) {
+		ERROR_FAILED(err, "setsockopt(SCO_OPTIONS)", errno);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean parse_set_opts(struct set_opts *opts, GError **err,
+						BtIOOption opt1, va_list args)
+{
+	BtIOOption opt = opt1;
+	const char *str;
+
+	memset(opts, 0, sizeof(*opts));
+
+	/* Set defaults */
+	opts->defer = DEFAULT_DEFER_TIMEOUT;
+	opts->master = -1;
+
+	while (opt != BT_IO_OPT_INVALID) {
+		switch (opt) {
+		case BT_IO_OPT_SOURCE:
+			str = va_arg(args, const char *);
+			if (strncasecmp(str, "hci", 3) == 0)
+				hci_devba(atoi(str + 3), &opts->src);
+			else
+				str2ba(str, &opts->src);
+			break;
+		case BT_IO_OPT_SOURCE_BDADDR:
+			bacpy(&opts->src, va_arg(args, const bdaddr_t *));
+			break;
+		case BT_IO_OPT_DEST:
+			str2ba(va_arg(args, const char *), &opts->dst);
+			break;
+		case BT_IO_OPT_DEST_BDADDR:
+			bacpy(&opts->dst, va_arg(args, const bdaddr_t *));
+			break;
+		case BT_IO_OPT_DEFER_TIMEOUT:
+			opts->defer = va_arg(args, int);
+			break;
+		case BT_IO_OPT_SEC_LEVEL:
+			opts->sec_level = va_arg(args, int);
+			break;
+		case BT_IO_OPT_CHANNEL:
+			opts->channel = va_arg(args, int);
+			break;
+		case BT_IO_OPT_PSM:
+			opts->psm = va_arg(args, int);
+			break;
+		case BT_IO_OPT_MTU:
+			opts->mtu = va_arg(args, int);
+			opts->imtu = opts->mtu;
+			opts->omtu = opts->mtu;
+			break;
+		case BT_IO_OPT_OMTU:
+			opts->omtu = va_arg(args, int);
+			if (!opts->mtu)
+				opts->mtu = opts->omtu;
+			break;
+		case BT_IO_OPT_IMTU:
+			opts->imtu = va_arg(args, int);
+			if (!opts->mtu)
+				opts->mtu = opts->imtu;
+			break;
+		case BT_IO_OPT_MASTER:
+			opts->master = va_arg(args, gboolean);
+			break;
+		default:
+			g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+					"Unknown option %d", opt);
+			return FALSE;
+		}
+
+		opt = va_arg(args, int);
+	}
+
+	return TRUE;
+}
+
+static gboolean get_peers(int sock, struct sockaddr *src, struct sockaddr *dst,
+				socklen_t len, GError **err)
+{
+	socklen_t olen;
+
+	memset(src, 0, len);
+	olen = len;
+	if (getsockname(sock, src, &olen) < 0) {
+		ERROR_FAILED(err, "getsockname", errno);
+		return FALSE;
+	}
+
+	memset(dst, 0, len);
+	olen = len;
+	if (getpeername(sock, dst, &olen) < 0) {
+		ERROR_FAILED(err, "getpeername", errno);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int l2cap_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
+{
+	struct l2cap_conninfo info;
+	socklen_t len;
+
+	len = sizeof(info);
+	if (getsockopt(sock, SOL_L2CAP, L2CAP_CONNINFO, &info, &len) < 0)
+		return -errno;
+
+	if (handle)
+		*handle = info.hci_handle;
+
+	if (dev_class)
+		memcpy(dev_class, info.dev_class, 3);
+
+	return 0;
+}
+
+static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1,
+								va_list args)
+{
+	BtIOOption opt = opt1;
+	struct sockaddr_l2 src, dst;
+	struct l2cap_options l2o;
+	int flags;
+	uint8_t dev_class[3];
+	uint16_t handle;
+	socklen_t len;
+
+	len = sizeof(l2o);
+	memset(&l2o, 0, len);
+	if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) {
+		ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno);
+		return FALSE;
+	}
+
+	if (!get_peers(sock, (struct sockaddr *) &src,
+				(struct sockaddr *) &dst, sizeof(src), err))
+		return FALSE;
+
+	while (opt != BT_IO_OPT_INVALID) {
+		switch (opt) {
+		case BT_IO_OPT_SOURCE:
+			ba2str(&src.l2_bdaddr, va_arg(args, char *));
+			break;
+		case BT_IO_OPT_SOURCE_BDADDR:
+			bacpy(va_arg(args, bdaddr_t *), &src.l2_bdaddr);
+			break;
+		case BT_IO_OPT_DEST:
+			ba2str(&dst.l2_bdaddr, va_arg(args, char *));
+			break;
+		case BT_IO_OPT_DEST_BDADDR:
+			bacpy(va_arg(args, bdaddr_t *), &dst.l2_bdaddr);
+			break;
+		case BT_IO_OPT_DEFER_TIMEOUT:
+			len = sizeof(int);
+			if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP,
+					va_arg(args, int *), &len) < 0) {
+				ERROR_FAILED(err, "getsockopt(DEFER_SETUP)",
+									errno);
+				return FALSE;
+			}
+			break;
+		case BT_IO_OPT_SEC_LEVEL:
+			if (!get_sec_level(sock, BT_IO_L2CAP,
+						va_arg(args, int *), err))
+				return FALSE;
+			break;
+		case BT_IO_OPT_PSM:
+			*(va_arg(args, uint16_t *)) = src.l2_psm ?
+						src.l2_psm : dst.l2_psm;
+			break;
+		case BT_IO_OPT_OMTU:
+			*(va_arg(args, uint16_t *)) = l2o.omtu;
+			break;
+		case BT_IO_OPT_IMTU:
+			*(va_arg(args, uint16_t *)) = l2o.imtu;
+			break;
+		case BT_IO_OPT_MASTER:
+			len = sizeof(flags);
+			if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags,
+								&len) < 0) {
+				ERROR_FAILED(err, "getsockopt(L2CAP_LM)",
+									errno);
+				return FALSE;
+			}
+			*(va_arg(args, gboolean *)) =
+				(flags & L2CAP_LM_MASTER) ? TRUE : FALSE;
+			break;
+		case BT_IO_OPT_HANDLE:
+			if (l2cap_get_info(sock, &handle, dev_class) < 0) {
+				ERROR_FAILED(err, "L2CAP_CONNINFO", errno);
+				return FALSE;
+			}
+			*(va_arg(args, uint16_t *)) = handle;
+			break;
+		case BT_IO_OPT_CLASS:
+			if (l2cap_get_info(sock, &handle, dev_class) < 0) {
+				ERROR_FAILED(err, "L2CAP_CONNINFO", errno);
+				return FALSE;
+			}
+			memcpy(va_arg(args, uint8_t *), dev_class, 3);
+			break;
+		default:
+			g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+					"Unknown option %d", opt);
+			return FALSE;
+		}
+
+		opt = va_arg(args, int);
+	}
+
+	return TRUE;
+}
+
+static int rfcomm_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
+{
+	struct rfcomm_conninfo info;
+	socklen_t len;
+
+	len = sizeof(info);
+	if (getsockopt(sock, SOL_RFCOMM, RFCOMM_CONNINFO, &info, &len) < 0)
+		return -errno;
+
+	if (handle)
+		*handle = info.hci_handle;
+
+	if (dev_class)
+		memcpy(dev_class, info.dev_class, 3);
+
+	return 0;
+}
+
+static gboolean rfcomm_get(int sock, GError **err, BtIOOption opt1,
+								va_list args)
+{
+	BtIOOption opt = opt1;
+	struct sockaddr_rc src, dst;
+	int flags;
+	socklen_t len;
+	uint8_t dev_class[3];
+	uint16_t handle;
+
+	if (!get_peers(sock, (struct sockaddr *) &src,
+				(struct sockaddr *) &dst, sizeof(src), err))
+		return FALSE;
+
+	while (opt != BT_IO_OPT_INVALID) {
+		switch (opt) {
+		case BT_IO_OPT_SOURCE:
+			ba2str(&src.rc_bdaddr, va_arg(args, char *));
+			break;
+		case BT_IO_OPT_SOURCE_BDADDR:
+			bacpy(va_arg(args, bdaddr_t *), &src.rc_bdaddr);
+			break;
+		case BT_IO_OPT_DEST:
+			ba2str(&dst.rc_bdaddr, va_arg(args, char *));
+			break;
+		case BT_IO_OPT_DEST_BDADDR:
+			bacpy(va_arg(args, bdaddr_t *), &dst.rc_bdaddr);
+			break;
+		case BT_IO_OPT_DEFER_TIMEOUT:
+			len = sizeof(int);
+			if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP,
+					va_arg(args, int *), &len) < 0) {
+				ERROR_FAILED(err, "getsockopt(DEFER_SETUP)",
+									errno);
+				return FALSE;
+			}
+			break;
+		case BT_IO_OPT_SEC_LEVEL:
+			if (!get_sec_level(sock, BT_IO_RFCOMM,
+						va_arg(args, int *), err))
+				return FALSE;
+			break;
+		case BT_IO_OPT_CHANNEL:
+			*(va_arg(args, uint8_t *)) = src.rc_channel ?
+					src.rc_channel : dst.rc_channel;
+			break;
+		case BT_IO_OPT_MASTER:
+			len = sizeof(flags);
+			if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags,
+								&len) < 0) {
+				ERROR_FAILED(err, "getsockopt(RFCOMM_LM)",
+									errno);
+				return FALSE;
+			}
+			*(va_arg(args, gboolean *)) =
+				(flags & RFCOMM_LM_MASTER) ? TRUE : FALSE;
+			break;
+		case BT_IO_OPT_HANDLE:
+			if (rfcomm_get_info(sock, &handle, dev_class) < 0) {
+				ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
+				return FALSE;
+			}
+			*(va_arg(args, uint16_t *)) = handle;
+			break;
+		case BT_IO_OPT_CLASS:
+			if (rfcomm_get_info(sock, &handle, dev_class) < 0) {
+				ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
+				return FALSE;
+			}
+			memcpy(va_arg(args, uint8_t *), dev_class, 3);
+			break;
+		default:
+			g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+					"Unknown option %d", opt);
+			return FALSE;
+		}
+
+		opt = va_arg(args, int);
+	}
+
+	return TRUE;
+}
+
+static int sco_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
+{
+	struct sco_conninfo info;
+	socklen_t len;
+
+	len = sizeof(info);
+	if (getsockopt(sock, SOL_SCO, SCO_CONNINFO, &info, &len) < 0)
+		return -errno;
+
+	if (handle)
+		*handle = info.hci_handle;
+
+	if (dev_class)
+		memcpy(dev_class, info.dev_class, 3);
+
+	return 0;
+}
+
+static gboolean sco_get(int sock, GError **err, BtIOOption opt1, va_list args)
+{
+	BtIOOption opt = opt1;
+	struct sockaddr_sco src, dst;
+	struct sco_options sco_opt;
+	socklen_t len;
+	uint8_t dev_class[3];
+	uint16_t handle;
+
+	len = sizeof(sco_opt);
+	memset(&sco_opt, 0, len);
+	if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) {
+		ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno);
+		return FALSE;
+	}
+
+	if (!get_peers(sock, (struct sockaddr *) &src,
+				(struct sockaddr *) &dst, sizeof(src), err))
+		return FALSE;
+
+	while (opt != BT_IO_OPT_INVALID) {
+		switch (opt) {
+		case BT_IO_OPT_SOURCE:
+			ba2str(&src.sco_bdaddr, va_arg(args, char *));
+			break;
+		case BT_IO_OPT_SOURCE_BDADDR:
+			bacpy(va_arg(args, bdaddr_t *), &src.sco_bdaddr);
+			break;
+		case BT_IO_OPT_DEST:
+			ba2str(&dst.sco_bdaddr, va_arg(args, char *));
+			break;
+		case BT_IO_OPT_DEST_BDADDR:
+			bacpy(va_arg(args, bdaddr_t *), &dst.sco_bdaddr);
+			break;
+		case BT_IO_OPT_MTU:
+		case BT_IO_OPT_IMTU:
+		case BT_IO_OPT_OMTU:
+			*(va_arg(args, uint16_t *)) = sco_opt.mtu;
+			break;
+		case BT_IO_OPT_HANDLE:
+			if (sco_get_info(sock, &handle, dev_class) < 0) {
+				ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
+				return FALSE;
+			}
+			*(va_arg(args, uint16_t *)) = handle;
+			break;
+		case BT_IO_OPT_CLASS:
+			if (sco_get_info(sock, &handle, dev_class) < 0) {
+				ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
+				return FALSE;
+			}
+			memcpy(va_arg(args, uint8_t *), dev_class, 3);
+			break;
+		default:
+			g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+					"Unknown option %d", opt);
+			return FALSE;
+		}
+
+		opt = va_arg(args, int);
+	}
+
+	return TRUE;
+}
+
+static gboolean get_valist(GIOChannel *io, BtIOType type, GError **err,
+						BtIOOption opt1, va_list args)
+{
+	int sock;
+
+	sock = g_io_channel_unix_get_fd(io);
+
+	switch (type) {
+	case BT_IO_L2RAW:
+	case BT_IO_L2CAP:
+		return l2cap_get(sock, err, opt1, args);
+	case BT_IO_RFCOMM:
+		return rfcomm_get(sock, err, opt1, args);
+	case BT_IO_SCO:
+		return sco_get(sock, err, opt1, args);
+	}
+
+	g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+			"Unknown BtIO type %d", type);
+	return FALSE;
+}
+
+gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data,
+					GDestroyNotify destroy, GError **err)
+{
+	int sock;
+	char c;
+	struct pollfd pfd;
+
+	sock = g_io_channel_unix_get_fd(io);
+
+	memset(&pfd, 0, sizeof(pfd));
+	pfd.fd = sock;
+	pfd.events = POLLOUT;
+
+	if (poll(&pfd, 1, 0) < 0) {
+		ERROR_FAILED(err, "poll", errno);
+		return FALSE;
+	}
+
+	if (!(pfd.revents & POLLOUT)) {
+		int ret;
+		ret = read(sock, &c, 1);
+	}
+
+	accept_add(io, connect, user_data, destroy);
+
+	return TRUE;
+}
+
+gboolean bt_io_set(GIOChannel *io, BtIOType type, GError **err,
+							BtIOOption opt1, ...)
+{
+	va_list args;
+	gboolean ret;
+	struct set_opts opts;
+	int sock;
+
+	va_start(args, opt1);
+	ret = parse_set_opts(&opts, err, opt1, args);
+	va_end(args);
+
+	if (!ret)
+		return ret;
+
+	sock = g_io_channel_unix_get_fd(io);
+
+	switch (type) {
+	case BT_IO_L2RAW:
+	case BT_IO_L2CAP:
+		return l2cap_set(sock, opts.sec_level, opts.imtu, opts.omtu,
+							opts.master, err);
+	case BT_IO_RFCOMM:
+		return rfcomm_set(sock, opts.sec_level, opts.master, err);
+	case BT_IO_SCO:
+		return sco_set(sock, opts.mtu, err);
+	}
+
+	g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+			"Unknown BtIO type %d", type);
+	return FALSE;
+}
+
+gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err,
+							BtIOOption opt1, ...)
+{
+	va_list args;
+	gboolean ret;
+
+	va_start(args, opt1);
+	ret = get_valist(io, type, err, opt1, args);
+	va_end(args);
+
+	return ret;
+}
+
+static GIOChannel *create_io(BtIOType type, gboolean server,
+					struct set_opts *opts, GError **err)
+{
+	int sock;
+	GIOChannel *io;
+
+	switch (type) {
+	case BT_IO_L2RAW:
+		sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
+		if (sock < 0) {
+			ERROR_FAILED(err, "socket(RAW, L2CAP)", errno);
+			return NULL;
+		}
+		if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0) < 0) {
+			ERROR_FAILED(err, "l2cap_bind", errno);
+			return NULL;
+		}
+		if (!l2cap_set(sock, opts->sec_level, 0, 0, -1, err))
+			return NULL;
+		break;
+	case BT_IO_L2CAP:
+		sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+		if (sock < 0) {
+			ERROR_FAILED(err, "socket(SEQPACKET, L2CAP)", errno);
+			return NULL;
+		}
+		if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0) < 0) {
+			ERROR_FAILED(err, "l2cap_bind", errno);
+			return NULL;
+		}
+		if (!l2cap_set(sock, opts->sec_level, opts->imtu, opts->omtu,
+							opts->master, err))
+			return NULL;
+		break;
+	case BT_IO_RFCOMM:
+		sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+		if (sock < 0) {
+			ERROR_FAILED(err, "socket(STREAM, RFCOMM)", errno);
+			return NULL;
+		}
+		if (rfcomm_bind(sock, &opts->src,
+					server ? opts->channel : 0) < 0) {
+			ERROR_FAILED(err, "rfcomm_bind", errno);
+			return NULL;
+		}
+		if (!rfcomm_set(sock, opts->sec_level, opts->master, err))
+			return NULL;
+		break;
+	case BT_IO_SCO:
+		sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+		if (sock < 0) {
+			ERROR_FAILED(err, "socket(SEQPACKET, SCO)", errno);
+			return NULL;
+		}
+		if (sco_bind(sock, &opts->src) < 0) {
+			ERROR_FAILED(err, "sco_bind", errno);
+			return NULL;
+		}
+		if (!sco_set(sock, opts->mtu, err))
+			return NULL;
+		break;
+	default:
+		g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+				"Unknown BtIO type %d", type);
+		return NULL;
+	}
+
+	io = g_io_channel_unix_new(sock);
+
+	g_io_channel_set_close_on_unref(io, TRUE);
+	g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL);
+
+	return io;
+}
+
+GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect,
+				gpointer user_data, GDestroyNotify destroy,
+				GError **gerr, BtIOOption opt1, ...)
+{
+	GIOChannel *io;
+	va_list args;
+	struct set_opts opts;
+	int err, sock;
+	gboolean ret;
+
+	va_start(args, opt1);
+	ret = parse_set_opts(&opts, gerr, opt1, args);
+	va_end(args);
+
+	if (ret == FALSE)
+		return NULL;
+
+	io = create_io(type, FALSE, &opts, gerr);
+	if (io == NULL)
+		return NULL;
+
+	sock = g_io_channel_unix_get_fd(io);
+
+	switch (type) {
+	case BT_IO_L2RAW:
+		err = l2cap_connect(sock, &opts.dst, 0);
+		break;
+	case BT_IO_L2CAP:
+		err = l2cap_connect(sock, &opts.dst, opts.psm);
+		break;
+	case BT_IO_RFCOMM:
+		err = rfcomm_connect(sock, &opts.dst, opts.channel);
+		break;
+	case BT_IO_SCO:
+		err = sco_connect(sock, &opts.dst);
+		break;
+	default:
+		g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+						"Unknown BtIO type %d", type);
+		return NULL;
+	}
+
+	if (err < 0) {
+		g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED,
+				"connect: %s (%d)", strerror(-err), -err);
+		g_io_channel_unref(io);
+		return NULL;
+	}
+
+	connect_add(io, connect, user_data, destroy);
+
+	return io;
+}
+
+GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect,
+				BtIOConfirm confirm, gpointer user_data,
+				GDestroyNotify destroy, GError **err,
+				BtIOOption opt1, ...)
+{
+	GIOChannel *io;
+	va_list args;
+	struct set_opts opts;
+	int sock;
+	gboolean ret;
+
+	if (type == BT_IO_L2RAW) {
+		g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
+				"Server L2CAP RAW sockets not supported");
+		return NULL;
+	}
+
+	va_start(args, opt1);
+	ret = parse_set_opts(&opts, err, opt1, args);
+	va_end(args);
+
+	if (ret == FALSE)
+		return NULL;
+
+	io = create_io(type, TRUE, &opts, err);
+	if (io == NULL)
+		return NULL;
+
+	sock = g_io_channel_unix_get_fd(io);
+
+	if (confirm)
+		setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer,
+							sizeof(opts.defer));
+
+	if (listen(sock, 5) < 0) {
+		ERROR_FAILED(err, "listen", errno);
+		g_io_channel_unref(io);
+		return NULL;
+	}
+
+	server_add(io, connect, confirm, user_data, destroy);
+
+	return io;
+}
+
+GQuark bt_io_error_quark(void)
+{
+	return g_quark_from_static_string("bt-io-error-quark");
+}
+
diff --git a/common/btio.h b/common/btio.h
new file mode 100644
index 0000000..2e50d98
--- /dev/null
+++ b/common/btio.h
@@ -0,0 +1,94 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009  Nokia Corporation
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#ifndef BT_IO_H
+#define BT_IO_H
+
+#include <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_PSM,
+	BT_IO_OPT_MTU,
+	BT_IO_OPT_OMTU,
+	BT_IO_OPT_IMTU,
+	BT_IO_OPT_MASTER,
+	BT_IO_OPT_HANDLE,
+	BT_IO_OPT_CLASS,
+} BtIOOption;
+
+typedef enum {
+	BT_IO_SEC_SDP = 0,
+	BT_IO_SEC_LOW,
+	BT_IO_SEC_MEDIUM,
+	BT_IO_SEC_HIGH,
+} BtIOSecLevel;
+
+typedef void (*BtIOConfirm)(GIOChannel *io, gpointer user_data);
+
+typedef void (*BtIOConnect)(GIOChannel *io, GError *err, gpointer user_data);
+
+gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data,
+					GDestroyNotify destroy, GError **err);
+
+gboolean bt_io_set(GIOChannel *io, BtIOType type, GError **err,
+						BtIOOption opt1, ...);
+
+gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err,
+						BtIOOption opt1, ...);
+
+GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect,
+				gpointer user_data, GDestroyNotify destroy,
+				GError **err, BtIOOption opt1, ...);
+
+GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect,
+				BtIOConfirm confirm, gpointer user_data,
+				GDestroyNotify destroy, GError **err,
+				BtIOOption opt1, ...);
+
+#endif
diff --git a/common/glib-helper.c b/common/glib-helper.c
new file mode 100644
index 0000000..727c55d
--- /dev/null
+++ b/common/glib-helper.c
@@ -0,0 +1,784 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.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/l2cap.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.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;
+
+struct hci_cmd_data {
+	bt_hci_result_t		cb;
+	uint16_t		handle;
+	uint16_t		ocf;
+	gpointer		caller_data;
+};
+
+static gboolean cached_session_expired(gpointer user_data)
+{
+	struct cached_sdp_session *cached = user_data;
+
+	cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached);
+
+	sdp_close(cached->session);
+
+	g_free(cached);
+
+	return FALSE;
+}
+
+static sdp_session_t *get_sdp_session(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	GSList *l;
+
+	for (l = cached_sdp_sessions; l != NULL; l = l->next) {
+		struct cached_sdp_session *c = l->data;
+		sdp_session_t *session;
+
+		if (bacmp(&c->src, src) || bacmp(&c->dst, dst))
+			continue;
+
+		g_source_remove(c->timer);
+
+		session = c->session;
+
+		cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c);
+		g_free(c);
+
+		return session;
+	}
+
+	return sdp_connect(src, dst, SDP_NON_BLOCKING);
+}
+
+static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst,
+				sdp_session_t *session)
+{
+	struct cached_sdp_session *cached;
+
+	cached = g_new0(struct cached_sdp_session, 1);
+
+	bacpy(&cached->src, src);
+	bacpy(&cached->dst, dst);
+
+	cached->session = session;
+
+	cached_sdp_sessions = g_slist_append(cached_sdp_sessions, cached);
+
+	cached->timer = g_timeout_add_seconds(CACHE_TIMEOUT,
+						cached_session_expired,
+						cached);
+}
+
+int set_nonblocking(int fd)
+{
+	long arg;
+
+	arg = fcntl(fd, F_GETFL);
+	if (arg < 0)
+		return -errno;
+
+	/* Return if already nonblocking */
+	if (arg & O_NONBLOCK)
+		return 0;
+
+	arg |= O_NONBLOCK;
+	if (fcntl(fd, F_SETFL, arg) < 0)
+		return -errno;
+
+	return 0;
+}
+
+struct search_context {
+	bdaddr_t		src;
+	bdaddr_t		dst;
+	sdp_session_t		*session;
+	bt_callback_t		cb;
+	bt_destroy_t		destroy;
+	gpointer		user_data;
+	uuid_t			uuid;
+	guint			io_id;
+};
+
+static GSList *context_list = NULL;
+
+static void search_context_cleanup(struct search_context *ctxt)
+{
+	context_list = g_slist_remove(context_list, ctxt);
+
+	if (ctxt->destroy)
+		ctxt->destroy(ctxt->user_data);
+
+	g_free(ctxt);
+}
+
+static void search_completed_cb(uint8_t type, uint16_t status,
+			uint8_t *rsp, size_t size, void *user_data)
+{
+	struct search_context *ctxt = user_data;
+	sdp_list_t *recs = NULL;
+	int scanned, seqlen = 0, bytesleft = size;
+	uint8_t dataType;
+	int err = 0;
+
+	if (status || type != SDP_SVC_SEARCH_ATTR_RSP) {
+		err = -EPROTO;
+		goto done;
+	}
+
+	scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen);
+	if (!scanned || !seqlen)
+		goto done;
+
+	rsp += scanned;
+	bytesleft -= scanned;
+	do {
+		sdp_record_t *rec;
+		int recsize;
+
+		recsize = 0;
+		rec = sdp_extract_pdu(rsp, bytesleft, &recsize);
+		if (!rec)
+			break;
+
+		if (!recsize) {
+			sdp_record_free(rec);
+			break;
+		}
+
+		scanned += recsize;
+		rsp += recsize;
+		bytesleft -= recsize;
+
+		recs = sdp_list_append(recs, rec);
+	} while (scanned < (ssize_t) size && bytesleft > 0);
+
+done:
+	cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session);
+
+	if (ctxt->cb)
+		ctxt->cb(recs, err, ctxt->user_data);
+
+	if (recs)
+		sdp_list_free(recs, (sdp_free_func_t) sdp_record_free);
+
+	search_context_cleanup(ctxt);
+}
+
+static gboolean search_process_cb(GIOChannel *chan,
+			GIOCondition cond, void *user_data)
+{
+	struct search_context *ctxt = user_data;
+	int err = 0;
+
+	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+		err = EIO;
+		goto failed;
+	}
+
+	if (sdp_process(ctxt->session) < 0)
+		goto failed;
+
+	return TRUE;
+
+failed:
+	if (err) {
+		sdp_close(ctxt->session);
+		ctxt->session = NULL;
+
+		if (ctxt->cb)
+			ctxt->cb(NULL, err, ctxt->user_data);
+
+		search_context_cleanup(ctxt);
+	}
+
+	return FALSE;
+}
+
+static gboolean connect_watch(GIOChannel *chan, GIOCondition cond, gpointer user_data)
+{
+	struct search_context *ctxt = user_data;
+	sdp_list_t *search, *attrids;
+	uint32_t range = 0x0000ffff;
+	socklen_t len;
+	int sk, err = 0;
+
+	sk = g_io_channel_unix_get_fd(chan);
+	ctxt->io_id = 0;
+
+	len = sizeof(err);
+	if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
+		err = errno;
+		goto failed;
+	}
+
+	if (err != 0)
+		goto failed;
+
+	if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) {
+		err = EIO;
+		goto failed;
+	}
+
+	search = sdp_list_append(NULL, &ctxt->uuid);
+	attrids = sdp_list_append(NULL, &range);
+	if (sdp_service_search_attr_async(ctxt->session,
+				search, SDP_ATTR_REQ_RANGE, attrids) < 0) {
+		sdp_list_free(attrids, NULL);
+		sdp_list_free(search, NULL);
+		err = EIO;
+		goto failed;
+	}
+
+	sdp_list_free(attrids, NULL);
+	sdp_list_free(search, NULL);
+
+	/* Set callback responsible for update the internal SDP transaction */
+	ctxt->io_id = g_io_add_watch(chan,
+				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+				search_process_cb, ctxt);
+	return FALSE;
+
+failed:
+	sdp_close(ctxt->session);
+	ctxt->session = NULL;
+
+	if (ctxt->cb)
+		ctxt->cb(NULL, -err, ctxt->user_data);
+
+	search_context_cleanup(ctxt);
+
+	return FALSE;
+}
+
+static int create_search_context(struct search_context **ctxt,
+				const bdaddr_t *src, const bdaddr_t *dst,
+				uuid_t *uuid)
+{
+	sdp_session_t *s;
+	GIOChannel *chan;
+
+	if (!ctxt)
+		return -EINVAL;
+
+	s = get_sdp_session(src, dst);
+	if (!s)
+		return -errno;
+
+	*ctxt = g_try_malloc0(sizeof(struct search_context));
+	if (!*ctxt) {
+		sdp_close(s);
+		return -ENOMEM;
+	}
+
+	bacpy(&(*ctxt)->src, src);
+	bacpy(&(*ctxt)->dst, dst);
+	(*ctxt)->session = s;
+	(*ctxt)->uuid = *uuid;
+
+	chan = g_io_channel_unix_new(sdp_get_socket(s));
+	(*ctxt)->io_id = g_io_add_watch(chan,
+				G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+				connect_watch, *ctxt);
+	g_io_channel_unref(chan);
+
+	return 0;
+}
+
+int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst,
+			uuid_t *uuid, bt_callback_t cb, void *user_data,
+			bt_destroy_t destroy)
+{
+	struct search_context *ctxt = NULL;
+	int err;
+
+	if (!cb)
+		return -EINVAL;
+
+	err = create_search_context(&ctxt, src, dst, uuid);
+	if (err < 0)
+		return err;
+
+	ctxt->cb	= cb;
+	ctxt->destroy	= destroy;
+	ctxt->user_data	= user_data;
+
+	context_list = g_slist_append(context_list, ctxt);
+
+	return 0;
+}
+
+int bt_discover_services(const bdaddr_t *src, const bdaddr_t *dst,
+		bt_callback_t cb, void *user_data, bt_destroy_t destroy)
+{
+	uuid_t uuid;
+
+	sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP);
+
+	return bt_search_service(src, dst, &uuid, cb, user_data, destroy);
+}
+
+static int find_by_bdaddr(const void *data, const void *user_data)
+{
+	const struct search_context *ctxt = data, *search = user_data;
+
+	return (bacmp(&ctxt->dst, &search->dst) &&
+					bacmp(&ctxt->src, &search->src));
+}
+
+int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	struct search_context search, *ctxt;
+	GSList *match;
+
+	memset(&search, 0, sizeof(search));
+	bacpy(&search.src, src);
+	bacpy(&search.dst, dst);
+
+	/* Ongoing SDP Discovery */
+	match = g_slist_find_custom(context_list, &search, find_by_bdaddr);
+	if (!match)
+		return -ENODATA;
+
+	ctxt = match->data;
+	if (!ctxt->session)
+		return -ENOTCONN;
+
+	if (ctxt->io_id)
+		g_source_remove(ctxt->io_id);
+
+	if (ctxt->session)
+		sdp_close(ctxt->session);
+
+	search_context_cleanup(ctxt);
+	return 0;
+}
+
+char *bt_uuid2string(uuid_t *uuid)
+{
+	gchar *str;
+	uuid_t uuid128;
+	unsigned int data0;
+	unsigned short data1;
+	unsigned short data2;
+	unsigned short data3;
+	unsigned int data4;
+	unsigned short data5;
+
+	if (!uuid)
+		return NULL;
+
+	switch (uuid->type) {
+	case SDP_UUID16:
+		sdp_uuid16_to_uuid128(&uuid128, uuid);
+		break;
+	case SDP_UUID32:
+		sdp_uuid32_to_uuid128(&uuid128, uuid);
+		break;
+	case SDP_UUID128:
+		memcpy(&uuid128, uuid, sizeof(uuid_t));
+		break;
+	default:
+		/* Type of UUID unknown */
+		return NULL;
+	}
+
+	memcpy(&data0, &uuid128.value.uuid128.data[0], 4);
+	memcpy(&data1, &uuid128.value.uuid128.data[4], 2);
+	memcpy(&data2, &uuid128.value.uuid128.data[6], 2);
+	memcpy(&data3, &uuid128.value.uuid128.data[8], 2);
+	memcpy(&data4, &uuid128.value.uuid128.data[10], 4);
+	memcpy(&data5, &uuid128.value.uuid128.data[14], 2);
+
+	str = g_try_malloc0(MAX_LEN_UUID_STR);
+	if (!str)
+		return NULL;
+
+	sprintf(str, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x",
+			g_ntohl(data0), g_ntohs(data1),
+			g_ntohs(data2), g_ntohs(data3),
+			g_ntohl(data4), g_ntohs(data5));
+
+	return str;
+}
+
+static struct {
+	const char	*name;
+	uint16_t	class;
+} bt_services[] = {
+	{ "vcp",	VIDEO_CONF_SVCLASS_ID		},
+	{ "pbap",	PBAP_SVCLASS_ID			},
+	{ "sap",	SAP_SVCLASS_ID			},
+	{ "ftp",	OBEX_FILETRANS_SVCLASS_ID	},
+	{ "bpp",	BASIC_PRINTING_SVCLASS_ID	},
+	{ "bip",	IMAGING_SVCLASS_ID		},
+	{ "synch",	IRMC_SYNC_SVCLASS_ID		},
+	{ "dun",	DIALUP_NET_SVCLASS_ID		},
+	{ "opp",	OBEX_OBJPUSH_SVCLASS_ID		},
+	{ "fax",	FAX_SVCLASS_ID			},
+	{ "spp",	SERIAL_PORT_SVCLASS_ID		},
+	{ "hsp",	HEADSET_SVCLASS_ID		},
+	{ "hfp",	HANDSFREE_SVCLASS_ID		},
+	{ }
+};
+
+uint16_t bt_name2class(const char *pattern)
+{
+	int i;
+
+	for (i = 0; bt_services[i].name; i++) {
+		if (strcasecmp(bt_services[i].name, pattern) == 0)
+			return bt_services[i].class;
+	}
+
+	return 0;
+}
+
+static inline gboolean is_uuid128(const char *string)
+{
+	return (strlen(string) == 36 &&
+			string[8] == '-' &&
+			string[13] == '-' &&
+			string[18] == '-' &&
+			string[23] == '-');
+}
+
+char *bt_name2string(const char *pattern)
+{
+	uuid_t uuid;
+	uint16_t uuid16;
+	int i;
+
+	/* UUID 128 string format */
+	if (is_uuid128(pattern))
+		return g_strdup(pattern);
+
+	/* Friendly service name format */
+	uuid16 = bt_name2class(pattern);
+	if (uuid16)
+		goto proceed;
+
+	/* HEX format */
+	uuid16 = strtol(pattern, NULL, 16);
+	for (i = 0; bt_services[i].class; i++) {
+		if (bt_services[i].class == uuid16)
+			goto proceed;
+	}
+
+	return NULL;
+
+proceed:
+	sdp_uuid16_create(&uuid, uuid16);
+
+	return bt_uuid2string(&uuid);
+}
+
+int bt_string2uuid(uuid_t *uuid, const char *string)
+{
+	uint32_t data0, data4;
+	uint16_t data1, data2, data3, data5;
+
+	if (is_uuid128(string) &&
+			sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
+				&data0, &data1, &data2, &data3, &data4, &data5) == 6) {
+		uint8_t val[16];
+
+		data0 = g_htonl(data0);
+		data1 = g_htons(data1);
+		data2 = g_htons(data2);
+		data3 = g_htons(data3);
+		data4 = g_htonl(data4);
+		data5 = g_htons(data5);
+
+		memcpy(&val[0], &data0, 4);
+		memcpy(&val[4], &data1, 2);
+		memcpy(&val[6], &data2, 2);
+		memcpy(&val[8], &data3, 2);
+		memcpy(&val[10], &data4, 4);
+		memcpy(&val[14], &data5, 2);
+
+		sdp_uuid128_create(uuid, val);
+
+		return 0;
+	} else {
+		uint16_t class = bt_name2class(string);
+		if (class) {
+			sdp_uuid16_create(uuid, class);
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+gchar *bt_list2string(GSList *list)
+{
+	GSList *l;
+	gchar *str, *tmp;
+
+	if (!list)
+		return NULL;
+
+	str = g_strdup((const gchar *) list->data);
+
+	for (l = list->next; l; l = l->next) {
+		tmp = g_strconcat(str, " " , (const gchar *) l->data, NULL);
+		g_free(str);
+		str = tmp;
+	}
+
+	return str;
+}
+
+GSList *bt_string2list(const gchar *str)
+{
+	GSList *l = NULL;
+	gchar **uuids;
+	int i = 0;
+
+	if (!str)
+		return NULL;
+
+	/* FIXME: eglib doesn't support g_strsplit */
+	uuids = g_strsplit(str, " ", 0);
+	if (!uuids)
+		return NULL;
+
+	while (uuids[i]) {
+		l = g_slist_append(l, uuids[i]);
+		i++;
+	}
+
+	g_free(uuids);
+
+	return l;
+}
+
+static gboolean hci_event_watch(GIOChannel *io,
+			GIOCondition cond, gpointer user_data)
+{
+	unsigned char buf[HCI_MAX_EVENT_SIZE], *body;
+	struct hci_cmd_data *cmd = user_data;
+	evt_cmd_status *evt_status;
+	evt_auth_complete *evt_auth;
+	evt_encrypt_change *evt_enc;
+	hci_event_hdr *hdr;
+	set_conn_encrypt_cp cp;
+	int dd;
+	uint16_t ocf;
+	uint8_t status = HCI_OE_POWER_OFF;
+
+	if (cond & G_IO_NVAL) {
+		cmd->cb(status, cmd->caller_data);
+		return FALSE;
+	}
+
+	if (cond & (G_IO_ERR | G_IO_HUP))
+		goto failed;
+
+	dd = g_io_channel_unix_get_fd(io);
+
+	if (read(dd, buf, sizeof(buf)) < 0)
+		goto failed;
+
+	hdr = (hci_event_hdr *) (buf + 1);
+	body = buf + (1 + HCI_EVENT_HDR_SIZE);
+
+	switch (hdr->evt) {
+	case EVT_CMD_STATUS:
+		evt_status = (evt_cmd_status *) body;
+		ocf = cmd_opcode_ocf(evt_status->opcode);
+		if (ocf != cmd->ocf)
+			return TRUE;
+		switch (ocf) {
+		case OCF_AUTH_REQUESTED:
+		case OCF_SET_CONN_ENCRYPT:
+			if (evt_status->status != 0) {
+				/* Baseband rejected command */
+				status = evt_status->status;
+				goto failed;
+			}
+			break;
+		default:
+			return TRUE;
+		}
+		/* Wait for the next event */
+		return TRUE;
+	case EVT_AUTH_COMPLETE:
+		evt_auth = (evt_auth_complete *) body;
+		if (evt_auth->handle != cmd->handle) {
+			/* Skipping */
+			return TRUE;
+		}
+
+		if (evt_auth->status != 0x00) {
+			status = evt_auth->status;
+			/* Abort encryption */
+			goto failed;
+		}
+
+		memset(&cp, 0, sizeof(cp));
+		cp.handle  = cmd->handle;
+		cp.encrypt = 1;
+
+		cmd->ocf = OCF_SET_CONN_ENCRYPT;
+
+		if (hci_send_cmd(dd, OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT,
+					SET_CONN_ENCRYPT_CP_SIZE, &cp) < 0) {
+			status = HCI_COMMAND_DISALLOWED;
+			goto failed;
+		}
+		/* Wait for encrypt change event */
+		return TRUE;
+	case EVT_ENCRYPT_CHANGE:
+		evt_enc = (evt_encrypt_change *) body;
+		if (evt_enc->handle != cmd->handle)
+			return TRUE;
+
+		/* Procedure finished: reporting status */
+		status = evt_enc->status;
+		break;
+	default:
+		/* Skipping */
+		return TRUE;
+	}
+
+failed:
+	cmd->cb(status, cmd->caller_data);
+	g_io_channel_shutdown(io, TRUE, NULL);
+
+	return FALSE;
+}
+
+int bt_acl_encrypt(const bdaddr_t *src, const bdaddr_t *dst,
+			bt_hci_result_t cb, gpointer user_data)
+{
+	GIOChannel *io;
+	struct hci_cmd_data *cmd;
+	struct hci_conn_info_req *cr;
+	auth_requested_cp cp;
+	struct hci_filter nf;
+	int dd, dev_id, err;
+	char src_addr[18];
+	uint32_t link_mode;
+	uint16_t handle;
+
+	ba2str(src, src_addr);
+	dev_id = hci_devid(src_addr);
+	if (dev_id < 0)
+		return -errno;
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0)
+		return -errno;
+
+	cr = g_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info));
+	cr->type = ACL_LINK;
+	bacpy(&cr->bdaddr, dst);
+
+	err = ioctl(dd, HCIGETCONNINFO, cr);
+	link_mode = cr->conn_info->link_mode;
+	handle = cr->conn_info->handle;
+	g_free(cr);
+
+	if (err < 0) {
+		err = errno;
+		goto failed;
+	}
+
+	if (link_mode & HCI_LM_ENCRYPT) {
+		/* Already encrypted */
+		err = EALREADY;
+		goto failed;
+	}
+
+	memset(&cp, 0, sizeof(cp));
+	cp.handle = htobs(handle);
+
+	if (hci_send_cmd(dd, OGF_LINK_CTL, OCF_AUTH_REQUESTED,
+				AUTH_REQUESTED_CP_SIZE, &cp) < 0) {
+		err = errno;
+		goto failed;
+	}
+
+	cmd = g_new0(struct hci_cmd_data, 1);
+	cmd->handle = handle;
+	cmd->ocf = OCF_AUTH_REQUESTED;
+	cmd->cb	= cb;
+	cmd->caller_data = user_data;
+
+	hci_filter_clear(&nf);
+	hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
+	hci_filter_set_event(EVT_CMD_STATUS, &nf);
+	hci_filter_set_event(EVT_AUTH_COMPLETE, &nf);
+	hci_filter_set_event(EVT_ENCRYPT_CHANGE, &nf);
+
+	if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
+		err = errno;
+		goto failed;
+	}
+
+	io = g_io_channel_unix_new(dd);
+	g_io_channel_set_close_on_unref(io, FALSE);
+	g_io_add_watch_full(io, G_PRIORITY_DEFAULT,
+			G_IO_HUP | G_IO_ERR | G_IO_NVAL | G_IO_IN,
+			hci_event_watch, cmd, g_free);
+	g_io_channel_unref(io);
+
+	return 0;
+
+failed:
+	close(dd);
+
+	return -err;
+}
diff --git a/common/glib-helper.h b/common/glib-helper.h
new file mode 100644
index 0000000..751f3a7
--- /dev/null
+++ b/common/glib-helper.h
@@ -0,0 +1,46 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+int set_nonblocking(int fd);
+
+typedef void (*bt_callback_t) (sdp_list_t *recs, int err, gpointer user_data);
+typedef void (*bt_destroy_t) (gpointer user_data);
+typedef void (*bt_hci_result_t) (uint8_t status, gpointer user_data);
+
+int bt_discover_services(const bdaddr_t *src, const bdaddr_t *dst,
+		bt_callback_t cb, void *user_data, bt_destroy_t destroy);
+
+int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst,
+			uuid_t *uuid, bt_callback_t cb, void *user_data,
+			bt_destroy_t destroy);
+int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst);
+
+gchar *bt_uuid2string(uuid_t *uuid);
+uint16_t bt_name2class(const char *string);
+char *bt_name2string(const char *string);
+int bt_string2uuid(uuid_t *uuid, const char *string);
+gchar *bt_list2string(GSList *list);
+GSList *bt_string2list(const gchar *str);
+
+int bt_acl_encrypt(const bdaddr_t *src, const bdaddr_t *dst,
+			bt_hci_result_t cb, gpointer user_data);
diff --git a/common/logging.c b/common/logging.c
new file mode 100644
index 0000000..6c6f925
--- /dev/null
+++ b/common/logging.c
@@ -0,0 +1,108 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <stdio.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+#include "logging.h"
+
+static volatile int debug_enabled = 0;
+
+static inline void vinfo(const char *format, va_list ap)
+{
+	vsyslog(LOG_INFO, format, ap);
+}
+
+void info(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+
+	vinfo(format, ap);
+
+	va_end(ap);
+}
+
+void error(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+
+	vsyslog(LOG_ERR, format, ap);
+
+	va_end(ap);
+}
+
+void debug(const char *format, ...)
+{
+	va_list ap;
+
+	if (!debug_enabled)
+		return;
+
+	va_start(ap, format);
+
+	vsyslog(LOG_DEBUG, format, ap);
+
+	va_end(ap);
+}
+
+void toggle_debug(void)
+{
+	debug_enabled = (debug_enabled + 1) % 2;
+}
+
+void enable_debug(void)
+{
+	debug_enabled = 1;
+}
+
+void disable_debug(void)
+{
+	debug_enabled = 0;
+}
+
+void start_logging(const char *ident, const char *message, ...)
+{
+	va_list ap;
+
+	openlog(ident, LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
+
+	va_start(ap, message);
+
+	vinfo(message, ap);
+
+	va_end(ap);
+}
+
+void stop_logging(void)
+{
+	closelog();
+}
diff --git a/common/logging.h b/common/logging.h
new file mode 100644
index 0000000..9508733
--- /dev/null
+++ b/common/logging.h
@@ -0,0 +1,38 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+#ifndef __LOGGING_H
+#define __LOGGING_H
+
+void info(const char *format, ...) __attribute__((format(printf, 1, 2)));
+void error(const char *format, ...) __attribute__((format(printf, 1, 2)));
+void debug(const char *format, ...) __attribute__((format(printf, 1, 2)));
+void toggle_debug(void);
+void enable_debug(void);
+void disable_debug(void);
+void start_logging(const char *ident, const char *message, ...);
+void stop_logging(void);
+
+#define DBG(fmt, arg...)  debug("%s: " fmt "\n" , __FUNCTION__ , ## arg)
+
+#endif /* __LOGGING_H */
diff --git a/common/oui.c b/common/oui.c
new file mode 100644
index 0000000..9863973
--- /dev/null
+++ b/common/oui.c
@@ -0,0 +1,109 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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 */
+
+#define OUIFILE "/var/lib/misc/oui.txt"
+
+char *ouitocomp(const char *oui)
+{
+	struct stat st;
+	char *str, *map, *off, *end;
+	int fd;
+
+	fd = open("oui.txt", O_RDONLY);
+	if (fd < 0) {
+		fd = open(OUIFILE, O_RDONLY);
+		if (fd < 0) {
+			fd = open("/usr/share/misc/oui.txt", O_RDONLY);
+			if (fd < 0)
+				return NULL;
+		}
+	}
+
+	if (fstat(fd, &st) < 0) {
+		close(fd);
+		return NULL;
+	}
+
+	str = malloc(128);
+	if (!str) {
+		close(fd);
+		return NULL;
+	}
+
+	memset(str, 0, 128);
+
+	map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+	if (!map || map == MAP_FAILED) {
+		free(str);
+		close(fd);
+		return NULL;
+	}
+
+	off = strstr(map, oui);
+	if (off) {
+		off += 18;
+		end = strpbrk(off, "\r\n");
+		strncpy(str, off, end - off);
+	} else {
+		free(str);
+		str = NULL;
+	}
+
+	munmap(map, st.st_size);
+
+	close(fd);
+
+	return str;
+}
+
+int oui2comp(const char *oui, char *comp, size_t size)
+{
+	char *tmp;
+
+	tmp = ouitocomp(oui);
+	if (!tmp)
+		return -1;
+
+	snprintf(comp, size, "%s", tmp);
+
+	free(tmp);
+
+	return 0;
+}
diff --git a/common/oui.h b/common/oui.h
new file mode 100644
index 0000000..79aa30b
--- /dev/null
+++ b/common/oui.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+char *ouitocomp(const char *oui);
+int oui2comp(const char *oui, char *comp, size_t size);
diff --git a/common/ppoll.h b/common/ppoll.h
new file mode 100644
index 0000000..7d09d44
--- /dev/null
+++ b/common/ppoll.h
@@ -0,0 +1,16 @@
+#ifdef ppoll
+#undef ppoll
+#endif
+
+#define ppoll compat_ppoll
+
+static inline int compat_ppoll(struct pollfd *fds, nfds_t nfds,
+		const struct timespec *timeout, const sigset_t *sigmask)
+{
+	if (timeout == NULL)
+		return poll(fds, nfds, -1);
+	else if (timeout->tv_sec == 0)
+		return poll(fds, nfds, 500);
+	else
+		return poll(fds, nfds, timeout->tv_sec * 1000);
+}
diff --git a/common/sdp-xml.c b/common/sdp-xml.c
new file mode 100644
index 0000000..18473d0
--- /dev/null
+++ b/common/sdp-xml.c
@@ -0,0 +1,799 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-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
+
+#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 "logging.h"
+#include "sdp-xml.h"
+
+#define STRBUFSIZE 1024
+#define MAXINDENT 64
+
+static void convert_raw_data_to_xml(sdp_data_t *value, int indent_level,
+		void *data, void (*appender)(void *, const char *))
+{
+	int i, hex;
+	char buf[STRBUFSIZE];
+	char indent[MAXINDENT];
+	char next_indent[MAXINDENT];
+
+	if (!value)
+		return;
+
+	if (indent_level >= MAXINDENT)
+		indent_level = MAXINDENT - 2;
+
+	for (i = 0; i < indent_level; i++) {
+		indent[i] = '\t';
+		next_indent[i] = '\t';
+	}
+
+	indent[i] = '\0';
+	next_indent[i] = '\t';
+	next_indent[i + 1] = '\0';
+
+	buf[STRBUFSIZE - 1] = '\0';
+
+	switch (value->dtd) {
+	case SDP_DATA_NIL:
+		appender(data, indent);
+		appender(data, "<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 = (char *) 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 = (char *)
+				malloc(sizeof(char) *
+				(value->unitSize + 1 + num_chars_to_escape * 4));
+			for (i = 0, j = 0; i < length; i++) {
+				if (value->val.str[i] == '&') {
+					strBuf[j++] = '&';
+					strBuf[j++] = 'a';
+					strBuf[j++] = 'm';
+					strBuf[j++] = 'p';
+				}
+				else if (value->val.str[i] == '<') {
+					strBuf[j++] = '&';
+					strBuf[j++] = 'l';
+					strBuf[j++] = 't';
+				}
+				else if (value->val.str[i] == '>') {
+					strBuf[j++] = '&';
+					strBuf[j++] = 'g';
+					strBuf[j++] = 't';
+				}
+				else if (value->val.str[i] == '"') {
+					strBuf[j++] = '&';
+					strBuf[j++] = 'q';
+					strBuf[j++] = 'u';
+					strBuf[j++] = 'o';
+					strBuf[j++] = 't';
+				}
+				else if (value->val.str[i] == '\0') {
+					strBuf[j++] = ' ';
+				} else {
+					strBuf[j++] = value->val.str[i];
+				}
+			}
+
+			strBuf[j] = '\0';
+		}
+
+		appender(data, "value=\"");
+		appender(data, strBuf);
+		appender(data, "\" />\n");
+		free(strBuf);
+		break;
+	}
+
+	case SDP_URL_STR8:
+	case SDP_URL_STR16:
+	case SDP_URL_STR32:
+	{
+		char *strBuf;
+
+		appender(data, indent);
+		appender(data, "<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 = (struct conversion_data *) data;
+	sdp_data_t *value = (sdp_data_t *) 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);
+
+	debug("URL size %d length %d: -->%s<--", ret->unitSize, length, url);
+
+	free(url);
+
+	return ret;
+}
+
+sdp_data_t *sdp_xml_parse_text(const char *data, char encoding)
+{
+	uint8_t dtd = SDP_TEXT_STR8;
+	char *text;
+	uint32_t length;
+	sdp_data_t *ret;
+
+	text = sdp_xml_parse_string_decode(data, encoding, &length);
+
+	if (length > UCHAR_MAX)
+		dtd = SDP_TEXT_STR16;
+
+	ret = sdp_data_alloc_with_length(dtd, text, length);
+
+	debug("Text size %d length %d: -->%s<--", ret->unitSize, length, text);
+
+	free(text);
+
+	return ret;
+}
+
+sdp_data_t *sdp_xml_parse_nil(const char *data)
+{
+	return sdp_data_alloc(SDP_DATA_NIL, 0);
+}
+
+#define DEFAULT_XML_DATA_SIZE 1024
+
+struct sdp_xml_data *sdp_xml_data_alloc()
+{
+	struct sdp_xml_data *elem;
+
+	elem = malloc(sizeof(struct sdp_xml_data));
+	if (!elem)
+		return NULL;
+
+	memset(elem, 0, sizeof(struct sdp_xml_data));
+
+	/* Null terminate the text */
+	elem->size = DEFAULT_XML_DATA_SIZE;
+	elem->text = malloc(DEFAULT_XML_DATA_SIZE);
+	elem->text[0] = '\0';
+
+	return elem;
+}
+
+void sdp_xml_data_free(struct sdp_xml_data *elem)
+{
+	if (elem->data)
+		sdp_data_free(elem->data);
+
+	if (elem->name)
+		free(elem->name);
+
+	if (elem->text)
+
+		free(elem->text);
+	free(elem);
+}
+
+struct sdp_xml_data *sdp_xml_data_expand(struct sdp_xml_data *elem)
+{
+	char *newbuf;
+
+	newbuf = malloc(elem->size * 2);
+	if (!newbuf)
+		return NULL;
+
+	memcpy(newbuf, elem->text, elem->size);
+	elem->size *= 2;
+	free(elem->text);
+
+	elem->text = newbuf;
+
+	return elem;
+}
+
+sdp_data_t *sdp_xml_parse_datatype(const char *el, struct sdp_xml_data *elem,
+							sdp_record_t *record)
+{
+	const char *data = elem->text;
+
+	if (!strcmp(el, "boolean"))
+		return sdp_xml_parse_int(data, SDP_BOOL);
+	else if (!strcmp(el, "uint8"))
+		return sdp_xml_parse_int(data, SDP_UINT8);
+	else if (!strcmp(el, "uint16"))
+		return sdp_xml_parse_int(data, SDP_UINT16);
+	else if (!strcmp(el, "uint32"))
+		return sdp_xml_parse_int(data, SDP_UINT32);
+	else if (!strcmp(el, "uint64"))
+		return sdp_xml_parse_int(data, SDP_UINT64);
+	else if (!strcmp(el, "uint128"))
+		return sdp_xml_parse_int(data, SDP_UINT128);
+	else if (!strcmp(el, "int8"))
+		return sdp_xml_parse_int(data, SDP_INT8);
+	else if (!strcmp(el, "int16"))
+		return sdp_xml_parse_int(data, SDP_INT16);
+	else if (!strcmp(el, "int32"))
+		return sdp_xml_parse_int(data, SDP_INT32);
+	else if (!strcmp(el, "int64"))
+		return sdp_xml_parse_int(data, SDP_INT64);
+	else if (!strcmp(el, "int128"))
+		return sdp_xml_parse_int(data, SDP_INT128);
+	else if (!strcmp(el, "uuid"))
+		return sdp_xml_parse_uuid(data, record);
+	else if (!strcmp(el, "url"))
+		return sdp_xml_parse_url(data);
+	else if (!strcmp(el, "text"))
+		return sdp_xml_parse_text(data, elem->type);
+	else if (!strcmp(el, "nil"))
+		return sdp_xml_parse_nil(data);
+
+	return NULL;
+}
diff --git a/common/sdp-xml.h b/common/sdp-xml.h
new file mode 100644
index 0000000..191f9df
--- /dev/null
+++ b/common/sdp-xml.h
@@ -0,0 +1,59 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-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
+ *
+ */
+
+
+#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/common/test_textfile.c b/common/test_textfile.c
new file mode 100644
index 0000000..29ca493
--- /dev/null
+++ b/common/test_textfile.c
@@ -0,0 +1,188 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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/common/textfile.c b/common/textfile.c
new file mode 100644
index 0000000..a755ab7
--- /dev/null
+++ b/common/textfile.c
@@ -0,0 +1,469 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+
+#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"
+
+#ifndef HAVE_FDATASYNC
+#define fdatasync fsync
+#endif
+
+int create_dirs(const char *filename, const mode_t mode)
+{
+	struct stat st;
+	char dir[PATH_MAX + 1], *prev, *next;
+	int err;
+
+	err = stat(filename, &st);
+	if (!err && S_ISREG(st.st_mode))
+		return 0;
+
+	memset(dir, 0, PATH_MAX + 1);
+	strcat(dir, "/");
+
+	prev = strchr(filename, '/');
+
+	while (prev) {
+		next = strchr(prev + 1, '/');
+		if (!next)
+			break;
+
+		if (next - prev == 1) {
+			prev = next;
+			continue;
+		}
+
+		strncat(dir, prev + 1, next - prev);
+		mkdir(dir, mode);
+
+		prev = next;
+	}
+
+	return 0;
+}
+
+int create_file(const char *filename, const mode_t mode)
+{
+	int fd;
+
+	umask(S_IWGRP | S_IWOTH);
+	create_dirs(filename, S_IRUSR | S_IWUSR | S_IXUSR |
+					S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+
+	fd = open(filename, O_RDWR | O_CREAT, mode);
+	if (fd < 0)
+		return fd;
+
+	close(fd);
+
+	return 0;
+}
+
+int create_name(char *buf, size_t size, const char *path, const char *address, const char *name)
+{
+	return snprintf(buf, size, "%s/%s/%s", path, address, name);
+}
+
+static inline char *find_key(char *map, size_t size, const char *key, size_t len, int icase)
+{
+	char *ptr = map;
+	size_t ptrlen = size;
+
+	while (ptrlen > len + 1) {
+		int cmp = (icase) ? strncasecmp(ptr, key, len) : strncmp(ptr, key, len);
+		if (cmp == 0) {
+			if (ptr == map)
+				return ptr;
+
+			if ((*(ptr - 1) == '\r' || *(ptr - 1) == '\n') &&
+							*(ptr + len) == ' ')
+				return ptr;
+		}
+
+		if (icase) {
+			char *p1 = memchr(ptr + 1, tolower(*key), ptrlen - 1);
+			char *p2 = memchr(ptr + 1, toupper(*key), ptrlen - 1);
+
+			if (!p1)
+				ptr = p2;
+			else if (!p2)
+				ptr = p1;
+			else
+				ptr = (p1 < p2) ? p1 : p2;
+		} else
+			ptr = memchr(ptr + 1, *key, ptrlen - 1);
+
+		if (!ptr)
+			return NULL;
+
+		ptrlen = size - (ptr - map);
+	}
+
+	return NULL;
+}
+
+static inline int write_key_value(int fd, const char *key, const char *value)
+{
+	char *str;
+	size_t size;
+	int err = 0;
+
+	size = strlen(key) + strlen(value) + 2;
+
+	str = malloc(size + 1);
+	if (!str)
+		return ENOMEM;
+
+	sprintf(str, "%s %s\n", key, value);
+
+	if (write(fd, str, size) < 0)
+		err = errno;
+
+	free(str);
+
+	return err;
+}
+
+static int write_key(const char *pathname, const char *key, const char *value, int icase)
+{
+	struct stat st;
+	char *map, *off, *end, *str;
+	off_t size, pos; size_t base;
+	int fd, len, err = 0;
+
+	fd = open(pathname, O_RDWR);
+	if (fd < 0)
+		return -errno;
+
+	if (flock(fd, LOCK_EX) < 0) {
+		err = errno;
+		goto close;
+	}
+
+	if (fstat(fd, &st) < 0) {
+		err = errno;
+		goto unlock;
+	}
+
+	size = st.st_size;
+
+	if (!size) {
+		if (value) {
+			pos = lseek(fd, size, SEEK_SET);
+			err = write_key_value(fd, key, value);
+		}
+		goto unlock;
+	}
+
+	map = mmap(NULL, size, PROT_READ | PROT_WRITE,
+					MAP_PRIVATE | MAP_LOCKED, fd, 0);
+	if (!map || map == MAP_FAILED) {
+		err = errno;
+		goto unlock;
+	}
+
+	len = strlen(key);
+	off = find_key(map, size, key, len, icase);
+	if (!off) {
+		if (value) {
+			munmap(map, size);
+			pos = lseek(fd, size, SEEK_SET);
+			err = write_key_value(fd, key, value);
+		}
+		goto unlock;
+	}
+
+	base = off - map;
+
+	end = strpbrk(off, "\r\n");
+	if (!end) {
+		err = EILSEQ;
+		goto unmap;
+	}
+
+	if (value && ((ssize_t) strlen(value) == end - off - len - 1) &&
+			!strncmp(off + len + 1, value, end - off - len - 1))
+		goto unmap;
+
+	len = strspn(end, "\r\n");
+	end += len;
+
+	len = size - (end - map);
+	if (!len) {
+		munmap(map, size);
+		if (ftruncate(fd, base) < 0) {
+			err = errno;
+			goto unlock;
+		}
+		pos = lseek(fd, base, SEEK_SET);
+		if (value)
+			err = write_key_value(fd, key, value);
+
+		goto unlock;
+	}
+
+	if (len < 0 || len > size) {
+		err = EILSEQ;
+		goto unmap;
+	}
+
+	str = malloc(len);
+	if (!str) {
+		err = errno;
+		goto unmap;
+	}
+
+	memcpy(str, end, len);
+
+	munmap(map, size);
+	if (ftruncate(fd, base) < 0) {
+		err = errno;
+		free(str);
+		goto unlock;
+	}
+	pos = lseek(fd, base, SEEK_SET);
+	if (value)
+		err = write_key_value(fd, key, value);
+
+	if (write(fd, str, len) < 0)
+		err = errno;
+
+	free(str);
+
+	goto unlock;
+
+unmap:
+	munmap(map, size);
+
+unlock:
+	flock(fd, LOCK_UN);
+
+close:
+	fdatasync(fd);
+
+	close(fd);
+	errno = err;
+
+	return -err;
+}
+
+static char *read_key(const char *pathname, const char *key, int icase)
+{
+	struct stat st;
+	char *map, *off, *end, *str = NULL;
+	off_t size; size_t len;
+	int fd, err = 0;
+
+	fd = open(pathname, O_RDONLY);
+	if (fd < 0)
+		return NULL;
+
+	if (flock(fd, LOCK_SH) < 0) {
+		err = errno;
+		goto close;
+	}
+
+	if (fstat(fd, &st) < 0) {
+		err = errno;
+		goto unlock;
+	}
+
+	size = st.st_size;
+
+	map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+	if (!map || map == MAP_FAILED) {
+		err = errno;
+		goto unlock;
+	}
+
+	len = strlen(key);
+	off = find_key(map, size, key, len, icase);
+	if (!off) {
+		err = EILSEQ;
+		goto unmap;
+	}
+
+	end = strpbrk(off, "\r\n");
+	if (!end) {
+		err = EILSEQ;
+		goto unmap;
+	}
+
+	str = malloc(end - off - len);
+	if (!str) {
+		err = EILSEQ;
+		goto unmap;
+	}
+
+	memset(str, 0, end - off - len);
+	strncpy(str, off + len + 1, end - off - len - 1);
+
+unmap:
+	munmap(map, size);
+
+unlock:
+	flock(fd, LOCK_UN);
+
+close:
+	close(fd);
+	errno = err;
+
+	return str;
+}
+
+int textfile_put(const char *pathname, const char *key, const char *value)
+{
+	return write_key(pathname, key, value, 0);
+}
+
+int textfile_caseput(const char *pathname, const char *key, const char *value)
+{
+	return write_key(pathname, key, value, 1);
+}
+
+int textfile_del(const char *pathname, const char *key)
+{
+	return write_key(pathname, key, NULL, 0);
+}
+
+int textfile_casedel(const char *pathname, const char *key)
+{
+	return write_key(pathname, key, NULL, 1);
+}
+
+char *textfile_get(const char *pathname, const char *key)
+{
+	return read_key(pathname, key, 0);
+}
+
+char *textfile_caseget(const char *pathname, const char *key)
+{
+	return read_key(pathname, key, 1);
+}
+
+int textfile_foreach(const char *pathname,
+		void (*func)(char *key, char *value, void *data), void *data)
+{
+	struct stat st;
+	char *map, *off, *end, *key, *value;
+	off_t size; size_t len;
+	int fd, err = 0;
+
+	fd = open(pathname, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+
+	if (flock(fd, LOCK_SH) < 0) {
+		err = errno;
+		goto close;
+	}
+
+	if (fstat(fd, &st) < 0) {
+		err = errno;
+		goto unlock;
+	}
+
+	size = st.st_size;
+
+	map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+	if (!map || map == MAP_FAILED) {
+		err = errno;
+		goto unlock;
+	}
+
+	off = map;
+
+	while (1) {
+		end = strpbrk(off, " ");
+		if (!end) {
+			err = EILSEQ;
+			break;
+		}
+
+		len = end - off;
+
+		key = malloc(len + 1);
+		if (!key) {
+			err = errno;
+			break;
+		}
+
+		memset(key, 0, len + 1);
+		memcpy(key, off, len);
+
+		off = end + 1;
+
+		end = strpbrk(off, "\r\n");
+		if (!end) {
+			err = EILSEQ;
+			free(key);
+			break;
+		}
+
+		len = end - off;
+
+		value = malloc(len + 1);
+		if (!value) {
+			err = errno;
+			free(key);
+			break;
+		}
+
+		memset(value, 0, len + 1);
+		memcpy(value, off, len);
+
+		func(key, value, data);
+
+		free(key);
+		free(value);
+
+		off = end + 1;
+	}
+
+	munmap(map, size);
+
+unlock:
+	flock(fd, LOCK_UN);
+
+close:
+	close(fd);
+	errno = err;
+
+	return 0;
+}
diff --git a/common/textfile.h b/common/textfile.h
new file mode 100644
index 0000000..a49e249
--- /dev/null
+++ b/common/textfile.h
@@ -0,0 +1,42 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+#ifndef __TEXTFILE_H
+#define __TEXTFILE_H
+
+int create_dirs(const char *filename, const mode_t mode);
+int create_file(const char *filename, const mode_t mode);
+int create_name(char *buf, size_t size, const char *path,
+				const char *address, const char *name);
+
+int textfile_put(const char *pathname, const char *key, const char *value);
+int textfile_caseput(const char *pathname, const char *key, const char *value);
+int textfile_del(const char *pathname, const char *key);
+int textfile_casedel(const char *pathname, const char *key);
+char *textfile_get(const char *pathname, const char *key);
+char *textfile_caseget(const char *pathname, const char *key);
+
+int textfile_foreach(const char *pathname,
+		void (*func)(char *key, char *value, void *data), void *data);
+
+#endif /* __TEXTFILE_H */
diff --git a/common/uinput.h b/common/uinput.h
new file mode 100644
index 0000000..230dfb7
--- /dev/null
+++ b/common/uinput.h
@@ -0,0 +1,724 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+ *
+ */
+
+#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/compat/Makefile.am b/compat/Makefile.am
new file mode 100644
index 0000000..58254ad
--- /dev/null
+++ b/compat/Makefile.am
@@ -0,0 +1,47 @@
+
+bin_PROGRAMS =
+man_MANS =
+
+if HIDD
+bin_PROGRAMS += hidd
+
+hidd_SOURCES = hidd.c hidd.h sdp.h sdp.c fakehid.c
+
+hidd_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@ -lm
+
+if MANPAGES
+man_MANS += hidd.1
+endif
+endif
+
+if PAND
+bin_PROGRAMS += pand
+
+pand_SOURCES = pand.c pand.h bnep.c sdp.h sdp.c
+
+pand_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@
+
+if MANPAGES
+man_MANS += pand.1
+endif
+endif
+
+if DUND
+bin_PROGRAMS += dund
+
+dund_SOURCES = dund.c dund.h lib.h sdp.h sdp.c dun.c msdun.c
+
+dund_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@
+
+if MANPAGES
+man_MANS += dund.1
+endif
+endif
+
+AM_CFLAGS = @BLUEZ_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common
+
+EXTRA_DIST = fakehid.txt hidd.1 pand.1 dund.1
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/compat/bnep.c b/compat/bnep.c
new file mode 100644
index 0000000..0498c78
--- /dev/null
+++ b/compat/bnep.c
@@ -0,0 +1,323 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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/l2cap.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++) {
+		printf("%s %s %s\n", ci[i].device,
+			batostr((bdaddr_t *) ci[i].dst),
+			bnep_svc2str(ci[i].role));
+	}
+	return 0;
+}
+
+int bnep_kill_connection(uint8_t *dst)
+{
+	struct bnep_conndel_req req;
+
+	memcpy(req.dst, dst, ETH_ALEN);
+	req.flags = 0;
+	if (ioctl(ctl, bnepconndel, &req)) {
+		perror("Failed to kill connection");
+		return -1;
+	}
+	return 0;
+}
+
+int bnep_kill_all_connections(void)
+{
+	struct bnep_connlist_req req;
+	struct bnep_conninfo ci[48];
+	unsigned int i;
+
+	req.cnum = 48;
+	req.ci   = ci;
+	if (ioctl(ctl, bnepgetconnlist, &req)) {
+		perror("Failed to get connection list");
+		return -1;
+	}
+
+	for (i = 0; i < req.cnum; i++) {
+		struct bnep_conndel_req req;
+		memcpy(req.dst, ci[i].dst, ETH_ALEN);
+		req.flags = 0;
+		ioctl(ctl, bnepconndel, &req);
+	}
+	return 0;
+}
+
+static int bnep_connadd(int sk, uint16_t role, char *dev)
+{
+	struct bnep_connadd_req req;
+
+	strncpy(req.device, dev, 16);
+	req.device[15] = '\0';
+	req.sock = sk;
+	req.role = role;
+	if (ioctl(ctl, bnepconnadd, &req))
+		return -1;
+	strncpy(dev, req.device, 16);
+	return 0;
+}
+
+struct __service_16 { 
+	uint16_t dst;
+	uint16_t src;
+} __attribute__ ((packed));
+
+struct __service_32 { 
+	uint16_t unused1;
+	uint16_t dst;
+	uint16_t unused2;
+	uint16_t src;
+} __attribute__ ((packed));
+
+struct __service_128 { 
+	uint16_t unused1;
+	uint16_t dst;
+	uint16_t unused2[8];
+	uint16_t src;
+	uint16_t unused3[7];
+} __attribute__ ((packed));
+
+int bnep_accept_connection(int sk, uint16_t role, char *dev)
+{
+	struct bnep_setup_conn_req *req;
+	struct bnep_control_rsp *rsp;
+	unsigned char pkt[BNEP_MTU];
+	ssize_t r;
+
+	r = recv(sk, pkt, BNEP_MTU, 0);
+	if (r <= 0)
+		return -1;
+
+	errno = EPROTO;
+
+	if ((size_t) r < sizeof(*req))
+		return -1;
+
+	req = (void *) pkt;
+	if (req->type != BNEP_CONTROL || req->ctrl != BNEP_SETUP_CONN_REQ)
+		return -1;
+
+	/* FIXME: Check role UUIDs */
+
+	rsp = (void *) pkt;
+	rsp->type = BNEP_CONTROL;
+	rsp->ctrl = BNEP_SETUP_CONN_RSP;
+	rsp->resp = htons(BNEP_SUCCESS);
+	if (send(sk, rsp, sizeof(*rsp), 0) < 0)
+		return -1;
+
+	return bnep_connadd(sk, role, dev);
+}
+
+/* Create BNEP connection 
+ * sk      - Connect L2CAP socket
+ * role    - Local role
+ * service - Remote service
+ * dev     - Network device (contains actual dev name on return)
+ */
+int bnep_create_connection(int sk, uint16_t role, uint16_t svc, char *dev)
+{
+	struct bnep_setup_conn_req *req;
+	struct bnep_control_rsp *rsp;
+	struct __service_16 *s;
+	struct timeval timeo;
+	unsigned char pkt[BNEP_MTU];
+	ssize_t r;
+
+	/* Send request */
+	req = (void *) pkt;
+	req->type = BNEP_CONTROL;
+	req->ctrl = BNEP_SETUP_CONN_REQ;
+	req->uuid_size = 2;	/* 16bit UUID */
+
+	s = (void *) req->service;
+	s->dst = htons(svc);
+	s->src = htons(role);
+
+	memset(&timeo, 0, sizeof(timeo));
+	timeo.tv_sec = 30;
+
+	setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+
+	if (send(sk, pkt, sizeof(*req) + sizeof(*s), 0) < 0)
+		return -1;
+
+receive:
+	/* Get response */
+	r = recv(sk, pkt, BNEP_MTU, 0);
+	if (r <= 0)
+		return -1;
+
+	memset(&timeo, 0, sizeof(timeo));
+	timeo.tv_sec = 0;
+
+	setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+
+	errno = EPROTO;
+
+	if ((size_t) r < sizeof(*rsp))
+		return -1;
+
+	rsp = (void *) pkt;
+	if (rsp->type != BNEP_CONTROL)
+		return -1;
+
+	if (rsp->ctrl != BNEP_SETUP_CONN_RSP)
+		goto receive;
+
+	r = ntohs(rsp->resp);
+
+	switch (r) {
+	case BNEP_SUCCESS:
+		break;
+
+	case BNEP_CONN_INVALID_DST:
+	case BNEP_CONN_INVALID_SRC:
+	case BNEP_CONN_INVALID_SVC:
+		errno = EPROTO;
+		return -1;
+
+	case BNEP_CONN_NOT_ALLOWED:
+		errno = EACCES;
+		return -1;
+	}
+
+	return bnep_connadd(sk, role, dev);
+}
diff --git a/compat/dun.c b/compat/dun.c
new file mode 100644
index 0000000..c74e5ee
--- /dev/null
+++ b/compat/dun.c
@@ -0,0 +1,331 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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);
+	return r;
+}
+
+static int uses_rfcomm(char *path, char *dev)
+{
+	struct dirent *de;
+	DIR   *dir;
+
+	dir = opendir(path);
+	if (!dir)
+		return 0;
+
+	if (chdir(path) < 0)
+		return 0;
+
+	while ((de = readdir(dir)) != NULL) {
+		char link[PATH_MAX + 1];
+		int  len = readlink(de->d_name, link, sizeof(link));
+		if (len > 0) {
+			link[len] = 0;
+			if (strstr(link, dev))
+				return 1;
+		}
+	}
+
+	closedir(dir);
+
+	return 0;
+}
+
+static int find_pppd(int id, pid_t *pid)
+{
+	struct dirent *de;
+	char  path[PATH_MAX + 1];
+	char  dev[10];
+	int   empty = 1;
+	DIR   *dir;
+
+	dir = opendir(PROC_BASE);
+	if (!dir) {
+		perror(PROC_BASE);
+		return -1;
+	}
+
+	sprintf(dev, "rfcomm%d", id);
+
+	*pid = 0;
+	while ((de = readdir(dir)) != NULL) {
+		empty = 0;
+		if (isdigit(de->d_name[0])) {
+			sprintf(path, "%s/%s/fd", PROC_BASE, de->d_name);
+			if (uses_rfcomm(path, dev)) {
+				*pid = atoi(de->d_name);
+				break;
+			}
+		}
+	}
+	closedir(dir);
+
+	if (empty)
+		fprintf(stderr, "%s is empty (not mounted ?)\n", PROC_BASE);
+
+	return *pid != 0;
+}
+
+static int dun_exec(char *tty, char *prog, char **args)
+{
+	int pid = fork();
+	int fd;
+	
+	switch (pid) {
+	case -1:
+		return -1;
+
+	case 0:
+		break;
+
+	default:
+		return pid;
+	}
+
+	setsid();
+
+	/* Close all FDs */
+	for (fd = 3; fd < 20; fd++)
+		close(fd);
+
+	execvp(prog, args);
+
+	syslog(LOG_ERR, "Error while executing %s", prog);
+
+	exit(1);
+}
+
+static int dun_create_tty(int sk, char *tty, int size)
+{
+	struct sockaddr_rc sa;
+	struct stat st;
+	socklen_t alen;
+	int id, try = 30;
+
+	struct rfcomm_dev_req req = {
+		flags:   (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP),
+		dev_id:  -1
+	};
+
+	alen = sizeof(sa);
+	if (getpeername(sk, (struct sockaddr *) &sa, &alen) < 0)
+		return -1;
+	bacpy(&req.dst, &sa.rc_bdaddr);
+
+	alen = sizeof(sa);
+	if (getsockname(sk, (struct sockaddr *) &sa, &alen) < 0)
+		return -1;
+	bacpy(&req.src, &sa.rc_bdaddr);
+	req.channel = sa.rc_channel;
+
+	id = ioctl(sk, RFCOMMCREATEDEV, &req);
+	if (id < 0)
+		return id;
+
+	snprintf(tty, size, "/dev/rfcomm%d", id);
+	while (stat(tty, &st) < 0) {
+		snprintf(tty, size, "/dev/bluetooth/rfcomm/%d", id);
+		if (stat(tty, &st) < 0) {
+			snprintf(tty, size, "/dev/rfcomm%d", id);
+			if (try--) {
+				usleep(100 * 1000);
+				continue;
+			}
+
+			memset(&req, 0, sizeof(req));
+			req.dev_id = id;
+			ioctl(sk, RFCOMMRELEASEDEV, &req);
+
+			return -1;
+		}
+	}
+
+	return id;
+}
+
+int dun_init(void)
+{
+	return 0;
+}
+
+int dun_cleanup(void)
+{
+	return 0;
+}
+
+static int show_conn(struct rfcomm_dev_info *di, unsigned long arg)
+{
+	pid_t pid;
+	
+	if (di->state == BT_CONNECTED &&
+		(di->flags & (1<<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
index 0000000..09fb7f7
--- /dev/null
+++ b/compat/dund.1
@@ -0,0 +1,72 @@
+.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.29.
+.TH BlueZ "1" "February 2003" "DUN daemon" "User Commands"
+.SH NAME
+dund \- BlueZ Bluetooth dial-up networking daemon
+.SH DESCRIPTION
+DUN daemon
+.SH SYNOPSIS
+dund <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
index 0000000..fc8ba30
--- /dev/null
+++ b/compat/dund.c
@@ -0,0 +1,638 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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);
+		sprintf(ch, "%d", channel);
+
+		/* Setup environment */
+		setenv("DUN_BDADDR",  ba, 1);
+		setenv("DUN_CHANNEL", ch, 1);
+
+		if (!dun_open_connection(nsk, pppd, pppd_opts, 0))
+			syslog(LOG_INFO, "New connection from %s", ba);
+
+		close(nsk);
+		exit(0);
+	}
+
+	if (use_sdp)
+		dun_sdp_unregister();
+	return 0;
+}
+
+/* Connect and initiate RFCOMM session
+ * Returns:
+ *   -1 - critical error (exit persist mode)
+ *   1  - non critical error
+ *   0  - success
+ */
+static int create_connection(char *dst, bdaddr_t *bdaddr, int mrouter)
+{
+	struct sockaddr_rc sa;
+	int sk, err = 0, ch;
+
+	if (use_cache && cache.valid && cache.channel) {
+		/* Use cached channel */
+		ch = cache.channel;
+
+	} else if (!channel) {
+		syslog(LOG_INFO, "Searching for %s on %s", mrouter ? "SP" : "LAP", dst);
+
+		if (dun_sdp_search(&src_addr, bdaddr, &ch, mrouter) <= 0)
+			return 0;
+	} else
+		ch = channel;
+
+	syslog(LOG_INFO, "Connecting to %s channel %d", dst, ch);
+
+	sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+	if (sk < 0) {
+		syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)",
+		     strerror(errno), errno);
+		return -1;
+	}
+
+	sa.rc_family  = AF_BLUETOOTH;
+	sa.rc_channel = 0;
+	sa.rc_bdaddr  = src_addr;
+
+	if (bind(sk, (struct sockaddr *) &sa, sizeof(sa)))
+		syslog(LOG_ERR, "Bind failed. %s(%d)", 
+			strerror(errno), errno);
+
+	sa.rc_channel = ch;
+	sa.rc_bdaddr  = *bdaddr;
+
+	if (!connect(sk, (struct sockaddr *) &sa, sizeof(sa)) ) {
+		if (mrouter) {
+			sleep(1);
+			close(sk);
+			return 0;
+		}
+
+		syslog(LOG_INFO, "Connection established");
+
+		if (msdun && ms_dun(sk, 0, msdun) < 0) {
+			syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno);
+			err = 1;
+			goto out;
+		}
+
+		if (!dun_open_connection(sk, pppd, pppd_opts, (persist > 0)))
+			err = 0;
+		else
+			err = 1;
+	} else {
+		syslog(LOG_ERR, "Connect to %s failed. %s(%d)",
+				dst, strerror(errno), errno);
+		err = 1;
+	}
+
+out:
+	if (use_cache) {
+		if (!err) {
+			/* Succesesful connection, validate cache */
+			strcpy(cache.dst, dst);
+			bacpy(&cache.bdaddr, bdaddr);
+			cache.channel = ch;
+			cache.valid   = use_cache;
+		} else {
+			cache.channel = 0;
+			cache.valid--;
+		}
+	}
+
+	close(sk);
+	return err;
+}
+
+/* Search and connect
+ * Returns:
+ *   -1 - critical error (exit persist mode)
+ *   1  - non critical error
+ *   0  - success
+ */
+static int do_connect(void)
+{
+	inquiry_info *ii;
+	int reconnect = 0;
+	int i, n, r = 0;
+
+	do {
+		if (reconnect)
+			sleep(persist);
+		reconnect = 1;
+
+		if (cache.valid) {
+			/* Use cached bdaddr */
+			r = create_connection(cache.dst, &cache.bdaddr, 0);
+			if (r < 0) {
+				terminate = 1;
+				break;
+			}
+			continue;
+		}
+
+		syslog(LOG_INFO, "Inquiring");
+
+		/* FIXME: Should we use non general LAP here ? */
+
+		ii = NULL;
+		n  = hci_inquiry(src_dev, search_duration, 0, NULL, &ii, 0);
+		if (n < 0) {
+			syslog(LOG_ERR, "Inquiry failed. %s(%d)", strerror(errno), errno);
+			continue;
+		}
+
+		for (i = 0; i < n; i++) {
+			char dst[40];
+			ba2str(&ii[i].bdaddr, dst);
+			
+			r = create_connection(dst, &ii[i].bdaddr, 0);
+			if (r < 0) {
+				terminate = 1;
+				break;
+			}
+		}
+		bt_free(ii);
+	} while (!terminate && persist);
+
+	return r;
+}
+
+static void do_show(void)
+{
+	dun_show_connections();
+}
+
+static void do_kill(char *dst)
+{
+	if (dst) {
+		bdaddr_t ba;
+		str2ba(dst, &ba);
+		dun_kill_connection((void *) &ba);
+	} else
+		dun_kill_all_connections();
+}
+
+static void sig_hup(int sig)
+{
+	return;
+}
+
+static void sig_term(int sig)
+{
+	io_cancel();
+	terminate = 1;
+}
+
+static struct option main_lopts[] = {
+	{ "help",	0, 0, 'h' },
+	{ "listen",	0, 0, 's' },
+	{ "connect",	1, 0, 'c' },
+	{ "search",	2, 0, 'Q' },
+	{ "kill",	1, 0, 'k' },
+	{ "killall",	0, 0, 'K' },
+	{ "channel",	1, 0, 'P' },
+	{ "device",	1, 0, 'i' },
+	{ "nosdp",	0, 0, 'D' },
+	{ "list",	0, 0, 'l' },
+	{ "show",	0, 0, 'l' },
+	{ "nodetach",	0, 0, 'n' },
+	{ "persist",	2, 0, 'p' },
+	{ "auth",	0, 0, 'A' },
+	{ "encrypt",	0, 0, 'E' },
+	{ "secure",	0, 0, 'S' },
+	{ "master",	0, 0, 'M' },
+	{ "cache",	0, 0, 'C' },
+	{ "pppd",	1, 0, 'd' },
+	{ "msdun",	2, 0, 'X' },
+	{ "activesync",	0, 0, 'a' },
+	{ "mrouter",	1, 0, 'm' },
+	{ "dialup",	0, 0, 'u' },
+	{ 0, 0, 0, 0 }
+};
+
+static const char *main_sopts = "hsc:k:Kr:i:lnp::DQ::AESMP:C::P:Xam:u";
+
+static const char *main_help = 
+	"Bluetooth LAP (LAN Access over PPP) daemon version %s\n"
+	"Usage:\n"
+	"\tdund <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())
+		return -1;
+
+	/* Check non daemon modes first */
+	switch (mode) {
+	case SHOW:
+		do_show();
+		return 0;
+
+	case KILL:
+		do_kill(dst);
+		return 0;
+
+	case NONE:
+		printf(main_help, VERSION);
+		return 0;
+	}
+
+	/* Initialize signals */
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_flags   = SA_NOCLDSTOP;
+	sa.sa_handler = SIG_IGN;
+	sigaction(SIGCHLD, &sa, NULL);
+	sigaction(SIGPIPE, &sa, NULL);
+
+	sa.sa_handler = sig_term;
+	sigaction(SIGTERM, &sa, NULL);
+	sigaction(SIGINT,  &sa, NULL);
+
+	sa.sa_handler = sig_hup;
+	sigaction(SIGHUP, &sa, NULL);
+
+	if (detach && daemon(0, 0)) {
+		perror("Can't start daemon");
+		exit(1);
+	}
+
+	openlog("dund", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
+	syslog(LOG_INFO, "Bluetooth DUN daemon version %s", VERSION);
+
+	if (src) {
+		src_dev = hci_devid(src);
+		if (src_dev < 0 || hci_devba(src_dev, &src_addr) < 0) {
+			syslog(LOG_ERR, "Invalid source. %s(%d)", strerror(errno), errno);
+			return -1;
+		}
+	}
+
+	if (dst) {
+		strncpy(cache.dst, dst, sizeof(cache.dst) - 1);
+		str2ba(dst, &cache.bdaddr);
+
+		/* Disable cache invalidation */
+		use_cache = cache.valid = ~0;
+	}
+
+	switch (mode) {
+	case CONNECT:
+		do_connect();
+		break;
+
+	case LISTEN:
+		do_listen();
+		break;
+	}
+
+	return 0;
+}
diff --git a/compat/dund.h b/compat/dund.h
new file mode 100644
index 0000000..0408e09
--- /dev/null
+++ b/compat/dund.h
@@ -0,0 +1,40 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#define DUN_CONFIG_DIR	"/etc/bluetooth/dun"
+
+#define DUN_DEFAULT_CHANNEL	1
+
+#define DUN_MAX_PPP_OPTS	40
+
+int dun_init(void);
+int dun_cleanup(void);
+
+int dun_show_connections(void);
+int dun_kill_connection(uint8_t *dst);
+int dun_kill_all_connections(void);
+
+int dun_open_connection(int sk, char *pppd, char **pppd_opts, int wait);
+
+int ms_dun(int fd, int server, int timeo);
diff --git a/compat/fakehid.c b/compat/fakehid.c
new file mode 100644
index 0000000..438185d
--- /dev/null
+++ b/compat/fakehid.c
@@ -0,0 +1,669 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+
+#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/fakehid.txt b/compat/fakehid.txt
new file mode 100644
index 0000000..000d0ee
--- /dev/null
+++ b/compat/fakehid.txt
@@ -0,0 +1,134 @@
+EPox Presenter
+==============
+
+# hcitool inq
+Inquiring ...
+        00:04:61:aa:bb:cc       clock offset: 0x1ded    class: 0x004000
+
+# hcitool info 00:04:61:aa:bb:cc
+Requesting information ...
+        BD Address:  00:04:61:aa:bb:cc
+        OUI Company: EPOX Computer Co., Ltd. (00-04-61)
+        Device Name: EPox BT-PM01B aabbcc
+        LMP Version: 1.1 (0x1) LMP Subversion: 0xf78
+        Manufacturer: Cambridge Silicon Radio (10)
+        Features: 0xff 0xff 0x0f 0x00 0x00 0x00 0x00 0x00
+                <3-slot packets> <5-slot packets> <encryption> <slot offset> 
+                <timing accuracy> <role switch> <hold mode> <sniff mode> 
+                <park state> <RSSI> <channel quality> <SCO link> <HV2 packets> 
+                <HV3 packets> <u-law log> <A-law log> <CVSD> <paging scheme> 
+                <power control> <transparent SCO> 
+
+# sdptool records --raw 00:04:61:aa:bb:cc
+Sequence
+        Attribute 0x0000 - ServiceRecordHandle
+                UINT32 0x00010000
+        Attribute 0x0001 - ServiceClassIDList
+                Sequence
+                        UUID16 0x1101 - SerialPort
+        Attribute 0x0004 - ProtocolDescriptorList
+                Sequence
+                        Sequence
+                                UUID16 0x0100 - L2CAP
+                        Sequence
+                                UUID16 0x0003 - RFCOMM
+                                UINT8 0x01
+        Attribute 0x0100
+                String Cable Replacement
+
+
+J-Three Keyboard
+================
+
+# hcitool inq
+Inquiring ...
+        00:0A:3A:aa:bb:cc       clock offset: 0x3039    class: 0x001f00
+
+# hcitool info 00:0A:3A:aa:bb:cc
+Password:
+Requesting information ...
+        BD Address:  00:0A:3A:aa:bb:cc
+        OUI Company: J-THREE INTERNATIONAL Holding Co., Ltd. (00-0A-3A)
+        Device Name: KEYBOARD
+        LMP Version: 1.1 (0x1) LMP Subversion: 0x2c2
+        Manufacturer: Cambridge Silicon Radio (10)
+        Features: 0xbc 0x06 0x07 0x00 0x00 0x00 0x00 0x00
+                <encryption> <slot offset> <timing accuracy> <role switch> 
+                <sniff mode> <RSSI> <channel quality> <CVSD> <paging scheme> 
+                <power control> 
+
+# sdptool records --raw 00:0A:3A:aa:bb:cc
+Sequence
+        Attribute 0x0000 - ServiceRecordHandle
+                UINT32 0x00010000
+        Attribute 0x0001 - ServiceClassIDList
+                Sequence
+                        UUID16 0x1101 - SerialPort
+        Attribute 0x0004 - ProtocolDescriptorList
+                Sequence
+                        Sequence
+                                UUID16 0x0100 - L2CAP
+                        Sequence
+                                UUID16 0x0003 - RFCOMM
+                                UINT8 0x01
+        Attribute 0x0006 - LanguageBaseAttributeIDList
+                Sequence
+                        UINT16 0x656e
+                        UINT16 0x006a
+                        UINT16 0x0100
+        Attribute 0x0100
+                String SPP slave
+
+
+Celluon Laserkey Keyboard
+=========================
+
+# hcitool inq
+Inquiring ...
+       00:0B:24:aa:bb:cc       clock offset: 0x3ab6    class: 0x400210
+
+# hcitool info 00:0B:24:aa:bb:cc
+Requesting information ...
+       BD Address:  00:0B:24:aa:bb:cc
+       OUI Company: AirLogic (00-0B-24)
+       Device Name: CL800BT
+       LMP Version: 1.1 (0x1) LMP Subversion: 0x291
+       Manufacturer: Cambridge Silicon Radio (10)
+       Features: 0xff 0xff 0x0f 0x00 0x00 0x00 0x00 0x00
+               <3-slot packets> <5-slot packets> <encryption> <slot offset>
+               <timing accuracy> <role switch> <hold mode> <sniff mode>
+               <park state> <RSSI> <channel quality> <SCO link> <HV2 packets>
+               <HV3 packets> <u-law log> <A-law log> <CVSD> <paging scheme>
+               <power control> <transparent SCO>
+
+# sdptool records --raw 00:0B:24:aa:bb:cc
+Sequence
+         Attribute 0x0000 - ServiceRecordHandle
+                 UINT32 0x00010000
+         Attribute 0x0001 - ServiceClassIDList
+                 Sequence
+                         UUID16 0x1101 - SerialPort
+         Attribute 0x0004 - ProtocolDescriptorList
+                 Sequence
+                         Sequence
+                                 UUID16 0x0100 - L2CAP
+                         Sequence
+                                 UUID16 0x0003 - RFCOMM
+                                 UINT8 0x01
+         Attribute 0x0100
+                 String Serial Port
+
+Packet format is as follows (all fields little-endian):
+     0 uint16  magic            # 0x5a5a
+     2 uint32  unknown          # ???
+     6 uint8   action           # 0 = keyup, 1 = keydown, 2 = repeat
+                                # 3, 4, 5, 6 = ??? (Mouse mode)
+     7 uint8   unknown[9]       # ???
+    16 uint8   action2          # ??? same as action
+    17 uint16  x                # Horizontal coordinate
+    19 uint16  y                # Vertical coordinate
+    21 uint16  time             # Some sort of timestamp
+    23 uint8   unknown[5]       # ???
+    28 uint8   key[]            # single byte keycode or 0xff byte
+                                # follwed by special keycode byte.
+    Each packet followed by a checksum byte.
diff --git a/compat/hidd.1 b/compat/hidd.1
new file mode 100644
index 0000000..b186ac2
--- /dev/null
+++ b/compat/hidd.1
@@ -0,0 +1,41 @@
+.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.33.
+.TH HIDD "1" "May 2004" "hidd - Bluetooth HID daemon" "User Commands"
+.SH NAME
+hidd \- Bluetooth HID daemon
+.SH DESCRIPTION
+hidd - Bluetooth HID daemon
+.SS "Usage:"
+.IP
+hidd [options] [commands]
+.SH OPTIONS
+.TP
+\fB\-i\fR <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
index 0000000..212c092
--- /dev/null
+++ b/compat/hidd.c
@@ -0,0 +1,861 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+
+#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));
+
+	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
index 0000000..362815a
--- /dev/null
+++ b/compat/hidd.h
@@ -0,0 +1,30 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+ *
+ */
+
+#define L2CAP_PSM_HIDP_CTRL 0x11
+#define L2CAP_PSM_HIDP_INTR 0x13
+
+int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel);
+int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel);
+int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel);
+int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel);
diff --git a/compat/lib.h b/compat/lib.h
new file mode 100644
index 0000000..d6373a7
--- /dev/null
+++ b/compat/lib.h
@@ -0,0 +1,86 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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
index 0000000..1759ef6
--- /dev/null
+++ b/compat/msdun.c
@@ -0,0 +1,153 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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
index 0000000..4603b8b
--- /dev/null
+++ b/compat/pand.1
@@ -0,0 +1,77 @@
+.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.29.
+.TH BlueZ "1" "February 2003" "PAN daemon" "User Commands"
+.SH NAME
+pand \- BlueZ Bluetooth PAN daemon
+.SH DESCRIPTION
+The pand PAN daemon allows your computer to connect to ethernet
+networks using Bluetooth.
+.SH SYNPOSIS
+pand <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
index 0000000..c3f3933
--- /dev/null
+++ b/compat/pand.c
@@ -0,0 +1,800 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+
+#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)
+		bnep_kill_connection((void *) strtoba(dst));
+	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())
+		return -1;
+
+	/* Check non daemon modes first */
+	switch (mode) {
+	case SHOW:
+		do_show();
+		return 0;
+
+	case KILL:
+		do_kill(dst);
+		return 0;
+
+	case NONE:
+		printf(main_help, VERSION);
+		return 0;
+	}
+
+	/* Initialize signals */
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_flags   = SA_NOCLDSTOP;
+	sa.sa_handler = SIG_IGN;
+	sigaction(SIGCHLD, &sa, NULL);
+	sigaction(SIGPIPE, &sa, NULL);
+
+	sa.sa_handler = sig_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);
+			return -1;
+		}
+	}
+
+	if (pidfile && write_pidfile())
+		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
index 0000000..4819178
--- /dev/null
+++ b/compat/pand.h
@@ -0,0 +1,36 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+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
index 0000000..d411c05
--- /dev/null
+++ b/compat/sdp.c
@@ -0,0 +1,719 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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);
+
+	free(str);
+
+	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);
+
+	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;
+
+	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);
+	} else {
+		pdlist2 = sdp_data_get(rec, 0x0100);
+		if (pdlist2)
+			strncpy(req->name, pdlist2->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
index 0000000..bec58a2
--- /dev/null
+++ b/compat/sdp.h
@@ -0,0 +1,39 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+ *
+ */
+
+#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
index 0000000..85e1dae
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,74 @@
+AC_PREREQ(2.60)
+AC_INIT()
+
+AM_INIT_AUTOMAKE(bluez, 4.47)
+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
+AC_PROG_CC_PIE
+AC_PROG_INSTALL
+AC_PROG_YACC
+AM_PROG_LEX
+
+m4_define([_LT_AC_TAGCONFIG], [])
+m4_ifdef([AC_LIBTOOL_TAGS], [AC_LIBTOOL_TAGS([])])
+
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
+
+GTK_DOC_CHECK
+
+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_NETLINK
+AC_PATH_SNDFILE
+
+AC_ARG_BLUEZ
+
+AC_OUTPUT([
+	Makefile
+	include/Makefile
+	lib/Makefile
+	gdbus/Makefile
+	common/Makefile
+	sbc/Makefile
+	src/Makefile
+	test/Makefile
+	cups/Makefile
+	tools/Makefile
+	client/Makefile
+	rfcomm/Makefile
+	compat/Makefile
+	plugins/Makefile
+	network/Makefile
+	serial/Makefile
+	input/Makefile
+	audio/Makefile
+	scripts/Makefile
+	scripts/bluetooth.rules
+	doc/Makefile
+	doc/version.xml
+	src/bluetoothd.8
+	src/hcid.conf.5
+	bluez.pc
+])
diff --git a/cups/Makefile.am b/cups/Makefile.am
new file mode 100644
index 0000000..70a8ba8
--- /dev/null
+++ b/cups/Makefile.am
@@ -0,0 +1,16 @@
+
+if CUPS
+cupsdir = $(libdir)/cups/backend
+
+cups_PROGRAMS = bluetooth
+
+bluetooth_SOURCES = main.c cups.h sdp.c spp.c hcrp.c
+
+bluetooth_LDADD = @GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+endif
+
+AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/cups/cups.h b/cups/cups.h
new file mode 100644
index 0000000..2cbac98
--- /dev/null
+++ b/cups/cups.h
@@ -0,0 +1,38 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+ *
+ */
+
+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
index 0000000..155236d
--- /dev/null
+++ b/cups/hcrp.c
@@ -0,0 +1,353 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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
index 0000000..2fbfe7e
--- /dev/null
+++ b/cups/main.c
@@ -0,0 +1,786 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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")) {
+				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 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);
+}
+
+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("network %s \"Unknown\" \"%s (Bluetooth)\"", uri, escaped);
+	if (id != NULL)
+		printf(" \"%s\"\n", id);
+	else
+		printf("\n");
+	g_free(escaped);
+	g_free(uri);
+}
+
+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 (dbus_message_get_args(adapter_reply, NULL, DBUS_TYPE_OBJECT_PATH, &adapter, DBUS_TYPE_INVALID) == FALSE)
+			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;
+	} else {
+		if (dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID) == FALSE)
+			return;
+	}
+
+	id = device_get_ieee1284_id(adapter, object_path);
+	add_device_to_list(name, bdaddr, id);
+	g_free(id);
+}
+
+static void discovery_completed(void)
+{
+	GSList *l;
+
+	for (l = device_list; l != NULL; l = l->next) {
+		struct cups_device *device = (struct cups_device *) l->data;
+
+		if (device->name == NULL)
+			device->name = g_strdelimit(g_strdup(device->bdaddr), ":", '-');
+		/* Give another try to getting an ID for the device */
+		if (device->id == NULL)
+			remote_device_found(NULL, device->bdaddr, device->name);
+		print_printer_details(device->name, device->bdaddr, device->id);
+		g_free(device->name);
+		g_free(device->bdaddr);
+		g_free(device->id);
+		g_free(device);
+	}
+
+	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 = (struct cups_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 (&error != NULL && 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")) {
+		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);
+		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 (&error != NULL && 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 (&error != NULL && 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);
+
+	dbus_connection_unref(conn);
+
+	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);
+
+	/* 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;
+	}
+
+	if (argc < 6 || argc > 7) {
+		fprintf(stderr, "Usage: bluetooth job-id user title copies options [file]\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
index 0000000..8bf6d32
--- /dev/null
+++ b/cups/sdp.c
@@ -0,0 +1,119 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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
index 0000000..3370218
--- /dev/null
+++ b/cups/spp.c
@@ -0,0 +1,118 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 0000000..6277dcc
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,31 @@
+
+DOC_MODULE = bluez
+
+DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml
+
+DOC_SOURCE_DIR = ../src
+
+SCAN_OPTIONS = --rebuild-sections
+
+MKDB_OPTIONS = --sgml-mode --output-format=xml --tmpl-dir=. \
+					--source-suffixes=c,h
+
+MKTMPL_OPTIONS = --output-dir=.
+
+HFILE_GLOB = $(top_srcdir)/src/*.h
+CFILE_GLOB = $(top_srcdir)/src/*.c
+
+IGNORE_HFILES =
+
+MAINTAINERCLEANFILES = Makefile.in \
+	$(DOC_MODULE).types $(DOC_MODULE)-*.txt *.sgml
+
+if ENABLE_GTK_DOC
+include $(top_srcdir)/doc/gtk-doc.make
+else
+EXTRA_DIST = $(DOC_MAIN_SGML_FILE) $(content_files)
+endif
+
+EXTRA_DIST += manager-api.txt adapter-api.txt device-api.txt \
+		service-api.txt agent-api.txt serial-api.txt \
+		network-api.txt input-api.txt audio-api.txt control-api.txt
diff --git a/doc/adapter-api.txt b/doc/adapter-api.txt
new file mode 100644
index 0000000..1e03b4e
--- /dev/null
+++ b/doc/adapter-api.txt
@@ -0,0 +1,270 @@
+BlueZ D-Bus Adapter API description
+***********************************
+
+Copyright (C) 2004-2008  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.DoesNotExist
+					 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
+
+		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()
+
+			Returns list of device 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 the 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 send 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 bascially the same values
+			that we be returned by the GetProperties method
+			from the org.bluez.Device interface. In addition there
+			can be values for the RSSI and the TX power level.
+
+		DeviceDisappeared(string address)
+
+			This signal will be send 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.
+
+		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.
diff --git a/doc/agent-api.txt b/doc/agent-api.txt
new file mode 100644
index 0000000..dbfc08a
--- /dev/null
+++ b/doc/agent-api.txt
@@ -0,0 +1,102 @@
+BlueZ D-Bus Agent API description
+**********************************
+
+Copyright (C) 2004-2008  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 RequestPairingConsent(object device)
+
+			This method gets called when the service daemon
+			needs to confirm an incoming pairing request.
+
+			To accept it should return an empty reply
+			or an error to reject.
+
+			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/audio-api.txt b/doc/audio-api.txt
new file mode 100644
index 0000000..1f09cd5
--- /dev/null
+++ b/doc/audio-api.txt
@@ -0,0 +1,458 @@
+BlueZ D-Bus Audio API description
+*********************************
+
+Copyright (C) 2004-2008  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()
+
+			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 presense 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/bluez-docs.xml b/doc/bluez-docs.xml
new file mode 100644
index 0000000..74a8bd1
--- /dev/null
+++ b/doc/bluez-docs.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
+<!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+  <bookinfo>
+    <title>BlueZ Reference Manual</title>
+    <releaseinfo>Version &version;</releaseinfo>
+    <authorgroup>
+      <author>
+	<firstname>Marcel</firstname>
+	<surname>Holtmann</surname>
+	<affiliation>
+	  <address>
+	    <email>marcel@holtmann.org</email>
+	  </address>
+	</affiliation>
+      </author>
+    </authorgroup>
+
+    <copyright>
+      <year>2002-2008</year>
+      <holder>Marcel Holtmann</holder>
+    </copyright>
+
+    <legalnotice>
+      <para>
+	Permission is granted to copy, distribute and/or modify this
+	document under the terms of the <citetitle>GNU Free
+	Documentation License</citetitle>, Version 1.1 or any later
+	version published by the Free Software Foundation with no
+	Invariant Sections, no Front-Cover Texts, and no Back-Cover
+	Texts. You may obtain a copy of the <citetitle>GNU Free
+	Documentation License</citetitle> from the Free Software
+	Foundation by visiting <ulink type="http"
+	url="http://www.fsf.org">their Web site</ulink> or by writing
+	to:
+
+	<address>
+	  The Free Software Foundation, Inc.,
+	  <street>59 Temple Place</street> - Suite 330,
+	  <city>Boston</city>, <state>MA</state> <postcode>02111-1307</postcode>,
+	  <country>USA</country>
+	</address>
+      </para>
+    </legalnotice>
+  </bookinfo>
+
+  <reference id="manager">
+    <title>Manager interface</title>
+    <para>
+<programlisting><xi:include href="manager-api.txt" parse="text" /></programlisting>
+    </para>
+  </reference>
+
+  <reference id="adapter">
+    <title>Adapter interface</title>
+    <para>
+<programlisting><xi:include href="adapter-api.txt" parse="text" /></programlisting>
+    </para>
+  </reference>
+
+  <reference id="device">
+    <title>Device interface</title>
+    <para>
+<programlisting><xi:include href="device-api.txt" parse="text" /></programlisting>
+    </para>
+  </reference>
+
+  <reference id="agent">
+    <title>Agent interface</title>
+    <para>
+<programlisting><xi:include href="agent-api.txt" parse="text" /></programlisting>
+    </para>
+  </reference>
+
+  <reference id="reference">
+    <title>API Reference</title>
+    <partintro>
+      <para>
+	This part presents the function reference for BlueZ.
+      </para>
+    </partintro>
+  </reference>
+
+  <appendix id="license">
+    <title>License</title>
+    <para>
+<programlisting><xi:include href="../COPYING" parse="text" /></programlisting>
+    </para>
+  </appendix>
+
+  <index>
+    <title>Index</title>
+  </index>
+</book>
diff --git a/doc/control-api.txt b/doc/control-api.txt
new file mode 100644
index 0000000..ffc0fc0
--- /dev/null
+++ b/doc/control-api.txt
@@ -0,0 +1,142 @@
+BlueZ D-Bus Control API description
+***********************************
+
+Copyright (C) 2004-2008  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		void Connect()
+
+			Connect to remote device (CT or TG).
+
+		void Disconnect()
+
+			Disconnect remote device.
+
+		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
+
+		boolean SendPassthrough(avc_operation_id key, boolean state,
+								string op_data)
+
+			Called to send Passthrough commands. ONLY valid if
+			BlueZ device is in CT role.
+
+		boolean SendVendorDependent(string op_data)
+
+			Called to send VendorDependent commands, other than
+			Metadata or Events defined in the AVRCP+Metadata
+			specification.
+
+		void ChangePlayback(string status, uint32 elapsed)
+
+			The status can be "playing", "stopped", "paused",
+			"forward-seek", "reverse-seek" or "error". Elapsed is
+			the position within the track in milliseconds.
+
+		void ChangeTrack(dict metadata)
+
+			Called to send the mandated TrackChange event and
+			potential metadata information.
+
+			Current defined metadata information are represented
+			with the following keys:
+
+				Title		string	(mandatory)
+				Artist		string
+				Album		string
+				Genre		string
+				NumberOfTracks	uint32
+				TrackNumber	uint32
+				TrackDuration	uint32	(in milliseconds)
+
+		void ChangeSetting(string setting, variant value)
+
+			Called to transmit Application Settings, CT Status
+			and the like.
+
+			Currenet defined settings are represented with the
+			following keys:
+
+				Equalizer	off, on
+				Repeat		off, singletrack, alltracks, group
+				Shuffle		off, alltracks, group
+				Scan		off, alltracks, group
+				Battery		normal, warning, critical, external, fullcharge
+				System		powered, unpowered, unplugged
+				Volume		uint8
+
+Signals		Connected()
+
+			Sent when a successful AVRCP connection has been made
+			to the remote device.
+
+		Disconnected()
+
+			Sent when the AVRCP connection to the remote device
+			has been disconnected.
+
+		Passthrough(uint8 key, boolean state, int32 company_id,
+								string op_data)
+
+			Called when Passthrough command is received from
+			connected device.
+
+			NOTE: according to the AV/C Subpanel Spec, company_id
+			and op_data are passed ONLY when the key is
+			"Vendor_Unique", or 0x7E.
+
+			When the key is NOT 0x7E, the signal returns
+			company_id=-1, and zero-length op_data.
+
+		VendorDependentReceived(string op_data)
+
+			Called when VendorDependent message is received from
+			connected device (except for Metadata defined in
+			Bluetooth SIG AVRCP+Metadata spec).
+
+		TrackChanged(dict metadata)
+
+			Called when Metadata is received from connected device.
+			May be multiple meta attribute/element pairs.
+
+		PlaybackChanged(string status, uint32 elapsed)
+
+		SettingChanged(string setting, variant value)
+
+Properties	uint8 SubUnitID [readonly]
+
+			The three-bit Subunit ID from the connected device.
+
+		uint8 SubUnitType [readonly]
+
+			The five-bit Subunit Type from the connected device.
+
+		boolean Connected [readonly]
+
+		array{uint32} CompanyIDs [readonly]
+
+			List of three-byte Company IDs (OUI) supported by the
+			connected device. Note that Bluetooth SIG Company
+			ID (0x001958) is always included.
+
+		array{string} Capabilities [readonly]
+
+			List of Capabilities provided by the connected device.
diff --git a/doc/device-api.txt b/doc/device-api.txt
new file mode 100644
index 0000000..698f9ce
--- /dev/null
+++ b/doc/device-api.txt
@@ -0,0 +1,188 @@
+BlueZ D-Bus Device API description
+**********************************
+
+Copyright (C) 2004-2008  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
+
+		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.
+
+		boolean Paired [readonly]
+
+			Indicates if the remote device is paired.
+
+		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.
+
+		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 adpater 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.
diff --git a/doc/gtk-doc.make b/doc/gtk-doc.make
new file mode 100644
index 0000000..354ffb7
--- /dev/null
+++ b/doc/gtk-doc.make
@@ -0,0 +1,173 @@
+# -*- mode: makefile -*-
+
+####################################
+# Everything below here is generic #
+####################################
+
+if GTK_DOC_USE_LIBTOOL
+GTKDOC_CC = $(LIBTOOL) --mode=compile $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+GTKDOC_LD = $(LIBTOOL) --mode=link $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+else
+GTKDOC_CC = $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+GTKDOC_LD = $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+endif
+
+# We set GPATH here; this gives us semantics for GNU make
+# which are more like other make's VPATH, when it comes to
+# whether a source that is a target of one rule is then
+# searched for in VPATH/GPATH.
+#
+GPATH = $(srcdir)
+
+TARGET_DIR=$(HTML_DIR)/$(DOC_MODULE)
+
+EXTRA_DIST = 				\
+	$(content_files)		\
+	$(HTML_IMAGES)			\
+	$(DOC_MAIN_SGML_FILE)		\
+	$(DOC_MODULE)-sections.txt	\
+	$(DOC_MODULE)-overrides.txt
+
+DOC_STAMPS=scan-build.stamp tmpl-build.stamp sgml-build.stamp html-build.stamp \
+	   $(srcdir)/tmpl.stamp $(srcdir)/sgml.stamp $(srcdir)/html.stamp
+
+SCANOBJ_FILES = 		 \
+	$(DOC_MODULE).args 	 \
+	$(DOC_MODULE).hierarchy  \
+	$(DOC_MODULE).interfaces \
+	$(DOC_MODULE).prerequisites \
+	$(DOC_MODULE).signals
+
+REPORT_FILES = \
+	$(DOC_MODULE)-undocumented.txt \
+	$(DOC_MODULE)-undeclared.txt \
+	$(DOC_MODULE)-unused.txt
+
+CLEANFILES = $(SCANOBJ_FILES) $(REPORT_FILES) $(DOC_STAMPS)
+
+if ENABLE_GTK_DOC
+all-local: html-build.stamp
+else
+all-local:
+endif
+
+docs: html-build.stamp
+
+#### scan ####
+
+scan-build.stamp: $(HFILE_GLOB) $(CFILE_GLOB)
+	@echo 'gtk-doc: Scanning header files'
+	@-chmod -R u+w $(srcdir)
+	cd $(srcdir) && \
+	  gtkdoc-scan --module=$(DOC_MODULE) --source-dir=$(DOC_SOURCE_DIR) --ignore-headers="$(IGNORE_HFILES)" $(SCAN_OPTIONS) $(EXTRA_HFILES)
+	if grep -l '^..*$$' $(srcdir)/$(DOC_MODULE).types > /dev/null 2>&1 ; then \
+	    CC="$(GTKDOC_CC)" LD="$(GTKDOC_LD)" CFLAGS="$(GTKDOC_CFLAGS)" LDFLAGS="$(GTKDOC_LIBS)" gtkdoc-scangobj $(SCANGOBJ_OPTIONS) --module=$(DOC_MODULE) --output-dir=$(srcdir) ; \
+	else \
+	    cd $(srcdir) ; \
+	    for i in $(SCANOBJ_FILES) ; do \
+               test -f $$i || touch $$i ; \
+	    done \
+	fi
+	touch scan-build.stamp
+
+$(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt: scan-build.stamp
+	@true
+
+#### templates ####
+
+tmpl-build.stamp: $(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt
+	@echo 'gtk-doc: Rebuilding template files'
+	@-chmod -R u+w $(srcdir)
+	cd $(srcdir) && gtkdoc-mktmpl --module=$(DOC_MODULE) $(MKTMPL_OPTIONS)
+	touch tmpl-build.stamp
+
+tmpl.stamp: tmpl-build.stamp
+	@true
+
+tmpl/*.sgml:
+	@true
+
+
+#### xml ####
+
+sgml-build.stamp: tmpl.stamp $(HFILE_GLOB) $(CFILE_GLOB) $(DOC_MODULE)-sections.txt $(srcdir)/tmpl/*.sgml $(expand_content_files)
+	@echo 'gtk-doc: Building XML'
+	@-chmod -R u+w $(srcdir)
+	cd $(srcdir) && \
+	gtkdoc-mkdb --module=$(DOC_MODULE) --source-dir=$(DOC_SOURCE_DIR) --output-format=xml --expand-content-files="$(expand_content_files)" --main-sgml-file=$(DOC_MAIN_SGML_FILE) $(MKDB_OPTIONS)
+	touch sgml-build.stamp
+
+sgml.stamp: sgml-build.stamp
+	@true
+
+#### html ####
+
+html-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files)
+	@echo 'gtk-doc: Building HTML'
+	@-chmod -R u+w $(srcdir)
+	rm -rf $(srcdir)/html
+	mkdir $(srcdir)/html
+	cd $(srcdir)/html && gtkdoc-mkhtml $(DOC_MODULE) ../$(DOC_MAIN_SGML_FILE)
+	test "x$(HTML_IMAGES)" = "x" || ( cd $(srcdir) && cp $(HTML_IMAGES) html )
+	@echo 'gtk-doc: Fixing cross-references'
+	cd $(srcdir) && gtkdoc-fixxref --module-dir=html --html-dir=$(HTML_DIR) $(FIXXREF_OPTIONS)
+	touch html-build.stamp
+
+##############
+
+clean-local:
+	rm -f *~ *.bak
+	rm -rf .libs
+
+distclean-local:
+	cd $(srcdir) && \
+	  rm -rf xml $(REPORT_FILES) \
+	         $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt
+
+maintainer-clean-local: clean
+	cd $(srcdir) && rm -rf xml html
+
+install-data-local:
+	-installfiles=`echo $(srcdir)/html/*`; \
+	if test "$$installfiles" = '$(srcdir)/html/*'; \
+	then echo '-- Nothing to install' ; \
+	else \
+	  $(mkinstalldirs) $(DESTDIR)$(TARGET_DIR); \
+	  for i in $$installfiles; do \
+	    echo '-- Installing '$$i ; \
+	    $(INSTALL_DATA) $$i $(DESTDIR)$(TARGET_DIR); \
+	  done; \
+	  echo '-- Installing $(srcdir)/html/index.sgml' ; \
+	  $(INSTALL_DATA) $(srcdir)/html/index.sgml $(DESTDIR)$(TARGET_DIR) || :; \
+	  which gtkdoc-rebase >/dev/null && \
+	    gtkdoc-rebase --relative --dest-dir=$(DESTDIR) --html-dir=$(DESTDIR)$(TARGET_DIR) ; \
+	fi
+	
+
+uninstall-local:
+	rm -f $(DESTDIR)$(TARGET_DIR)/*
+
+#
+# Require gtk-doc when making dist
+#
+if ENABLE_GTK_DOC
+dist-check-gtkdoc:
+else
+dist-check-gtkdoc:
+	@echo "*** gtk-doc must be installed and enabled in order to make dist"
+	@false
+endif
+
+dist-hook: dist-check-gtkdoc dist-hook-local
+	mkdir $(distdir)/tmpl
+	mkdir $(distdir)/xml
+	mkdir $(distdir)/html
+	-cp $(srcdir)/tmpl/*.sgml $(distdir)/tmpl
+	-cp $(srcdir)/xml/*.xml $(distdir)/xml
+	cp $(srcdir)/html/* $(distdir)/html
+	-cp $(srcdir)/$(DOC_MODULE).types $(distdir)/
+	-cp $(srcdir)/$(DOC_MODULE)-sections.txt $(distdir)/
+	cd $(distdir) && rm -f $(DISTCLEANFILES)
+	-gtkdoc-rebase --online --relative --html-dir=$(distdir)/html
+
+.PHONY : dist-hook-local docs
diff --git a/doc/input-api.txt b/doc/input-api.txt
new file mode 100644
index 0000000..38310ce
--- /dev/null
+++ b/doc/input-api.txt
@@ -0,0 +1,44 @@
+BlueZ D-Bus Input API description
+*********************************
+
+Copyright (C) 2004-2008  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
index 0000000..e045cad
--- /dev/null
+++ b/doc/manager-api.txt
@@ -0,0 +1,72 @@
+BlueZ D-Bus Manager API description
+***********************************
+
+Copyright (C) 2004-2008  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()
+
+			Returns list of adapter object paths under /org/bluez
+
+			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/network-api.txt b/doc/network-api.txt
new file mode 100644
index 0000000..7271193
--- /dev/null
+++ b/doc/network-api.txt
@@ -0,0 +1,86 @@
+BlueZ D-Bus Network API description
+***********************************
+
+Copyright (C) 2004-2008  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
+			device name. Examples of the device name are bnep0,
+			bnep1 etc.
+
+			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 Device [readonly]
+
+			Indicates the network interface name when available.
+
+		string UUID [readonly]
+
+			Indicates the connection role when available.
+
+
+Network Hub/Peer/Router hierarchy
+=================
+
+Service		org.bluez
+Interface	org.bluez.network.{Hub, Peer, Router}
+Object path	/org/bluez/{hci0,hci1,...}
+
+Methods		dict GetProperties()
+
+			Returns all properties for the GN/PANU/NAP server. See the
+			properties section for available properties.
+
+		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
+
+Properties	string Name[readwrite]
+
+			The Bluetooth network server name.
+
+		boolean Enable[readwrite]
+
+			Indicates if the server is Enabled/Disabled.
+
+		string Uuid[readonly]
+
+			The Bluetooth network server UUID 128 identification.
diff --git a/doc/node-api.txt b/doc/node-api.txt
new file mode 100644
index 0000000..7a33dab
--- /dev/null
+++ b/doc/node-api.txt
@@ -0,0 +1,28 @@
+BlueZ D-Bus Node API description
+********************************
+
+Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org>
+
+
+Node hierarchy
+==============
+
+Service		org.bluez
+Interface	org.bluez.Node
+Object path	[variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/{node0,...}
+
+Methods		dict GetProperties()
+
+			Returns all properties for the device node. See the
+			properties section for available properties.
+
+			Possible Errors: org.bluez.Error.DoesNotExist
+					 org.bluez.Error.InvalidArguments
+
+Properties	string Name [readonly]
+
+			The name of the node. For example "rfcomm0".
+
+		object Device [readonly]
+
+			The object path of the device this node belongs to.
diff --git a/doc/serial-api.txt b/doc/serial-api.txt
new file mode 100644
index 0000000..b4c806a
--- /dev/null
+++ b/doc/serial-api.txt
@@ -0,0 +1,41 @@
+BlueZ D-Bus Serial API description
+**********************************
+
+Copyright (C) 2004-2008  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
index 0000000..c9489fb
--- /dev/null
+++ b/doc/service-api.txt
@@ -0,0 +1,62 @@
+BlueZ D-Bus Adapter API description
+***********************************
+
+Copyright (C) 2004-2008  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
index 0000000..d78bda9
--- /dev/null
+++ b/doc/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/gdbus/Android.mk b/gdbus/Android.mk
new file mode 100755
index 0000000..c8a646b
--- /dev/null
+++ b/gdbus/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	mainloop.c object.c watch.c
+
+LOCAL_CFLAGS+=-O3 -DNEED_DBUS_WATCH_GET_UNIX_FD
+
+LOCAL_C_INCLUDES:= \
+	$(call include-path-for, glib) \
+	$(call include-path-for, dbus)
+
+LOCAL_MODULE:=libgdbus_static
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/gdbus/Makefile.am b/gdbus/Makefile.am
new file mode 100644
index 0000000..9447555
--- /dev/null
+++ b/gdbus/Makefile.am
@@ -0,0 +1,8 @@
+
+noinst_LTLIBRARIES = libgdbus.la
+
+libgdbus_la_SOURCES = gdbus.h mainloop.c object.c watch.c
+
+AM_CFLAGS = @DBUS_CFLAGS@ @GLIB_CFLAGS@
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h
new file mode 100644
index 0000000..fa618a5
--- /dev/null
+++ b/gdbus/gdbus.h
@@ -0,0 +1,139 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  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
+ *
+ */
+
+#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);
+
+gboolean g_dbus_request_name(DBusConnection *connection, const char *name,
+							DBusError *error);
+
+gboolean g_dbus_check_service(DBusConnection *connection, const char *name);
+
+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 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 struct {
+	const char *name;
+	const char *signature;
+	const char *reply;
+	GDBusMethodFunction function;
+	GDBusMethodFlags flags;
+} GDBusMethodTable;
+
+typedef struct {
+	const char *name;
+	const char *signature;
+	GDBusSignalFlags flags;
+} GDBusSignalTable;
+
+typedef struct {
+	const char *name;
+	const char *type;
+	GDBusPropertyFlags flags;
+} GDBusPropertyTable;
+
+gboolean g_dbus_register_interface(DBusConnection *connection,
+					const char *path, const char *name,
+					GDBusMethodTable *methods,
+					GDBusSignalTable *signals,
+					GDBusPropertyTable *properties,
+					void *user_data,
+					GDBusDestroyFunction destroy);
+gboolean g_dbus_unregister_interface(DBusConnection *connection,
+					const char *path, const char *name);
+
+DBusMessage *g_dbus_create_error(DBusMessage *message, const char *name,
+						const char *format, ...);
+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 *rule, 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
index 0000000..eaba42e
--- /dev/null
+++ b/gdbus/mainloop.c
@@ -0,0 +1,348 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  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 <stdint.h>
+
+#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...)
+
+typedef struct {
+	uint32_t id;
+	DBusTimeout *timeout;
+} timeout_handler_t;
+
+struct watch_info {
+	guint watch_id;
+	GIOChannel *io;
+	DBusConnection *conn;
+};
+
+struct server_info {
+	guint watch_id;
+	GIOChannel *io;
+	DBusServer *server;
+};
+
+struct disconnect_data {
+	GDBusWatchFunction disconnect_cb;
+	void *user_data;
+};
+
+static DBusHandlerResult disconnect_filter(DBusConnection *conn,
+						DBusMessage *msg, void *data)
+{
+	struct disconnect_data *dc_data = data;
+
+	if (dbus_message_is_signal(msg,
+			DBUS_INTERFACE_LOCAL, "Disconnected") == TRUE) {
+		error("Got disconnected from the system message bus");
+		dc_data->disconnect_cb(conn, dc_data->user_data);
+		dbus_connection_unref(conn);
+	}
+
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static gboolean message_dispatch_cb(void *data)
+{
+	DBusConnection *connection = data;
+
+	dbus_connection_ref(connection);
+
+	/* Dispatch messages */
+	while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS);
+
+	dbus_connection_unref(connection);
+
+	return FALSE;
+}
+
+static gboolean watch_func(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+	DBusWatch *watch = data;
+	struct watch_info *info = dbus_watch_get_data(watch);
+	int flags = 0;
+
+	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(watch, flags);
+
+	if (dbus_connection_get_dispatch_status(info->conn) == DBUS_DISPATCH_DATA_REMAINS)
+		g_timeout_add(DISPATCH_TIMEOUT, message_dispatch_cb, info->conn);
+
+	return TRUE;
+}
+
+static dbus_bool_t add_watch(DBusWatch *watch, void *data)
+{
+	GIOCondition cond = G_IO_HUP | G_IO_ERR;
+	DBusConnection *conn = data;
+	struct watch_info *info;
+	int fd, flags;
+
+	if (!dbus_watch_get_enabled(watch))
+		return TRUE;
+
+	info = g_new(struct watch_info, 1);
+
+	fd = dbus_watch_get_unix_fd(watch);
+	info->io = g_io_channel_unix_new(fd);
+	info->conn = dbus_connection_ref(conn);
+
+	dbus_watch_set_data(watch, info, NULL);
+
+	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->watch_id = g_io_add_watch(info->io, cond, watch_func, watch);
+
+	return TRUE;
+}
+
+static void remove_watch(DBusWatch *watch, void *data)
+{
+	struct watch_info *info = dbus_watch_get_data(watch);
+
+	dbus_watch_set_data(watch, NULL, NULL);
+
+	if (info) {
+		g_source_remove(info->watch_id);
+		g_io_channel_unref(info->io);
+		dbus_connection_unref(info->conn);
+		g_free(info);
+	}
+}
+
+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)
+{
+	timeout_handler_t *handler = data;
+
+	/* if not enabled should not be polled by the main loop */
+	if (dbus_timeout_get_enabled(handler->timeout) != TRUE)
+		return FALSE;
+
+	dbus_timeout_handle(handler->timeout);
+
+	return FALSE;
+}
+
+static void timeout_handler_free(void *data)
+{
+	timeout_handler_t *handler = data;
+	if (!handler)
+		return;
+
+	g_source_remove(handler->id);
+	g_free(handler);
+}
+
+static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data)
+{
+	timeout_handler_t *handler;
+
+	if (!dbus_timeout_get_enabled(timeout))
+		return TRUE;
+
+	handler = g_new0(timeout_handler_t, 1);
+
+	handler->timeout = timeout;
+	handler->id = g_timeout_add(dbus_timeout_get_interval(timeout),
+					timeout_handler_dispatch, handler);
+
+	dbus_timeout_set_data(timeout, handler, timeout_handler_free);
+
+	return TRUE;
+}
+
+static void remove_timeout(DBusTimeout *timeout, void *data)
+{
+}
+
+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_cb(DBusConnection *conn,
+				DBusDispatchStatus new_status, void *data)
+{
+	if (!dbus_connection_get_is_connected(conn))
+		return;
+
+	if (new_status == DBUS_DISPATCH_DATA_REMAINS)
+		g_timeout_add(DISPATCH_TIMEOUT, message_dispatch_cb, data);
+}
+
+static 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, conn, NULL);
+
+	dbus_connection_set_dispatch_status_function(conn, dispatch_status_cb,
+								conn, NULL);
+}
+
+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 (name != NULL) {
+		if (dbus_bus_request_name(conn, name,
+				DBUS_NAME_FLAG_DO_NOT_QUEUE, error) !=
+				DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ) {
+			dbus_connection_unref(conn);
+			return NULL;
+		}
+
+		if (error != NULL) {
+			if (dbus_error_is_set(error) == TRUE) {
+				dbus_connection_unref(conn);
+				return NULL;
+			}
+		}
+	}
+
+	setup_dbus_with_main_loop(conn);
+
+	return conn;
+}
+
+gboolean g_dbus_request_name(DBusConnection *connection, const char *name,
+							DBusError *error)
+{
+	return TRUE;
+}
+
+gboolean g_dbus_check_service(DBusConnection *connection, const char *name)
+{
+	DBusMessage *message, *reply;
+	const char **names;
+	int i, count;
+	gboolean result = FALSE;
+
+	message = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
+			DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "ListNames");
+	if (message == NULL) {
+		error("Can't allocate new message");
+		return FALSE;
+	}
+
+	reply = dbus_connection_send_with_reply_and_block(connection,
+							message, -1, NULL);
+
+	dbus_message_unref(message);
+
+	if (reply == NULL) {
+		error("Failed to execute method call");
+		return FALSE;
+	}
+
+	if (dbus_message_get_args(reply, NULL,
+				DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+				&names, &count, DBUS_TYPE_INVALID) == FALSE) {
+		error("Failed to read name list");
+		goto done;
+	}
+
+	for (i = 0; i < count; i++)
+		if (g_str_equal(names[i], name) == TRUE) {
+			result = TRUE;
+			break;
+		}
+
+done:
+	dbus_message_unref(reply);
+
+	return result;
+}
+
+gboolean g_dbus_set_disconnect_function(DBusConnection *connection,
+				GDBusWatchFunction function,
+				void *user_data, DBusFreeFunction destroy)
+{
+	struct disconnect_data *dc_data;
+
+	dc_data = g_new(struct disconnect_data, 1);
+
+	dc_data->disconnect_cb = function;
+	dc_data->user_data = user_data;
+
+	dbus_connection_set_exit_on_disconnect(connection, FALSE);
+
+	if (dbus_connection_add_filter(connection, disconnect_filter,
+						dc_data, g_free) == FALSE) {
+		error("Can't add D-Bus disconnect filter");
+		g_free(dc_data);
+		return FALSE;
+	}
+
+	return TRUE;
+}
diff --git a/gdbus/object.c b/gdbus/object.c
new file mode 100644
index 0000000..3186921
--- /dev/null
+++ b/gdbus/object.c
@@ -0,0 +1,658 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  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 <stdio.h>
+#include <string.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "gdbus.h"
+
+#define info(fmt...)
+#define error(fmt...)
+#define debug(fmt...)
+
+struct generic_data {
+	unsigned int refcount;
+	GSList *interfaces;
+	char *introspect;
+};
+
+struct interface_data {
+	char *name;
+	GDBusMethodTable *methods;
+	GDBusSignalTable *signals;
+	GDBusPropertyTable *properties;
+	void *user_data;
+	GDBusDestroyFunction destroy;
+};
+
+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)
+{
+	GDBusMethodTable *method;
+	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 name=\"%s\">\n", path);
+
+	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 DBusHandlerResult introspect(DBusConnection *connection,
+				DBusMessage *message, struct generic_data *data)
+{
+	DBusMessage *reply;
+
+	if (!dbus_message_has_signature(message, DBUS_TYPE_INVALID_AS_STRING)) {
+		error("Unexpected signature to introspect call");
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	}
+
+	if (!data->introspect)
+		generate_introspection_xml(connection, data,
+						dbus_message_get_path(message));
+
+	reply = dbus_message_new_method_return(message);
+	if (!reply)
+		return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+	dbus_message_append_args(reply, DBUS_TYPE_STRING, &data->introspect,
+					DBUS_TYPE_INVALID);
+
+	dbus_connection_send(connection, reply, NULL);
+
+	dbus_message_unref(reply);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+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)
+		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;
+	GDBusMethodTable *method;
+	const char *interface;
+
+	if (dbus_message_is_method_call(message,
+					DBUS_INTERFACE_INTROSPECTABLE,
+								"Introspect"))
+		return introspect(connection, message, data);
+
+	interface = dbus_message_get_interface(message);
+
+	iface = find_interface(data->interfaces, interface);
+	if (!iface)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	for (method = iface->methods; method &&
+			method->name && method->function; method++) {
+		DBusMessage *reply;
+
+		if (dbus_message_is_method_call(message, iface->name,
+							method->name) == FALSE)
+			continue;
+
+		if (dbus_message_has_signature(message,
+						method->signature) == FALSE)
+			continue;
+
+		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;
+	}
+
+	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)
+		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))
+		goto done;
+
+	if (!data)
+		goto done;
+
+	g_free(data->introspect);
+	data->introspect = NULL;
+
+done:
+	g_free(parent_path);
+}
+
+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->introspect = g_strdup(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE "<node></node>");
+
+	data->refcount = 1;
+
+	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);
+
+	return data;
+}
+
+static void object_path_unref(DBusConnection *connection, const char *path)
+{
+	struct generic_data *data = NULL;
+
+	if (dbus_connection_get_object_path_data(connection, path,
+						(void *) &data) == FALSE)
+		return;
+
+	if (data == NULL)
+		return;
+
+	data->refcount--;
+
+	if (data->refcount > 0)
+		return;
+
+	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;
+	GDBusSignalTable *signal;
+
+	*args = NULL;
+	if (!dbus_connection_get_object_path_data(conn, path,
+					(void *) &data) || !data) {
+		error("dbus_connection_emit_signal: path %s isn't registered",
+				path);
+		return FALSE;
+	}
+
+	iface = find_interface(data->interfaces, interface);
+	if (!iface) {
+		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) {
+		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) {
+		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,
+					GDBusMethodTable *methods,
+					GDBusSignalTable *signals,
+					GDBusPropertyTable *properties,
+					void *user_data,
+					GDBusDestroyFunction destroy)
+{
+	struct generic_data *data;
+	struct interface_data *iface;
+
+	data = object_path_ref(connection, path);
+	if (data == NULL)
+		return FALSE;
+
+	if (find_interface(data->interfaces, name))
+		return FALSE;
+
+	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);
+
+	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;
+	struct interface_data *iface;
+
+	if (!path)
+		return FALSE;
+
+	if (dbus_connection_get_object_path_data(connection, path,
+						(void *) &data) == FALSE)
+		return FALSE;
+
+	if (data == NULL)
+		return FALSE;
+
+	iface = find_interface(data->interfaces, name);
+	if (!iface)
+		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);
+
+	g_free(data->introspect);
+	data->introspect = NULL;
+
+	object_path_unref(connection, path);
+
+	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/watch.c b/gdbus/watch.c
new file mode 100644
index 0000000..c7a4e69
--- /dev/null
+++ b/gdbus/watch.c
@@ -0,0 +1,422 @@
+/*
+ *
+ *  D-Bus helper library
+ *
+ *  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 <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 name_exit_filter(DBusConnection *connection,
+					DBusMessage *message, void *user_data);
+
+static guint listener_id = 0;
+static GSList *name_listeners = NULL;
+
+struct name_callback {
+	GDBusWatchFunction conn_func;
+	GDBusWatchFunction disc_func;
+	void *user_data;
+	guint id;
+};
+
+struct name_data {
+	DBusConnection *connection;
+	char *name;
+	GSList *callbacks;
+	GSList *processed;
+	gboolean lock;
+};
+
+static struct name_data *name_data_find(DBusConnection *connection,
+							const char *name)
+{
+	GSList *current;
+
+	for (current = name_listeners;
+			current != NULL; current = current->next) {
+		struct name_data *data = current->data;
+
+		if (connection != data->connection)
+			continue;
+
+		if (name == NULL || g_str_equal(name, data->name))
+			return data;
+	}
+
+	return NULL;
+}
+
+static struct name_callback *name_callback_find(GSList *callbacks, guint id)
+{
+	GSList *current;
+
+	for (current = callbacks; current != NULL; current = current->next) {
+		struct name_callback *cb = current->data;
+		if (cb->id == id)
+			return cb;
+	}
+
+	return NULL;
+}
+
+static void name_data_call_and_free(struct name_data *data)
+{
+	GSList *l;
+
+	for (l = data->callbacks; l != NULL; l = l->next) {
+		struct name_callback *cb = l->data;
+		if (cb->disc_func)
+			cb->disc_func(data->connection, cb->user_data);
+		g_free(cb);
+	}
+
+	g_slist_free(data->callbacks);
+	g_free(data->name);
+	g_free(data);
+}
+
+static void name_data_free(struct name_data *data)
+{
+	GSList *l;
+
+	for (l = data->callbacks; l != NULL; l = l->next)
+		g_free(l->data);
+
+	g_slist_free(data->callbacks);
+	g_free(data->name);
+	g_free(data);
+}
+
+static int name_data_add(DBusConnection *connection, const char *name,
+						GDBusWatchFunction connect,
+						GDBusWatchFunction disconnect,
+						void *user_data, guint id)
+{
+	int first = 1;
+	struct name_data *data = NULL;
+	struct name_callback *cb = NULL;
+
+	cb = g_new(struct name_callback, 1);
+
+	cb->conn_func = connect;
+	cb->disc_func = disconnect;
+	cb->user_data = user_data;
+	cb->id = id;
+
+	data = name_data_find(connection, name);
+	if (data) {
+		first = 0;
+		goto done;
+	}
+
+	data = g_new0(struct name_data, 1);
+
+	data->connection = connection;
+	data->name = g_strdup(name);
+
+	name_listeners = g_slist_append(name_listeners, data);
+
+done:
+	if (data->lock)
+		data->processed = g_slist_append(data->processed, cb);
+	else
+		data->callbacks = g_slist_append(data->callbacks, cb);
+
+	return first;
+}
+
+static void name_data_remove(DBusConnection *connection,
+					const char *name, guint id)
+{
+	struct name_data *data;
+	struct name_callback *cb = NULL;
+
+	data = name_data_find(connection, name);
+	if (!data)
+		return;
+
+	cb = name_callback_find(data->callbacks, id);
+	if (cb) {
+		data->callbacks = g_slist_remove(data->callbacks, cb);
+		g_free(cb);
+	}
+
+	if (data->callbacks)
+		return;
+
+	name_listeners = g_slist_remove(name_listeners, data);
+	name_data_free(data);
+
+	/* Remove filter if there are no listeners left for the connection */
+	data = name_data_find(connection, NULL);
+	if (!data)
+		dbus_connection_remove_filter(connection,
+						name_exit_filter,
+						NULL);
+}
+
+static gboolean add_match(DBusConnection *connection, const char *name)
+{
+	DBusError err;
+	char match_string[128];
+
+	snprintf(match_string, sizeof(match_string),
+			"interface=%s,member=NameOwnerChanged,arg0=%s",
+			DBUS_INTERFACE_DBUS, name);
+
+	dbus_error_init(&err);
+
+	dbus_bus_add_match(connection, match_string, &err);
+
+	if (dbus_error_is_set(&err)) {
+		error("Adding match rule \"%s\" failed: %s", match_string,
+				err.message);
+		dbus_error_free(&err);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean remove_match(DBusConnection *connection, const char *name)
+{
+	DBusError err;
+	char match_string[128];
+
+	snprintf(match_string, sizeof(match_string),
+			"interface=%s,member=NameOwnerChanged,arg0=%s",
+			DBUS_INTERFACE_DBUS, name);
+
+	dbus_error_init(&err);
+
+	dbus_bus_remove_match(connection, match_string, &err);
+
+	if (dbus_error_is_set(&err)) {
+		error("Removing owner match rule for %s failed: %s",
+				name, err.message);
+		dbus_error_free(&err);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static DBusHandlerResult name_exit_filter(DBusConnection *connection,
+					DBusMessage *message, void *user_data)
+{
+	struct name_data *data;
+	struct name_callback *cb;
+	char *name, *old, *new;
+
+	if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
+							"NameOwnerChanged"))
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	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;
+	}
+
+	data = name_data_find(connection, name);
+	if (!data) {
+		error("Got NameOwnerChanged signal for %s which has no listeners", name);
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	}
+
+	data->lock = TRUE;
+
+	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;
+
+		data->callbacks = g_slist_remove(data->callbacks, cb);
+
+		if (!cb->conn_func || !cb->disc_func) {
+			g_free(cb);
+			continue;
+		}
+
+		data->processed = g_slist_append(data->processed, cb);
+	}
+
+	data->callbacks = data->processed;
+	data->processed = NULL;
+	data->lock = FALSE;
+
+	if (data->callbacks)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	name_listeners = g_slist_remove(name_listeners, data);
+	name_data_free(data);
+
+	/* Remove filter if there no listener left for the connection */
+	data = name_data_find(connection, NULL);
+	if (!data)
+		dbus_connection_remove_filter(connection, name_exit_filter,
+						NULL);
+
+	remove_match(connection, name);
+
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
+				GDBusWatchFunction connect,
+				GDBusWatchFunction disconnect,
+				void *user_data, GDBusDestroyFunction destroy)
+{
+	int first;
+
+	if (!name_data_find(connection, NULL)) {
+		if (!dbus_connection_add_filter(connection,
+					name_exit_filter, NULL, NULL)) {
+			error("dbus_connection_add_filter() failed");
+			return 0;
+		}
+	}
+
+	listener_id++;
+	first = name_data_add(connection, name, connect, disconnect,
+						user_data, listener_id);
+	/* The filter is already added if this is not the first callback
+	 * registration for the name */
+	if (!first)
+		return listener_id;
+
+	if (name) {
+		debug("name_listener_add(%s)", name);
+
+		if (!add_match(connection, name)) {
+			name_data_remove(connection, name, listener_id);
+			return 0;
+		}
+	}
+
+	return listener_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 *rule, GDBusSignalFunction function,
+				void *user_data, GDBusDestroyFunction destroy)
+{
+	return 0;
+}
+
+gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
+{
+	struct name_data *data;
+	struct name_callback *cb;
+	GSList *ldata, *lcb;
+
+	if (id == 0)
+		return FALSE;
+
+	for (ldata = name_listeners; ldata; ldata = ldata->next) {
+		data = ldata->data;
+		for (lcb = data->callbacks; lcb; lcb = lcb->next) {
+			cb = lcb->data;
+			if (cb->id == id)
+				goto remove;
+		}
+		for (lcb = data->processed; lcb; lcb = lcb->next) {
+			cb = lcb->data;
+			if (cb->id == id)
+				goto remove;
+		}
+	}
+
+	return FALSE;
+
+remove:
+	data->callbacks = g_slist_remove(data->callbacks, cb);
+	data->processed = g_slist_remove(data->processed, cb);
+	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->name) {
+		if (!remove_match(data->connection, data->name))
+			return FALSE;
+	}
+
+	name_listeners = g_slist_remove(name_listeners, data);
+	name_data_free(data);
+
+	/* Remove filter if there are no listeners left for the connection */
+	data = name_data_find(connection, NULL);
+	if (!data)
+		dbus_connection_remove_filter(connection, name_exit_filter,
+						NULL);
+
+	return TRUE;
+}
+
+void g_dbus_remove_all_watches(DBusConnection *connection)
+{
+	struct name_data *data;
+
+	while ((data = name_data_find(connection, NULL))) {
+		name_listeners = g_slist_remove(name_listeners, data);
+		name_data_call_and_free(data);
+	}
+
+	dbus_connection_remove_filter(connection, name_exit_filter, NULL);
+}
diff --git a/include/bluetooth/Makefile.am b/include/bluetooth/Makefile.am
new file mode 100644
index 0000000..992e987
--- /dev/null
+++ b/include/bluetooth/Makefile.am
@@ -0,0 +1,14 @@
+
+includedir = @includedir@/bluetooth
+
+include_HEADERS = \
+	bluetooth.h hci.h hci_lib.h sco.h l2cap.h \
+	sdp.h sdp_lib.h rfcomm.h bnep.h cmtp.h hidp.h
+
+MAINTAINERCLEANFILES = Makefile.in
+
+all-local:
+	@if [ ! -e bluetooth ] ; then $(LN_S) $(top_srcdir)/include bluetooth ; fi
+
+clean-local:
+	@rm -f bluetooth
diff --git a/include/bluetooth/bluetooth.h b/include/bluetooth/bluetooth.h
new file mode 100644
index 0000000..b0b23a4
--- /dev/null
+++ b/include/bluetooth/bluetooth.h
@@ -0,0 +1,160 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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>
+
+#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
+
+/* 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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __BLUETOOTH_H */
diff --git a/include/bluetooth/bnep.h b/include/bluetooth/bnep.h
new file mode 100644
index 0000000..a022c8e
--- /dev/null
+++ b/include/bluetooth/bnep.h
@@ -0,0 +1,153 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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/include/bluetooth/cmtp.h b/include/bluetooth/cmtp.h
new file mode 100644
index 0000000..b7439ac
--- /dev/null
+++ b/include/bluetooth/cmtp.h
@@ -0,0 +1,69 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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/include/bluetooth/hci.h b/include/bluetooth/hci.h
new file mode 100644
index 0000000..ad06445
--- /dev/null
+++ b/include/bluetooth/hci.h
@@ -0,0 +1,1823 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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 device 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 device flags */
+enum {
+	HCI_UP,
+	HCI_INIT,
+	HCI_RUNNING,
+
+	HCI_PSCAN,
+	HCI_ISCAN,
+	HCI_AUTH,
+	HCI_ENCRYPT,
+	HCI_INQUIRY,
+
+	HCI_RAW,
+};
+
+/* 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 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_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_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_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_EXT_FEAT	0x80
+
+/* 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
+
+/* 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 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_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
+
+/* 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
+
+/* 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
+
+/* 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_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;
+};
+#define HCI_DEV_NONE	0xffff
+
+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/include/bluetooth/hci_lib.h b/include/bluetooth/hci_lib.h
new file mode 100644
index 0000000..bd50785
--- /dev/null
+++ b/include/bluetooth/hci_lib.h
@@ -0,0 +1,210 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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_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_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_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/include/bluetooth/hidp.h b/include/bluetooth/hidp.h
new file mode 100644
index 0000000..7ae56c8
--- /dev/null
+++ b/include/bluetooth/hidp.h
@@ -0,0 +1,85 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+ *
+ */
+
+#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/include/bluetooth/l2cap.h b/include/bluetooth/l2cap.h
new file mode 100644
index 0000000..8bede46
--- /dev/null
+++ b/include/bluetooth/l2cap.h
@@ -0,0 +1,205 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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;
+};
+
+#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/include/bluetooth/rfcomm.h b/include/bluetooth/rfcomm.h
new file mode 100644
index 0000000..0765af3
--- /dev/null
+++ b/include/bluetooth/rfcomm.h
@@ -0,0 +1,99 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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/include/bluetooth/sco.h b/include/bluetooth/sco.h
new file mode 100644
index 0000000..43b6069
--- /dev/null
+++ b/include/bluetooth/sco.h
@@ -0,0 +1,62 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+#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/include/bluetooth/sdp.h b/include/bluetooth/sdp.h
new file mode 100644
index 0000000..63cfcf0
--- /dev/null
+++ b/include/bluetooth/sdp.h
@@ -0,0 +1,504 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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 <cutils/sockets.h>
+
+#define SDP_UNIX_PATH ANDROID_SOCKET_DIR"/bluetooth"
+#define SDP_RESPONSE_TIMEOUT	20
+#define SDP_REQ_BUFFER_SIZE	2048
+#define SDP_RSP_BUFFER_SIZE	65535
+#define SDP_PDU_CHUNK_SIZE	1024
+
+/*
+ * 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 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 MDP_SVCLASS_ID			0x1400
+#define MDP_SOURCE_SVCLASS_ID		0x1401
+#define MDP_SINK_SVCLASS_ID		0x1402
+#define APPLE_AGENT_SVCLASS_ID		0x2112
+
+/*
+ * 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 MDP_PROFILE_ID			MDP_SVCLASS_ID
+#define MDP_SOURCE_PROFILE_ID		MDP_SROUCE_SVCLASS_ID
+#define MDP_SINK_PROFILE_ID		MDP_SINK_SVCLASS_ID
+#define APPLE_AGENT_PROFILE_ID		APPLE_AGENT_SVCLASS_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_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_FAX_CLASS1_SUPPORT		0x0302
+#define SDP_ATTR_REMOTE_AUDIO_VOLUME_CONTROL	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 data[16];
+} uint128_t;
+
+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/include/bluetooth/sdp_lib.h b/include/bluetooth/sdp_lib.h
new file mode 100644
index 0000000..f3f4357
--- /dev/null
+++ b/include/bluetooth/sdp_lib.h
@@ -0,0 +1,616 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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);
+uuid_t *sdp_uuid_to_uuid128(uuid_t *uuid);
+void sdp_uuid16_to_uuid128(uuid_t *uuid128, uuid_t *uuid16);
+void sdp_uuid32_to_uuid128(uuid_t *uuid128, 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);
+}
+
+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/input/Android.mk b/input/Android.mk
new file mode 100755
index 0000000..ce8b421
--- /dev/null
+++ b/input/Android.mk
@@ -0,0 +1,38 @@
+LOCAL_PATH:= $(call my-dir)
+
+# HID plugin
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	device.c \
+	fakehid.c \
+	main.c \
+	manager.c \
+	server.c
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\" \
+	-DSTORAGEDIR=\"/data/misc/bluetoothd\" \
+	-DCONFIGDIR=\"/etc/bluez\"
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+	$(LOCAL_PATH)/../src \
+	$(LOCAL_PATH)/../gdbus \
+	$(call include-path-for, glib) \
+	$(call include-path-for, dbus)
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetoothd \
+	libbluetooth \
+	libdbus \
+	libexpat \
+	libcutils
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/bluez-plugin
+LOCAL_UNSTRIPPED_PATH := $(TARGET_OUT_SHARED_LIBRARIES_UNSTRIPPED)/bluez-plugin
+LOCAL_MODULE := input
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/input/Makefile.am b/input/Makefile.am
new file mode 100644
index 0000000..98ce928
--- /dev/null
+++ b/input/Makefile.am
@@ -0,0 +1,24 @@
+
+if INPUTPLUGIN
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES = input.la
+
+input_la_SOURCES = main.c manager.h manager.c \
+			server.h server.c device.h device.c \
+						fakehid.c fakehid.h
+
+LDADD = $(top_builddir)/common/libhelper.a \
+		@GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+endif
+
+AM_LDFLAGS = -module -avoid-version -no-undefined
+
+AM_CFLAGS = -fvisibility=hidden \
+		@BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/src
+
+EXTRA_DIST = input.conf sixpair.c
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/input/device.c b/input/device.c
new file mode 100644
index 0000000..2cfc5d8
--- /dev/null
+++ b/input/device.c
@@ -0,0 +1,1293 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/hidp.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+#include "uinput.h"
+
+#include "../src/storage.h"
+#include "../src/manager.h"
+#include "../src/dbus-common.h"
+#include "adapter.h"
+#include "../src/device.h"
+
+#include "device.h"
+#include "error.h"
+#include "fakehid.h"
+#include "glib-helper.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];
+	gsize bread = 0, bwritten;
+	uint16_t key;
+
+	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;
+	}
+
+	memset(buf, 0, BUF_SIZE);
+	if (g_io_channel_read(chan, buf, sizeof(buf) - 1,
+				&bread) != G_IO_ERROR_NONE) {
+		error("IO Channel read error");
+		goto failed;
+	}
+
+	debug("Received: %s", buf);
+
+	if (g_io_channel_write(chan, ok, 6, &bwritten) != G_IO_ERROR_NONE) {
+		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 inline DBusMessage *not_supported(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+							"Not supported");
+}
+
+static inline DBusMessage *in_progress(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+				"Device connection already in progress");
+}
+
+static inline DBusMessage *already_connected(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyConnected",
+					"Already connected to a device");
+}
+
+static inline DBusMessage *connection_attempt_failed(DBusMessage *msg,
+							const char *err)
+{
+	return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".ConnectionAttemptFailed",
+				err ? err : "Connection attempt failed");
+}
+
+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 = connection_attempt_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 = connection_attempt_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:
+	if (req->rd_data)
+		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) {
+		fake = g_new0(struct fake_input, 1);
+		fake->connect = fake_hid_connect;
+		fake->disconnect = fake_hid_disconnect;
+		fake->priv = fake_hid;
+		err = fake_hid_connadd(fake, iconn->intr_io, fake_hid);
+		goto cleanup;
+	}
+
+	if (idev->name)
+		strncpy(req->name, idev->name, sizeof(req->name) - 1);
+
+	/* Encryption is mandatory for keyboards */
+	if (req->subclass & 0x40) {
+		err = bt_acl_encrypt(&idev->src, &idev->dst, encrypt_completed, req);
+		if (err == 0) {
+			/* Waiting async encryption */
+			return 0;
+		} else if (err != -EALREADY) {
+			error("bt_acl_encrypt(): %s(%d)", strerror(-err), -err);
+			goto cleanup;
+		}
+	}
+
+	if (req->vendor == 0x054c && req->product == 0x0268) {
+		unsigned char buf[] = { 0x53, 0xf4,  0x42, 0x03, 0x00, 0x00 };
+		int sk = g_io_channel_unix_get_fd(iconn->ctrl_io);
+		err = write(sk, buf, sizeof(buf));
+	}
+
+	err = ioctl_connadd(req);
+
+cleanup:
+	if (req->rd_data)
+		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;
+		g_io_channel_unref(iconn->intr_io);
+		iconn->intr_io = NULL;
+		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 = connection_attempt_failed(iconn->pending_connect, err_msg);
+	g_dbus_send_message(idev->conn, reply);
+
+	if (iconn->ctrl_io)
+		g_io_channel_shutdown(iconn->ctrl_io, FALSE, NULL);
+
+	if (iconn->intr_io) {
+		if (!conn_err)
+			g_io_channel_shutdown(iconn->intr_io, FALSE, NULL);
+		g_io_channel_unref(iconn->intr_io);
+		iconn->intr_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 = connection_attempt_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_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		reply = connection_attempt_failed(iconn->pending_connect,
+							err->message);
+		g_error_free(err);
+		g_io_channel_shutdown(chan, TRUE, NULL);
+		goto failed;
+	}
+
+	iconn->intr_io = io;
+
+	return;
+
+failed:
+	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 not_supported(msg);
+
+	if (iconn->pending_connect)
+		return in_progress(msg);
+
+	if (is_connected(iconn))
+		return 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_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 = connection_attempt_failed(msg, err->message);
+	g_error_free(err);
+	return reply;
+}
+
+static DBusMessage *create_errno_message(DBusMessage *msg, int err)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+							strerror(err));
+}
+
+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 create_errno_message(msg, -err);
+
+	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void device_unregister(void *data)
+{
+	struct input_device *idev = data;
+
+	debug("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;
+	}
+
+	debug("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
index 0000000..f9ec7c2
--- /dev/null
+++ b/input/device.h
@@ -0,0 +1,55 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+#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;
+};
+
+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
index 0000000..6c9a715
--- /dev/null
+++ b/input/fakehid.c
@@ -0,0 +1,387 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.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 "logging.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;
+	gsize size;
+	char buff[50];
+
+	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;
+	}
+
+	memset(buff, 0, sizeof(buff));
+
+	if (g_io_channel_read(chan, buff, sizeof(buff), &size) !=
+							G_IO_ERROR_NONE) {
+		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,
+	},
+
+	{ },
+};
+
+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;
+}
+
+int fake_hid_connadd(struct fake_input *fake, GIOChannel *intr_io,
+						struct fake_hid *fake_hid)
+{
+	if (fake_hid->setup_uinput(fake, fake_hid)) {
+		error("Error setting up uinput");
+		return ENOMEM;
+	}
+
+	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 0;
+}
diff --git a/input/fakehid.h b/input/fakehid.h
new file mode 100644
index 0000000..90aacb4
--- /dev/null
+++ b/input/fakehid.h
@@ -0,0 +1,39 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 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);
+};
+
+struct fake_hid *get_fake_hid(uint16_t vendor, uint16_t product);
+
+int 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
index 0000000..abfb64f
--- /dev/null
+++ b/input/input.conf
@@ -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
index 0000000..913ea7b
--- /dev/null
+++ b/input/main.c
@@ -0,0 +1,86 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <bluetooth/bluetooth.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "logging.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
index 0000000..567075b
--- /dev/null
+++ b/input/manager.c
@@ -0,0 +1,207 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <gdbus.h>
+
+#include "logging.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;
+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) {
+			debug("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
index 0000000..754c57a
--- /dev/null
+++ b/input/manager.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+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
index 0000000..5ae9f8c
--- /dev/null
+++ b/input/server.c
@@ -0,0 +1,235 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <unistd.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "logging.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;
+	}
+
+	debug("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_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_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
index 0000000..c3dee07
--- /dev/null
+++ b/input/server.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+int server_start(const bdaddr_t *src);
+void server_stop(const bdaddr_t *src);
diff --git a/input/sixpair.c b/input/sixpair.c
new file mode 100644
index 0000000..5c58b9b
--- /dev/null
+++ b/input/sixpair.c
@@ -0,0 +1,299 @@
+/* To compile
+ * gcc -g -Wall -I../src -I../lib/ -I../include -DSTORAGEDIR=\"/var/lib/bluetooth\" -o sixpair sixpair.c ../src/storage.c ../common/libhelper.a -I../common `pkg-config --libs --cflags glib-2.0 libusb-1.0` -lbluetooth
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <sdp.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp_lib.h>
+#include <glib.h>
+#include <libusb.h>
+
+#include "storage.h"
+
+/* Vendor and product ID for the Sixaxis PS3 controller */
+#define VENDOR 0x054c
+#define PRODUCT 0x0268
+
+#define PS3_PNP_RECORD "3601920900000A000100000900013503191124090004350D35061901000900113503190011090006350909656E09006A0901000900093508350619112409010009000D350F350D350619010009001335031900110901002513576972656C65737320436F6E74726F6C6C65720901012513576972656C65737320436F6E74726F6C6C6572090102251B536F6E7920436F6D707574657220456E7465727461696E6D656E740902000901000902010901000902020800090203082109020428010902052801090206359A35980822259405010904A101A102850175089501150026FF00810375019513150025013500450105091901291381027501950D0600FF8103150026FF0005010901A10075089504350046FF0009300931093209358102C0050175089527090181027508953009019102750895300901B102C0A1028502750895300901B102C0A10285EE750895300901B102C0A10285EF750895300901B102C0C0090207350835060904090901000902082800090209280109020A280109020B09010009020C093E8009020D280009020E2800"
+
+gboolean option_get_master = TRUE;
+char *option_master= NULL;
+gboolean option_store_info = TRUE;
+const char *option_device = NULL;
+gboolean option_quiet = FALSE;
+
+const GOptionEntry options[] = {
+	{ "get-master", '\0', 0, G_OPTION_ARG_NONE, &option_get_master, "Get currently set master address", NULL },
+	{ "set-master", '\0', 0, G_OPTION_ARG_STRING, &option_master, "Set master address (\"auto\" for automatic)", NULL },
+	{ "store-info", '\0', 0, G_OPTION_ARG_NONE, &option_store_info, "Store the HID info into the input database", NULL },
+	{ "device", '\0', 0, G_OPTION_ARG_STRING, &option_device, "Only handle one device (default, all supported", NULL },
+	{ "quiet", 'q', 0, G_OPTION_ARG_NONE, &option_quiet, "Quieten the output", NULL },
+	{ NULL }
+};
+
+static gboolean
+show_master (libusb_device_handle *devh, int itfnum)
+{
+	unsigned char msg[8];
+	int res;
+
+	res = libusb_control_transfer (devh,
+				       LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
+				       0x01, 0x03f5, itfnum,
+				       (void*) msg, sizeof(msg),
+				       5000);
+
+	if (res < 0) {
+		g_warning ("Getting the master Bluetooth address failed");
+		return FALSE;
+	}
+	g_print ("Current Bluetooth master: %02X:%02X:%02X:%02X:%02X:%02X\n",
+		 msg[2], msg[3], msg[4], msg[5], msg[6], msg[7]);
+
+	return TRUE;
+}
+
+static char *
+get_bdaddr (libusb_device_handle *devh, int itfnum)
+{
+	unsigned char msg[17];
+	char *address;
+	int res;
+
+	res = libusb_control_transfer (devh,
+				       LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
+				       0x01, 0x03f2, itfnum,
+				       (void*) msg, sizeof(msg),
+				       5000);
+
+	if (res < 0) {
+		g_warning ("Getting the device Bluetooth address failed");
+		return NULL;
+	}
+
+	address = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X",
+				   msg[4], msg[5], msg[6], msg[7], msg[8], msg[9]);
+
+	if (option_quiet == FALSE) {
+		g_print ("Device Bluetooth address: %s\n", address);
+	}
+
+	return address;
+}
+
+static gboolean
+set_master_bdaddr (libusb_device_handle *devh, int itfnum, char *host)
+{
+	unsigned char msg[8];
+	int mac[6];
+	int res;
+
+	if (sscanf(host, "%X:%X:%X:%X:%X:%X",
+		   &mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) != 6) {
+		return FALSE;
+	}
+
+	msg[0] = 0x01;
+	msg[1] = 0x00;
+	msg[2] = mac[0];
+	msg[3] = mac[1];
+	msg[4] = mac[2];
+	msg[5] = mac[3];
+	msg[6] = mac[4];
+	msg[7] = mac[5];
+
+	res = libusb_control_transfer (devh,
+				       LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
+				       0x09, 0x03f5, itfnum,
+				       (void*) msg, sizeof(msg),
+				       5000);
+
+	if (res < 0) {
+		g_warning ("Setting the master Bluetooth address failed");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static char *
+get_host_bdaddr (void)
+{
+	FILE *f;
+	int mac[6];
+
+	//FIXME use dbus to get the default adapter
+
+	f = popen("hcitool dev", "r");
+
+	if (f == NULL) {
+		//FIXME
+		return NULL;
+	}
+	if (fscanf(f, "%*s\n%*s %X:%X:%X:%X:%X:%X",
+		   &mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) != 6) {
+		//FIXME
+		return NULL;
+	}
+
+	return g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+}
+
+static int
+handle_device (libusb_device *dev, struct libusb_config_descriptor *cfg, int itfnum, const struct libusb_interface_descriptor *alt)
+{
+	libusb_device_handle *devh;
+	int res, retval;
+
+	retval = -1;
+
+	if (libusb_open (dev, &devh) < 0) {
+		g_warning ("Can't open device");
+		goto bail;
+	}
+	libusb_detach_kernel_driver (devh, itfnum);
+
+	res = libusb_claim_interface (devh, itfnum);
+	if (res < 0) {
+		g_warning ("Can't claim interface %d", itfnum);
+		goto bail;
+	}
+
+	if (option_get_master != FALSE) {
+		if (show_master (devh, itfnum) == FALSE)
+			goto bail;
+		retval = 0;
+	}
+
+	if (option_master != NULL) {
+		if (strcmp (option_master, "auto") == 0) {
+			g_free (option_master);
+			option_master = get_host_bdaddr ();
+			if (option_master == NULL) {
+				g_warning ("Can't get bdaddr from default device");
+				retval = -1;
+				goto bail;
+			}
+		}
+	} else {
+		option_master = get_host_bdaddr ();
+		if (option_master == NULL) {
+			g_warning ("Can't get bdaddr from default device");
+			retval = -1;
+			goto bail;
+		}
+	}
+
+	if (option_store_info != FALSE) {
+		sdp_record_t *rec;
+		char *device;
+		bdaddr_t dst, src;
+
+		device = get_bdaddr (devh, itfnum);
+		if (device == NULL) {
+			retval = -1;
+			goto bail;
+		}
+
+		rec = record_from_string (PS3_PNP_RECORD);
+		store_record(option_master, device, rec);
+		write_trust(option_master, device, "[all]", TRUE);
+		store_device_id(option_master, device, 0xffff, 0x054c, 0x0268, 0);
+		str2ba(option_master, &src);
+		str2ba(device, &dst);
+		write_device_profiles(&src, &dst, "");
+		write_device_name(&src, &dst, "PLAYSTATION(R)3 Controller");
+		sdp_record_free(rec);
+
+		if (set_master_bdaddr (devh, itfnum, option_master) == FALSE) {
+			retval = -1;
+			goto bail;
+		}
+	}
+
+bail:
+	libusb_release_interface (devh, itfnum);
+	res = libusb_attach_kernel_driver(devh, itfnum);
+	if (res < 0) {
+		//FIXME sometimes the kernel tells us ENOENT, but succeeds anyway...
+		g_warning ("Reattaching the driver failed: %d", res);
+	}
+	if (devh != NULL)
+		libusb_close (devh);
+
+	return retval;
+}
+
+int main (int argc, char **argv)
+{
+	GOptionContext *context;
+	GError *error = NULL;
+	libusb_device **list;
+	ssize_t num_devices, i;
+
+	context = g_option_context_new ("- Manage Sixaxis PS3 controllers");
+	g_option_context_add_main_entries (context, options, NULL);
+	if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) {
+		g_warning ("Couldn't parse command-line options: %s", error->message);
+		return 1;
+	}
+
+	/* Check that the passed bdaddr is correct */
+	if (option_master != NULL && strcmp (option_master, "auto") != 0) {
+		//FIXME check bdaddr
+	}
+
+	libusb_init (NULL);
+
+	/* Find device(s) */
+	num_devices = libusb_get_device_list (NULL, &list);
+	if (num_devices < 0) {
+		g_warning ("libusb_get_device_list failed");
+		return 1;
+	}
+
+	for (i = 0; i < num_devices; i++) {
+		struct libusb_config_descriptor *cfg;
+		libusb_device *dev = list[i];
+		struct libusb_device_descriptor desc;
+		guint8 j;
+
+		if (libusb_get_device_descriptor (dev, &desc) < 0) {
+			g_warning ("libusb_get_device_descriptor failed");
+			continue;
+		}
+
+		/* Here we check for the supported devices */
+		if (desc.idVendor != VENDOR || desc.idProduct != PRODUCT)
+			continue;
+
+		/* Look for the interface number that interests us */
+		for (j = 0; j < desc.bNumConfigurations; j++) {
+			struct libusb_config_descriptor *config;
+			guint8 k;
+
+			libusb_get_config_descriptor (dev, j, &config);
+
+			for (k = 0; k < config->bNumInterfaces; k++) {
+				const struct libusb_interface *itf = &config->interface[k];
+				int l;
+
+				for (l = 0; l < itf->num_altsetting ; l++) {
+					struct libusb_interface_descriptor alt;
+
+					alt = itf->altsetting[l];
+					if (alt.bInterfaceClass == 3) {
+						handle_device (dev, cfg, l, &alt);
+					}
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
diff --git a/lib/Android.mk b/lib/Android.mk
new file mode 100755
index 0000000..a0dc4d0
--- /dev/null
+++ b/lib/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	bluetooth.c \
+	sdp.c \
+	hci.c
+
+LOCAL_C_INCLUDES+= \
+	 $(LOCAL_PATH)/../include/
+
+LOCAL_MODULE:=libbluetooth
+
+LOCAL_CFLAGS+=-O3
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..f7a8ffb
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,9 @@
+
+lib_LTLIBRARIES = libbluetooth.la
+
+libbluetooth_la_SOURCES = bluetooth.c hci.c sdp.c
+libbluetooth_la_LDFLAGS = -version-info 5:7:2
+
+INCLUDES = -I$(top_builddir)/include
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/lib/bluetooth.c b/lib/bluetooth.c
new file mode 100644
index 0000000..1c34f25
--- /dev/null
+++ b/lib/bluetooth.c
@@ -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-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 <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/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)
+{
+	const char *ptr = str;
+	int i;
+
+	uint8_t *ba = bt_malloc(sizeof(bdaddr_t));
+	if (!ba)
+		return NULL;
+
+	for (i = 0; i < 6; i++) {
+		ba[i] = (uint8_t) strtol(ptr, NULL, 16);
+		if (i != 5 && !(ptr = strchr(ptr,':')))
+			ptr = ":00:00:00:00:00";
+		ptr++;
+	}
+
+	return (bdaddr_t *) ba;
+}
+
+int ba2str(const bdaddr_t *ba, char *str)
+{
+	uint8_t b[6];
+
+	baswap((bdaddr_t *) b, ba);
+	return sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+		b[0], b[1], b[2], b[3], b[4], b[5]);
+}
+
+int str2ba(const char *str, bdaddr_t *ba)
+{
+	uint8_t b[6];
+	const char *ptr = str;
+	int i;
+
+	for (i = 0; i < 6; i++) {
+		b[i] = (uint8_t) strtol(ptr, NULL, 16);
+		if (i != 5 && !(ptr = strchr(ptr, ':')))
+			ptr = ":00:00:00:00:00";
+		ptr++;
+	}
+
+	baswap(ba, (bdaddr_t *) b);
+
+	return 0;
+}
+
+int ba2oui(const bdaddr_t *ba, char *str)
+{
+	uint8_t b[6];
+
+	baswap((bdaddr_t *) b, ba);
+
+	return sprintf(str, "%2.2X-%2.2X-%2.2X", b[0], b[1], b[2]);
+}
+
+int bachk(const char *str)
+{
+	char tmp[18], *ptr = tmp;
+
+	if (!str)
+		return -1;
+
+	if (strlen(str) != 17)
+		return -1;
+
+	memcpy(tmp, str, 18);
+
+	while (*ptr) {
+		*ptr = toupper(*ptr);
+		if (*ptr < '0'|| (*ptr > '9' && *ptr < 'A') || *ptr > 'F')
+			return -1;
+		ptr++;
+
+		*ptr = toupper(*ptr);
+		if (*ptr < '0'|| (*ptr > '9' && *ptr < 'A') || *ptr > 'F')
+			return -1;
+		ptr++;
+
+		*ptr = toupper(*ptr);
+		if (*ptr == 0)
+			break;
+		if (*ptr != ':')
+			return -1;
+		ptr++;
+	}
+
+	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";	/* (tentative) */
+	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 65535:
+		return "internal use";
+	default:
+		return "not assigned";
+	}
+}
diff --git a/lib/hci.c b/lib/hci.c
new file mode 100644
index 0000000..6d25a8b
--- /dev/null
+++ b/lib/hci.c
@@ -0,0 +1,2498 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/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_dtypetostr(int type)
+{
+	switch (type) {
+	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";
+	}
+}
+
+/* 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 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 Capabilities Response Negative Reply",	163 },
+	{ "Reserved",					164 },
+	{ "Reserved",					165 },
+	{ "Reserved",					166 },
+	{ "Reserved",					167 },
+
+	{ 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 },
+	{ 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 */
+		{ "<no. 37>",		0x20		},	/* Bit 5 */
+		{ "<no. 38>",		0x40		},	/* 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 */
+		{ "<no. 49>",		0x02		},	/* 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 */
+		{ "<no. 58>",		0x04		},	/* 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(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;
+		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;
+
+		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_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_inquiry_transmit_power_level(int dd, int8_t *level, int to)
+{
+	read_inquiry_transmit_power_level_rp rp;
+	struct hci_request rq;
+
+	memset(&rq, 0, sizeof(rq));
+	rq.ogf    = OGF_HOST_CTL;
+	rq.ocf    = OCF_READ_INQUIRY_TRANSMIT_POWER_LEVEL;
+	rq.rparam = &rp;
+	rq.rlen   = READ_INQUIRY_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_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;
+}
diff --git a/lib/sdp.c b/lib/sdp.c
new file mode 100644
index 0000000..bf34c3c
--- /dev/null
+++ b/lib/sdp.c
@@ -0,0 +1,4603 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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 <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 <netinet/in.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
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define ntoh64(x) (x)
+static inline void ntoh128(uint128_t *src, uint128_t *dst)
+{
+	int i;
+	for (i = 0; i < 16; i++)
+		dst->data[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(uint128_t *src, uint128_t *dst)
+{
+	int i;
+	for (i = 0; i < 16; i++)
+		dst->data[15 - i] = src->data[i];
+}
+#endif
+
+#define hton64(x)     ntoh64(x)
+#define hton128(x, y) ntoh128(x, y)
+
+#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"		},
+	{ 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"		},
+	{ MDP_SVCLASS_ID,			"MDP"				},
+	{ MDP_SOURCE_SVCLASS_ID,		"MDP Source"			},
+	{ MDP_SINK_SVCLASS_ID,			"MDP Sink"			},
+	{ APPLE_AGENT_SVCLASS_ID,		"Apple Agent"			},
+	{ 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 = *(uint8_t *) ptr++;
+
+	switch (dtd) {
+	case SDP_SEQ8:
+	case SDP_ALT8:
+	case SDP_TEXT_STR8:
+	case SDP_URL_STR8:
+		*(uint8_t *)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));
+
+	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));
+
+	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 = *(uint8_t *) 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));
+
+	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 0;
+	}
+
+	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));
+
+	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 (sdp_data_t *)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 0;
+
+	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 0;
+	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 = (sdp_data_t *) 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 = (sdp_data_t *) 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)
+				goto fail;
+
+			u = malloc(sizeof(uuid_t));
+			memset(u, 0, sizeof(uuid_t));
+			*u = d->val.uuid;
+			*seqp = sdp_list_append(*seqp, u);
+		}
+		return 0;
+	}
+fail:
+	sdp_list_free(*seqp, free);
+	errno = EINVAL;
+	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 = (void **)malloc(len * sizeof(void *));
+	values = (void **)malloc(len * sizeof(void *));
+	for (p = seq, i = 0; i < len; i++, p = p->next) {
+		uuid_t *uuid = (uuid_t *)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 (pCode && pEncoding && pOffset) {
+			lang = malloc(sizeof(sdp_lang_attr_t));
+			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));
+			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,
+								(void *)name);
+	if (prov)
+		sdp_attr_add_new(rec, SDP_ATTR_PROVNAME_PRIMARY, SDP_TEXT_STR8,
+								(void *)prov);
+	if (desc)
+		sdp_attr_add_new(rec, SDP_ATTR_SVCDESC_PRIMARY, SDP_TEXT_STR8,
+								(void *)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 = (void **)malloc(seqlen * sizeof(void *));
+	seqs = (void **)malloc(seqlen * sizeof(void *));
+	for (i = 0, p = proto; p; p = p->next, i++) {
+		sdp_list_t *elt = (sdp_list_t *)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 = (sdp_data_t *)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,
+						(sdp_list_t *) 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,
+						(sdp_list_t *) 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 = (void **)malloc(3 * seqlen * sizeof(void *));
+	void **values = (void **)malloc(3 * seqlen * sizeof(void *));
+	const sdp_list_t *p;
+
+	for (p = seq; p; p = p->next) {
+		sdp_lang_attr_t *lang = (sdp_lang_attr_t *)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 = (void **)malloc(seqlen * sizeof(void *));
+	void **seqs = (void **)malloc(seqlen * sizeof(void *));
+	const sdp_list_t *p;
+
+	for (p = profiles; p; p = p->next) {
+		sdp_data_t *seq;
+		void *dtds[2], *values[2];
+		sdp_profile_desc_t *profile = (sdp_profile_desc_t *)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_uuid16_cmp(const void *p1, const void *p2)
+{
+	const uuid_t *u1 = (const uuid_t *)p1;
+	const uuid_t *u2 = (const uuid_t *)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 = (const uuid_t *)p1;
+	const uuid_t *u2 = (const uuid_t *)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, 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, 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(uuid_t *uuid)
+{
+	uuid_t *uuid128 = bt_malloc(sizeof(uuid_t));
+	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 = *(uint8_t *) 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 */
+		*(uint8_t *)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;
+		*(uint8_t *) 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:
+	if (req)
+		free(req);
+
+	if (rsp)
+		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:
+	if (reqbuf)
+		free(reqbuf);
+
+	if (rspbuf)
+		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:
+	if (reqbuf)
+		free(reqbuf);
+	if (rspbuf)
+		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));
+	memset((void *)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("SvcRec : 0x%lx\n", (unsigned long)rec);
+	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 = (uuid_t *) 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:
+	if (reqbuf)
+		free(reqbuf);
+	if (rspbuf)
+		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 0;
+	}
+
+	reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+	rspbuf = malloc(SDP_RSP_BUFFER_SIZE);
+	if (!reqbuf || !rspbuf) {
+		errno = ENOMEM;
+		goto end;
+	}
+	memset((char *) &rsp_concat_buf, 0, sizeof(sdp_buf_t));
+	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:
+	if (reqbuf)
+		free(reqbuf);
+	if (rsp_concat_buf.data)
+		free(rsp_concat_buf.data);
+	if (rspbuf)
+		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;
+
+	/* check if the buffer is already allocated */
+	if (t->rsp_concat_buf.data)
+		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:
+
+	if (t->reqbuf) {
+		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;
+
+	/* check if the buffer is already allocated */
+	if (t->rsp_concat_buf.data)
+		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:
+
+	if (t->reqbuf) {
+		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;
+
+	/* check if the buffer is already allocated */
+	if (t->rsp_concat_buf.data)
+		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:
+
+	if (t->reqbuf) {
+		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);
+	}
+
+	if (rspbuf)
+		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;
+	}
+	reqbuf = malloc(SDP_REQ_BUFFER_SIZE);
+	rspbuf = malloc(SDP_RSP_BUFFER_SIZE);
+	if (!reqbuf || !rspbuf) {
+		errno = ENOMEM;
+		status = -1;
+		goto end;
+	}
+
+	memset((char *)&rsp_concat_buf, 0, sizeof(sdp_buf_t));
+	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:
+	if (rsp_concat_buf.data)
+		free(rsp_concat_buf.data);
+	if (reqbuf)
+		free(reqbuf);
+	if (rspbuf)
+		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) {
+		if (t->reqbuf)
+			free(t->reqbuf);
+
+		if (t->rsp_concat_buf.data)
+			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);
+	if (session->priv)
+		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++;
+}
diff --git a/network/Makefile.am b/network/Makefile.am
new file mode 100644
index 0000000..f5cb320
--- /dev/null
+++ b/network/Makefile.am
@@ -0,0 +1,24 @@
+
+if NETWORKPLUGIN
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES = network.la
+
+network_la_SOURCES = main.c manager.h manager.c \
+				server.h server.c bridge.h bridge.c \
+				connection.h connection.c common.h common.c
+
+LDADD = $(top_builddir)/common/libhelper.a \
+		@GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+endif
+
+AM_LDFLAGS = -module -avoid-version -no-undefined
+
+AM_CFLAGS = -fvisibility=hidden \
+		@BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/src
+
+EXTRA_DIST = network.conf
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/network/bridge.c b/network/bridge.c
new file mode 100644
index 0000000..995da5c
--- /dev/null
+++ b/network/bridge.c
@@ -0,0 +1,148 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <unistd.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <linux/sockios.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/bnep.h>
+
+#include "logging.h"
+#include "bridge.h"
+#include "common.h"
+
+static int bridge_socket = -1;
+static const char *gn_bridge = NULL;
+static const char *nap_bridge = NULL;
+
+int bridge_init(const char *gn_iface, const char *nap_iface)
+{
+#if 0
+	struct stat st;
+
+	if (stat("/sys/module/bridge", &st) < 0)
+		return -EOPNOTSUPP;
+#endif
+	bridge_socket = socket(AF_INET, SOCK_STREAM, 0);
+	if (bridge_socket < 0) {
+		error("Failed to open bridge socket: %s (%d)",
+				strerror(errno), errno);
+		return -errno;
+	}
+
+	gn_bridge = gn_iface;
+	nap_bridge = nap_iface;
+
+	return 0;
+}
+
+void bridge_cleanup(void)
+{
+	close(bridge_socket);
+
+	bridge_socket = -1;
+}
+
+int bridge_create(int id)
+{
+	int err;
+	const char *name = bridge_get_name(id);
+
+	err = ioctl(bridge_socket, SIOCBRADDBR, name);
+	if (err < 0)
+		return -errno;
+
+	info("bridge %s created", name);
+
+	return 0;
+}
+
+int bridge_remove(int id)
+{
+	int err;
+	const char *name = bridge_get_name(id);
+
+	err = bnep_if_down(name);
+	if (err < 0)
+		return err;
+
+	err = ioctl(bridge_socket, SIOCBRDELBR, name);
+	if (err < 0)
+		return -errno;
+
+	info("bridge %s removed", name);
+
+	return 0;
+}
+
+int bridge_add_interface(int id, const char *dev)
+{
+	struct ifreq ifr;
+	int err;
+	int ifindex = if_nametoindex(dev);
+	const char *name = bridge_get_name(id);
+
+	if (!name)
+		return -EINVAL;
+
+	if (ifindex == 0)
+		return -ENODEV;
+
+	memset(&ifr, 0, sizeof(ifr));
+	strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
+	ifr.ifr_ifindex = ifindex;
+
+	err = ioctl(bridge_socket, SIOCBRADDIF, &ifr);
+	if (err < 0)
+		return err;
+
+	info("bridge %s: interface %s added", name, dev);
+
+	err = bnep_if_up(name, id);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+const char *bridge_get_name(int id)
+{
+	if (id == BNEP_SVC_GN)
+		return gn_bridge;
+
+	if (id == BNEP_SVC_NAP)
+		return nap_bridge;
+
+	return NULL;
+}
diff --git a/network/bridge.h b/network/bridge.h
new file mode 100644
index 0000000..272afe2
--- /dev/null
+++ b/network/bridge.h
@@ -0,0 +1,30 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+int bridge_init(const char *gn_iface, const char *nap_iface);
+void bridge_cleanup(void);
+
+int bridge_create(int id);
+int bridge_remove(int id);
+int bridge_add_interface(int id, const char *dev);
+const char *bridge_get_name(int id);
diff --git a/network/common.c b/network/common.c
new file mode 100644
index 0000000..479716a
--- /dev/null
+++ b/network/common.c
@@ -0,0 +1,373 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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 <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/bnep.h>
+
+#include <glib.h>
+
+#include "logging.h"
+#include "common.h"
+
+static int ctl;
+static GSList *pids;
+
+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 }
+};
+
+static const char *panu = NULL;
+static const char *gn = NULL;
+static const char *nap = NULL;
+
+struct bnep_data {
+	char *devname;
+	char *script;
+	int pid;
+};
+
+static gint find_devname(gconstpointer a, gconstpointer b)
+{
+	struct bnep_data *data = (struct bnep_data *) a;
+	const char *devname = b;
+
+	return strcmp(data->devname, devname);
+}
+
+static void script_exited(GPid pid, gint status, gpointer data)
+{
+	if (WIFEXITED(status))
+		debug("%d exited with status %d", pid, WEXITSTATUS(status));
+	else
+		debug("%d was killed by signal %d", pid, WTERMSIG(status));
+
+	g_spawn_close_pid(pid);
+}
+
+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(const char *panu_script, const char *gn_script,
+		const char *nap_script)
+{
+	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;
+	}
+
+	panu = panu_script;
+	gn = gn_script;
+	nap = nap_script;
+	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;
+}
+
+static void bnep_setup(gpointer data)
+{
+}
+
+static int bnep_exec(const char **argv)
+{
+	int pid;
+	GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH;
+
+	if (!g_spawn_async(NULL, (char **) argv, NULL, flags, bnep_setup, NULL,
+				&pid, NULL)) {
+		error("Unable to execute %s %s", argv[0], argv[1]);
+		return -EINVAL;
+	}
+
+	return pid;
+}
+
+int bnep_if_up(const char *devname, uint16_t id)
+{
+	int sd, err;
+	struct ifreq ifr;
+	const char *argv[5];
+	struct bnep_data *bnep = NULL;
+	GSList *l;
+
+	/* Check if a script is running */
+	l = g_slist_find_custom(pids, devname, find_devname);
+	if (l) {
+		bnep = l->data;
+
+		if (bnep->script && !strcmp(bnep->script, "avahi-autoipd")) {
+			argv[0] = bnep->script;
+			argv[1] = devname;
+			argv[2] = "--refresh";
+			argv[3] = NULL;
+
+			bnep->pid = bnep_exec(argv);
+		}
+	}
+
+	sd = socket(AF_INET, SOCK_DGRAM, 0);
+	memset(&ifr, 0, sizeof(ifr));
+	strncpy(ifr.ifr_name, devname, IF_NAMESIZE - 1);
+
+	ifr.ifr_flags |= IFF_UP;
+	ifr.ifr_flags |= IFF_MULTICAST;
+
+	if ((ioctl(sd, SIOCSIFFLAGS, (caddr_t) &ifr)) < 0) {
+		err = errno;
+		error("Could not bring up %s. %s(%d)", devname, strerror(err),
+			err);
+		return -err;
+	}
+
+	if (bnep)
+		return bnep->pid;
+
+	bnep = g_new0(struct bnep_data, 1);
+	bnep->devname = g_strdup(devname);
+
+	if (!id)
+		goto done;
+
+	if (id == BNEP_SVC_PANU)
+		bnep->script = g_strdup(panu);
+	else if (id == BNEP_SVC_GN)
+		bnep->script = g_strdup(gn);
+	else
+		bnep->script = g_strdup(nap);
+
+	if (!bnep->script)
+		goto done;
+
+	argv[0] = bnep->script;
+	argv[1] = devname;
+
+	if (!strcmp(bnep->script, "avahi-autoipd")) {
+		argv[2] = "--no-drop-root";
+		argv[3] = "--no-chroot";
+		argv[4] = NULL;
+	} else
+		argv[2] = NULL;
+
+	bnep->pid = bnep_exec(argv);
+	g_child_watch_add(bnep->pid, script_exited, bnep);
+
+done:
+	pids = g_slist_append(pids, bnep);
+
+	return bnep->pid;
+}
+
+int bnep_if_down(const char *devname)
+{
+	int sd, err, pid;
+	struct ifreq ifr;
+	struct bnep_data *bnep;
+	GSList *l;
+	GSpawnFlags flags;
+	const char *argv[4];
+
+	l = g_slist_find_custom(pids, devname, find_devname);
+	if (!l)
+		return 0;
+
+	bnep = l->data;
+
+	if (!bnep->pid)
+		goto done;
+
+	if (bnep->script && !strcmp(bnep->script, "avahi-autoipd")) {
+		argv[0] = bnep->script;
+		argv[1] = devname;
+		argv[2] = "--kill";
+		argv[3] = NULL;
+
+		flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH;
+		g_spawn_async(NULL, (char **) argv, NULL, flags, bnep_setup,
+				(gpointer) devname, &pid, NULL);
+
+		goto done;
+	}
+
+	/* Kill script */
+	err = kill(bnep->pid, SIGTERM);
+	if (err < 0)
+		error("kill(%d, SIGTERM): %s (%d)", bnep->pid,
+			strerror(errno), errno);
+
+done:
+	sd = 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 */
+	ioctl(sd, SIOCSIFFLAGS, (caddr_t) &ifr);
+
+	pids = g_slist_remove(pids, bnep);
+
+	if (bnep->devname)
+		g_free(bnep->devname);
+
+	if (bnep->script)
+		g_free(bnep->script);
+
+	g_free(bnep);
+
+	return 0;
+}
diff --git a/network/common.h b/network/common.h
new file mode 100644
index 0000000..424d618
--- /dev/null
+++ b/network/common.h
@@ -0,0 +1,42 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+#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(const char *panu_script, const char *gn_script,
+		const char *nap_script);
+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, uint16_t id);
+int bnep_if_down(const char *devname);
diff --git a/network/connection.c b/network/connection.c
new file mode 100644
index 0000000..7a82467
--- /dev/null
+++ b/network/connection.c
@@ -0,0 +1,658 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/bnep.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "logging.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 const char *prefix = 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 inline DBusMessage *not_supported(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+							"Not supported");
+}
+
+static inline DBusMessage *already_connected(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Device already connected");
+}
+
+static inline DBusMessage *not_connected(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Device not connected");
+}
+
+static inline DBusMessage *not_permited(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Operation not permited");
+}
+
+static inline DBusMessage *connection_attempt_failed(DBusMessage *msg,
+							const char *err)
+{
+	return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".ConnectionAttemptFailed",
+				err ? err : "Connection attempt failed");
+}
+
+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 = "";
+		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, "Device",
+					DBUS_TYPE_STRING, &property);
+		emit_property_changed(connection, nc->peer->path,
+					NETWORK_PEER_INTERFACE, "UUID",
+					DBUS_TYPE_STRING, &property);
+		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, 16);
+	strncpy(nc->dev, prefix, sizeof(nc->dev) - 1);
+
+	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 = connection_attempt_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];
+	gsize r;
+	int sk;
+	const char *pdev, *uuid;
+	gboolean connected;
+
+	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;
+	}
+
+	memset(pkt, 0, BNEP_MTU);
+	if (g_io_channel_read(chan, pkt, sizeof(pkt) - 1,
+				&r) != G_IO_ERROR_NONE) {
+		error("IO Channel read error");
+		goto failed;
+	}
+
+	if (r <= 0) {
+		error("No packet received on l2cap socket");
+		goto failed;
+	}
+
+	errno = EPROTO;
+
+	if (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;
+	}
+
+	sk = g_io_channel_unix_get_fd(chan);
+
+	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, nc->id);
+	pdev = nc->dev;
+	uuid = bnep_uuid(nc->id);
+
+	g_dbus_send_reply(connection, nc->msg,
+			DBUS_TYPE_STRING, &pdev,
+			DBUS_TYPE_INVALID);
+
+	connected = TRUE;
+	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, "Device",
+				DBUS_TYPE_STRING, &pdev);
+	emit_property_changed(connection, nc->peer->path,
+				NETWORK_PEER_INTERFACE, "UUID",
+				DBUS_TYPE_STRING, &uuid);
+
+	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 not_supported(msg);
+
+	if (nc->state != DISCONNECTED)
+		return 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 = connection_attempt_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 not_permited(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 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);
+
+	/* Device */
+	property = nc ? nc->dev : "";
+	dict_append_entry(&dict, "Device", 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;
+
+	debug("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;
+	}
+
+	debug("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, 16);
+	strncpy(nc->dev, prefix, sizeof(nc->dev) - 1);
+	nc->state = DISCONNECTED;
+	nc->peer = peer;
+
+	peer->connections = g_slist_append(peer->connections, nc);
+
+	return 0;
+}
+
+int connection_init(DBusConnection *conn, const char *iface_prefix)
+{
+	connection = dbus_connection_ref(conn);
+	prefix = iface_prefix;
+
+	return 0;
+}
+
+void connection_exit()
+{
+	dbus_connection_unref(connection);
+	connection = NULL;
+	prefix = NULL;
+}
diff --git a/network/connection.h b/network/connection.h
new file mode 100644
index 0000000..ba1fdf6
--- /dev/null
+++ b/network/connection.h
@@ -0,0 +1,28 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+int connection_init(DBusConnection *conn, const char *iface_prefix);
+void connection_exit();
+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
index 0000000..97838a2
--- /dev/null
+++ b/network/main.c
@@ -0,0 +1,59 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..bd52279
--- /dev/null
+++ b/network/manager.c
@@ -0,0 +1,393 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/bnep.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "logging.h"
+
+#include "adapter.h"
+#include "device.h"
+#include "bridge.h"
+#include "manager.h"
+#include "common.h"
+#include "connection.h"
+#include "server.h"
+
+#define IFACE_PREFIX "bnep%d"
+#define GN_IFACE  "pan0"
+#define NAP_IFACE "pan1"
+
+static struct btd_adapter_driver network_panu_server_driver;
+static struct btd_adapter_driver network_gn_server_driver;
+static struct btd_adapter_driver network_nap_server_driver;
+
+static DBusConnection *connection = NULL;
+
+static struct network_conf {
+	gboolean connection_enabled;
+	gboolean server_enabled;
+	gboolean security;
+	char *iface_prefix;
+	char *panu_script;
+	char *gn_script;
+	char *nap_script;
+	char *gn_iface;
+	char *nap_iface;
+} conf = {
+	.connection_enabled = TRUE,
+	.server_enabled = TRUE,
+	.security = TRUE,
+	.iface_prefix = NULL,
+	.panu_script = NULL,
+	.gn_script = NULL,
+	.nap_script = NULL,
+	.gn_iface = NULL,
+	.nap_iface = NULL
+};
+
+static void conf_cleanup(void)
+{
+	g_free(conf.iface_prefix);
+	g_free(conf.panu_script);
+	g_free(conf.gn_script);
+	g_free(conf.nap_script);
+	g_free(conf.gn_iface);
+	g_free(conf.nap_iface);
+}
+
+static void read_config(const char *file)
+{
+	GKeyFile *keyfile;
+	GError *err = NULL;
+	char **disabled;
+
+	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_clear_error(&err);
+		goto done;
+	}
+
+	disabled = g_key_file_get_string_list(keyfile, "General",
+						"Disable", NULL, &err);
+	if (err) {
+		debug("%s: %s", file, err->message);
+		g_clear_error(&err);
+	} else {
+		int i;
+		for (i = 0; disabled[i] != NULL; i++) {
+			if (g_str_equal(disabled[i], "Connection"))
+				conf.connection_enabled = FALSE;
+			else if (g_str_equal(disabled[i], "Server"))
+				conf.server_enabled = FALSE;
+		}
+		g_strfreev(disabled);
+	}
+
+	conf.security = !g_key_file_get_boolean(keyfile, "General",
+						"DisableSecurity", &err);
+	if (err) {
+		debug("%s: %s", file, err->message);
+		g_clear_error(&err);
+	}
+
+#if 0
+	conf.panu_script = g_key_file_get_string(keyfile, "PANU Role",
+						"Script", &err);
+	if (err) {
+		debug("%s: %s", file, err->message);
+		g_clear_error(&err);
+	}
+
+	conf.gn_script = g_key_file_get_string(keyfile, "GN Role",
+						"Script", &err);
+	if (err) {
+		debug("%s: %s", file, err->message);
+		g_clear_error(&err);
+	}
+
+	conf.nap_script = g_key_file_get_string(keyfile, "NAP Role",
+						"Script", &err);
+	if (err) {
+		debug("%s: %s", file, err->message);
+		g_clear_error(&err);
+	}
+#endif
+
+	conf.iface_prefix = g_key_file_get_string(keyfile, "PANU Role",
+						"Interface", &err);
+	if (err) {
+		debug("%s: %s", file, err->message);
+		g_clear_error(&err);
+	}
+
+	conf.gn_iface = g_key_file_get_string(keyfile, "GN Role",
+						"Interface", &err);
+	if (err) {
+		debug("%s: %s", file, err->message);
+		g_clear_error(&err);
+	}
+
+	conf.nap_iface = g_key_file_get_string(keyfile, "NAP Role",
+						"Interface", &err);
+	if (err) {
+		debug("%s: %s", file, err->message);
+		g_clear_error(&err);
+	}
+
+done:
+	g_key_file_free(keyfile);
+
+	if (!conf.iface_prefix)
+		conf.iface_prefix = g_strdup(IFACE_PREFIX);
+
+	if (!conf.gn_iface)
+		conf.gn_iface = g_strdup(GN_IFACE);
+	if (!conf.nap_iface)
+		conf.nap_iface = g_strdup(NAP_IFACE);
+
+	debug("Config options: InterfacePrefix=%s, PANU_Script=%s, "
+		"GN_Script=%s, NAP_Script=%s, GN_Interface=%s, "
+		"NAP_Interface=%s, Security=%s",
+		conf.iface_prefix, conf.panu_script, conf.gn_script,
+		conf.nap_script, conf.gn_iface, conf.nap_iface,
+		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, uint16_t id)
+{
+	const gchar *path = adapter_get_path(adapter);
+
+	DBG("path %s", path);
+
+	if (!conf.server_enabled)
+		return 0;
+
+	return server_register(adapter, id);
+}
+
+static void network_server_remove(struct btd_adapter *adapter, uint16_t id)
+{
+	const gchar *path = adapter_get_path(adapter);
+
+	DBG("path %s", path);
+
+	server_unregister(adapter, id);
+}
+
+static int panu_server_probe(struct btd_adapter *adapter)
+{
+	return network_server_probe(adapter, BNEP_SVC_PANU);
+}
+
+static int gn_server_probe(struct btd_adapter *adapter)
+{
+	return network_server_probe(adapter, BNEP_SVC_GN);
+}
+
+static int nap_server_probe(struct btd_adapter *adapter)
+{
+	return network_server_probe(adapter, BNEP_SVC_NAP);
+}
+
+static void panu_server_remove(struct btd_adapter *adapter)
+{
+	network_server_remove(adapter, BNEP_SVC_PANU);
+}
+
+static void gn_server_remove(struct btd_adapter *adapter)
+{
+	network_server_remove(adapter, BNEP_SVC_GN);
+}
+
+static void nap_server_remove(struct btd_adapter *adapter)
+{
+	network_server_remove(adapter, BNEP_SVC_NAP);
+}
+
+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_panu_server_driver = {
+	.name	= "network-panu-server",
+	.probe	= panu_server_probe,
+	.remove	= panu_server_remove,
+};
+
+static struct btd_adapter_driver network_gn_server_driver = {
+	.name	= "network-gn-server",
+	.probe	= gn_server_probe,
+	.remove	= gn_server_remove,
+};
+
+static struct btd_adapter_driver network_nap_server_driver = {
+	.name	= "network-nap-server",
+	.probe	= nap_server_probe,
+	.remove	= nap_server_remove,
+};
+
+int network_manager_init(DBusConnection *conn)
+{
+	read_config(CONFIGDIR "/network.conf");
+
+	if (bnep_init(conf.panu_script, conf.gn_script, conf.nap_script)) {
+		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 (bridge_init(conf.gn_iface, conf.nap_iface) < 0) {
+		error("Can't init bridge module");
+		return -1;
+	}
+
+	if (server_init(conn, conf.iface_prefix, conf.security) < 0)
+		return -1;
+
+	/* Register PANU, GN and NAP servers if they don't exist */
+	btd_register_adapter_driver(&network_panu_server_driver);
+	btd_register_adapter_driver(&network_gn_server_driver);
+	btd_register_adapter_driver(&network_nap_server_driver);
+
+	if (connection_init(conn, conf.iface_prefix) < 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)
+{
+	if (conf.server_enabled)
+		server_exit();
+
+	if (conf.connection_enabled) {
+		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_panu_server_driver);
+	btd_unregister_adapter_driver(&network_gn_server_driver);
+	btd_unregister_adapter_driver(&network_nap_server_driver);
+
+	dbus_connection_unref(connection);
+	connection = NULL;
+
+	bnep_cleanup();
+	bridge_cleanup();
+	conf_cleanup();
+}
diff --git a/network/manager.h b/network/manager.h
new file mode 100644
index 0000000..e26d60b
--- /dev/null
+++ b/network/manager.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+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
index 0000000..4c24c8d
--- /dev/null
+++ b/network/network.conf
@@ -0,0 +1,33 @@
+# Configuration file for the network service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+
+# Disable link encryption: default=false
+#DisableSecurity=true
+
+[PANU Role]
+
+# Network interface name for PANU for connections. default:bnep%d
+# (up to 16 characters)
+#Interface=
+
+# PAN user connection interface up script. default:none
+Script=avahi-autoipd
+
+[GN Role]
+
+# Network Interface name for Group Network server. default:pan0
+#Interface=
+
+# Group Network connection interface up script. default:none
+Script=avahi-autoipd
+
+[NAP Role]
+
+# Network Interface name for Network Access Point server. default:pan1
+#Interface=
+
+# Network Access Point connection interface up script. default:none
+Script=dhclient
diff --git a/network/server.c b/network/server.c
new file mode 100644
index 0000000..23d3b64
--- /dev/null
+++ b/network/server.c
@@ -0,0 +1,918 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/bnep.h>
+#include <bluetooth/l2cap.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 "logging.h"
+#include "error.h"
+#include "sdpd.h"
+#include "btio.h"
+#include "glib-helper.h"
+
+#include "bridge.h"
+#include "common.h"
+#include "server.h"
+
+#define NETWORK_PEER_INTERFACE "org.bluez.NetworkPeer"
+#define NETWORK_HUB_INTERFACE "org.bluez.NetworkHub"
+#define NETWORK_ROUTER_INTERFACE "org.bluez.NetworkRouter"
+#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		*range;		/* IP Address range */
+	gboolean	enable;		/* Enable flag */
+	uint32_t	record_id;	/* Service record id */
+	uint16_t	id;		/* Service class identifier */
+	GSList		*sessions;	/* Active connections */
+	struct network_adapter *na;	/* Adapter reference */
+};
+
+static DBusConnection *connection = NULL;
+static GSList *adapters = NULL;
+static const char *prefix = NULL;
+static gboolean security = TRUE;
+
+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;
+}
+
+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 = "BlueZ PAN 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];
+	const char *bridge;
+	int err, nsk;
+
+	/* Server can be disabled in the meantime */
+	if (ns->enable == FALSE)
+		return -EPERM;
+
+	memset(devname, 0, 16);
+	strncpy(devname, prefix, sizeof(devname) - 1);
+
+	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);
+
+	bridge = bridge_get_name(ns->id);
+	if (bridge) {
+		if (bridge_add_interface(ns->id, devname) < 0) {
+			error("Can't add %s to the bridge %s: %s(%d)",
+					devname, bridge, strerror(errno),
+					errno);
+			return -EPERM;
+		}
+
+		bnep_if_up(devname, 0);
+	} else
+		bnep_if_up(devname, ns->id);
+
+	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);
+}
+
+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;
+
+	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;
+	}
+
+	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 || ns->enable == FALSE) {
+		error("Server unavailable: (0x%x)", dst_role);
+		goto reply;
+	}
+
+	if (server_connadd(ns, na->setup, dst_role) < 0)
+		goto reply;
+
+	na->setup = NULL;
+
+	rsp = BNEP_SUCCESS;
+
+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;
+	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;
+	}
+
+	debug("BNEP: incoming connect from %s", address);
+
+	if (na->setup) {
+		error("Refusing connect from %s: setup in progress", address);
+		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, const char *iface_prefix,
+		gboolean secure)
+{
+	security = secure;
+	connection = dbus_connection_ref(conn);
+	prefix = iface_prefix;
+
+	if (bridge_create(BNEP_SVC_GN) < 0)
+		error("Can't create GN bridge");
+
+	return 0;
+}
+
+void server_exit()
+{
+	if (bridge_remove(BNEP_SVC_GN) < 0)
+		error("Can't remove GN bridge");
+
+	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;
+	}
+
+	debug("register_server_record: got record id 0x%x", record->handle);
+
+	return record->handle;
+}
+
+
+static inline DBusMessage *failed(DBusMessage *msg, const char *description)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+				description);
+}
+
+static inline DBusMessage *invalid_arguments(DBusMessage *msg,
+					const char *description)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+				description);
+}
+
+static DBusMessage *enable(DBusConnection *conn,
+			DBusMessage *msg, void *data)
+{
+	struct network_server *ns = data;
+	DBusMessage *reply;
+
+	if (ns->enable)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".AlreadyExist",
+						"Server already enabled");
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	/* Add the service record */
+	ns->record_id = register_server_record(ns);
+	if (!ns->record_id) {
+		dbus_message_unref(reply);
+		return failed(msg, "Service record registration failed");
+	}
+
+	ns->enable = TRUE;
+
+	return reply;
+}
+
+static DBusMessage *disable(DBusConnection *conn,
+				DBusMessage *msg, void *data)
+{
+	struct network_server *ns = data;
+	DBusMessage *reply;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (!ns->enable)
+		return failed(msg, "Not enabled");
+
+	/* Remove the service record */
+	if (ns->record_id) {
+		remove_record_from_server(ns->record_id);
+		ns->record_id = 0;
+	}
+
+	ns->enable = FALSE;
+
+	g_slist_foreach(ns->sessions, (GFunc) session_free, NULL);
+	g_slist_free(ns->sessions);
+
+	return reply;
+}
+
+static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg,
+				const char *name, void *data)
+{
+	struct network_server *ns = data;
+	DBusMessage *reply;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (!name || (strlen(name) == 0))
+		return invalid_arguments(msg, "Invalid name");
+
+	if (ns->name)
+		g_free(ns->name);
+	ns->name = g_strdup(name);
+
+	if (ns->enable && ns->record_id) {
+		uint32_t handle = register_server_record(ns);
+		if (!handle) {
+			dbus_message_unref(reply);
+			return failed(msg,
+				"Service record attribute update failed");
+		}
+
+		remove_record_from_server(ns->record_id);
+		ns->record_id = handle;
+	}
+
+	return reply;
+}
+
+static DBusMessage *get_properties(DBusConnection *conn,
+				DBusMessage *msg, void *data)
+{
+	struct network_server *ns = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	const char *uuid;
+
+	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, "Name", DBUS_TYPE_STRING, &ns->name);
+
+	uuid = bnep_uuid(ns->id);
+	dict_append_entry(&dict, "Uuid", DBUS_TYPE_STRING, &uuid);
+
+	dict_append_entry(&dict, "Enabled", DBUS_TYPE_BOOLEAN, &ns->enable);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+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 invalid_arguments(msg, "Not a dict");
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+		return invalid_arguments(msg, "Key not a string");
+
+	dbus_message_iter_get_basic(&iter, &property);
+	dbus_message_iter_next(&iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+		return invalid_arguments(msg, "Value not a variant");
+	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 invalid_arguments(msg, "Value not string");
+		dbus_message_iter_get_basic(&sub, &name);
+
+		return set_name(conn, msg, name, data);
+	} else if (g_str_equal("Enabled", property)) {
+		gboolean enabled;
+
+		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+			return invalid_arguments(msg, "Value not boolean");
+		dbus_message_iter_get_basic(&sub, &enabled);
+
+		return enabled ? enable(conn, msg, data) :
+				disable(conn, msg, data);
+	}
+
+	return invalid_arguments(msg, "Property does not exist");
+}
+
+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);
+
+	if (ns->iface)
+		g_free(ns->iface);
+
+	if (ns->name)
+		g_free(ns->name);
+
+	if (ns->range)
+		g_free(ns->range);
+
+	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;
+
+	debug("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[] = {
+	{ "SetProperty",	"sv",	"",	set_property },
+	{ "GetProperties",	"",	"a{sv}",get_properties },
+	{ }
+};
+
+static GDBusSignalTable server_signals[] = {
+	{ "PropertyChanged",		"sv"		},
+	{ }
+};
+
+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, uint16_t id)
+{
+	struct network_adapter *na;
+	struct network_server *ns;
+	const char *path;
+
+	na = find_adapter(adapters, adapter);
+	if (!na) {
+		na = create_adapter(adapter);
+		if (!na)
+			return -EINVAL;
+		adapters = g_slist_append(adapters, na);
+	}
+
+	ns = find_server(na->servers, id);
+	if (ns)
+		return 0;
+
+	ns = g_new0(struct network_server, 1);
+
+	switch (id) {
+	case BNEP_SVC_PANU:
+		ns->iface = g_strdup(NETWORK_PEER_INTERFACE);
+		ns->name = g_strdup("BlueZ PANU service");
+		break;
+	case BNEP_SVC_GN:
+		ns->iface = g_strdup(NETWORK_HUB_INTERFACE);
+		ns->name = g_strdup("BlueZ GN service");
+		break;
+	case BNEP_SVC_NAP:
+		ns->iface = g_strdup(NETWORK_ROUTER_INTERFACE);
+		ns->name = g_strdup("BlueZ NAP service");
+		break;
+	}
+
+	path = adapter_get_path(adapter);
+
+	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;
+	}
+
+	adapter_get_address(adapter, &ns->src);
+	ns->id = id;
+	ns->na = na;
+	ns->record_id = register_server_record(ns);
+	ns->enable = TRUE;
+	na->servers = g_slist_append(na->servers, ns);
+
+	debug("Registered interface %s on path %s", ns->iface, path);
+
+	return 0;
+}
+
+int server_unregister(struct btd_adapter *adapter, uint16_t id)
+{
+	struct network_adapter *na;
+	struct network_server *ns;
+
+	na = find_adapter(adapters, adapter);
+	if (!na)
+		return -EINVAL;
+
+	ns = find_server(na->servers, id);
+	if (!ns)
+		return -EINVAL;
+
+	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
index 0000000..5072c47
--- /dev/null
+++ b/network/server.h
@@ -0,0 +1,34 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+int server_init(DBusConnection *conn, const char *iface_prefix,
+		gboolean secure);
+void server_exit();
+int server_register(struct btd_adapter *adapter, uint16_t id);
+int server_unregister(struct btd_adapter *adapter, uint16_t id);
+int server_register_from_file(const char *path, const bdaddr_t *src,
+		uint16_t id, const char *filename);
+
+int server_store(const char *path);
+
+int server_find_data(const char *path, const char *pattern);
diff --git a/plugins/Android.mk b/plugins/Android.mk
new file mode 100755
index 0000000..6302d00
--- /dev/null
+++ b/plugins/Android.mk
@@ -0,0 +1,38 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# libplugin
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	hciops.c \
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\" \
+	-DBLUETOOTH_PLUGIN_BUILTIN \
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+        $(LOCAL_PATH)/../common \
+        $(LOCAL_PATH)/../gdbus \
+        $(LOCAL_PATH)/../src \
+        $(call include-path-for, glib) \
+        $(call include-path-for, dbus) \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetoothd \
+	libbluetooth \
+	libcutils \
+	libdbus
+
+LOCAL_STATIC_LIBRARIES := \
+	libbluez-common-static \
+	libglib_static
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/bluez-plugin
+LOCAL_UNSTRIPPED_PATH := $(TARGET_OUT_SHARED_LIBRARIES_UNSTRIPPED)/bluez-plugin
+LOCAL_MODULE:=libbuiltinplugin
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
new file mode 100644
index 0000000..9d9f970
--- /dev/null
+++ b/plugins/Makefile.am
@@ -0,0 +1,71 @@
+
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES =
+
+builtin_modules =
+builtin_sources =
+builtin_cflags =
+
+if SERVICEPLUGIN
+builtin_modules += service
+builtin_sources += service.c
+endif
+
+builtin_modules += hciops
+builtin_sources += hciops.c
+
+if NETLINK
+plugin_LTLIBRARIES += netlink.la
+netlink_la_LIBADD = @NETLINK_LIBS@
+endif
+
+builtin_modules += hal
+builtin_sources += hal.c
+
+builtin_modules += storage
+builtin_sources += storage.c
+
+noinst_LTLIBRARIES = libbuiltin.la echo.la
+
+libbuiltin_la_SOURCES = $(builtin_sources)
+libbuiltin_la_LDFLAGS =
+libbuiltin_la_CFLAGS = $(AM_CFLAGS) \
+			$(builtin_cflags) -DBLUETOOTH_PLUGIN_BUILTIN
+
+BUILT_SOURCES = builtin.h
+
+nodist_libbuiltin_la_SOURCES = $(BUILT_SOURCES)
+
+AM_LDFLAGS = -module -avoid-version -no-undefined
+
+AM_CFLAGS = -fvisibility=hidden @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ \
+			@GLIB_CFLAGS@ @GDBUS_CFLAGS@ @NETLINK_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/src
+
+CLEANFILES = $(BUILT_SOURCES)
+
+MAINTAINERCLEANFILES = Makefile.in
+
+builtin.h:
+	echo "" > $@
+	list='$(builtin_modules)'; for i in $$list; \
+	  do echo "extern struct bluetooth_plugin_desc __bluetooth_builtin_$$i;" >> $@; done
+	echo "" >> $@
+	echo "static struct bluetooth_plugin_desc *__bluetooth_builtin[] = {" >> $@
+	list='$(builtin_modules)'; for i in $$list; \
+	  do echo "&__bluetooth_builtin_$$i," >> $@; done
+	echo "NULL };" >> $@
+
+all-local:
+	@$(LN_S) -f $(top_srcdir)/input/.libs/input.so
+	@$(LN_S) -f $(top_srcdir)/audio/.libs/audio.so
+	@$(LN_S) -f $(top_srcdir)/serial/.libs/serial.so
+	@$(LN_S) -f $(top_srcdir)/network/.libs/network.so
+
+clean-local:
+	@rm -f network.so
+	@rm -f serial.so
+	@rm -f audio.so
+	@rm -f input.so
diff --git a/plugins/builtin.h b/plugins/builtin.h
new file mode 100644
index 0000000..ebc7d62
--- /dev/null
+++ b/plugins/builtin.h
@@ -0,0 +1,26 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+extern struct bluetooth_plugin_desc __bluetooth_builtin_hciops;
+static struct bluetooth_plugin_desc *__bluetooth_builtin[] = {
+	&__bluetooth_builtin_hciops,
+	NULL };
diff --git a/plugins/echo.c b/plugins/echo.c
new file mode 100644
index 0000000..919d085
--- /dev/null
+++ b/plugins/echo.c
@@ -0,0 +1,167 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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 "logging.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)
+{
+	debug("Setup echo plugin");
+
+	return btd_register_adapter_driver(&echo_server);
+}
+
+static void echo_exit(void)
+{
+	debug("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/hal.c b/plugins/hal.c
new file mode 100644
index 0000000..6815f28
--- /dev/null
+++ b/plugins/hal.c
@@ -0,0 +1,162 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <dbus/dbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "plugin.h"
+#include "adapter.h"
+#include "logging.h"
+#include "dbus-hci.h"
+
+static void formfactor_reply(DBusPendingCall *call, void *user_data)
+{
+	struct btd_adapter *adapter = user_data;
+	const char *formfactor = NULL;
+	DBusMessage *reply;
+	uint8_t cls[3], minor = 0;
+	int dd;
+
+	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;
+	}
+
+	debug("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);
+
+	dd = hci_open_dev(adapter_get_dev_id(adapter));
+	if (dd < 0)
+		return;
+
+	if (hci_read_class_of_dev(dd, cls, 500) < 0) {
+		hci_close_dev(dd);
+		return;
+	}
+
+	debug("Current device class is 0x%02x%02x%02x\n",
+						cls[2], cls[1], cls[0]);
+
+	/* Computer major class */
+	debug("Setting 0x%06x for major/minor device class", (1 << 8) | minor);
+
+	hci_close_dev(dd);
+
+	set_major_and_minor_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
index 0000000..4df799b
--- /dev/null
+++ b/plugins/hciops.c
@@ -0,0 +1,773 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/socket.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 <glib.h>
+
+#include <dbus/dbus.h>
+
+#include "hcid.h"
+#include "sdpd.h"
+#include "adapter.h"
+#include "plugin.h"
+#include "logging.h"
+#include "manager.h"
+#include "storage.h"
+
+static int child_pipe[2] = { -1, -1 };
+
+static guint child_io_id = 0;
+static guint ctl_io_id = 0;
+
+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
+		debug("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 configure_device(int index)
+{
+	struct hci_dev_info di;
+	uint16_t policy;
+	int dd;
+
+	if (hci_devinfo(index, &di) < 0)
+		return;
+
+	if (hci_test_bit(HCI_RAW, &di.flags))
+		return;
+
+	dd = hci_open_dev(index);
+	if (dd < 0) {
+		error("Can't open device hci%d: %s (%d)",
+						index, strerror(errno), errno);
+		return;
+	}
+
+	/* Set device name */
+	if ((main_opts.flags & (1 << HCID_SET_NAME)) && main_opts.name) {
+		change_local_name_cp cp;
+
+		memset(cp.name, 0, sizeof(cp.name));
+		expand_name((char *) cp.name, sizeof(cp.name),
+						main_opts.name, index);
+
+		hci_send_cmd(dd, OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME,
+					CHANGE_LOCAL_NAME_CP_SIZE, &cp);
+	}
+
+	/* Set device class */
+	if ((main_opts.flags & (1 << HCID_SET_CLASS))) {
+		write_class_of_dev_cp cp;
+		uint32_t class;
+		uint8_t cls[3];
+
+		if (read_local_class(&di.bdaddr, cls) < 0) {
+			class = htobl(main_opts.class);
+			cls[2] = get_service_classes(&di.bdaddr);
+			memcpy(cp.dev_class, &class, 3);
+		} else {
+			if (!(main_opts.scan & SCAN_INQUIRY))
+				cls[1] &= 0xdf; /* Clear discoverable bit */
+			cls[2] = get_service_classes(&di.bdaddr);
+			memcpy(cp.dev_class, cls, 3);
+		}
+
+		hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV,
+					WRITE_CLASS_OF_DEV_CP_SIZE, &cp);
+	}
+
+	/* 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(dd, OGF_HOST_CTL, OCF_WRITE_PAGE_TIMEOUT,
+					WRITE_PAGE_TIMEOUT_CP_SIZE, &cp);
+	}
+
+	/* Set default link policy */
+	policy = htobs(main_opts.link_policy);
+	hci_send_cmd(dd, OGF_LINK_POLICY,
+				OCF_WRITE_DEFAULT_LINK_POLICY, 2, &policy);
+
+	hci_close_dev(dd);
+}
+
+static void init_device(int index)
+{
+	struct hci_dev_req dr;
+	struct hci_dev_info di;
+	pid_t pid;
+	int dd;
+
+	/* 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:
+			debug("child %d forked", pid);
+			return;
+	}
+
+	dd = hci_open_dev(index);
+	if (dd < 0) {
+		error("Can't open device hci%d: %s (%d)",
+					index, strerror(errno), errno);
+		exit(1);
+	}
+
+	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);
+	}
+
+	/* Set link policy */
+	dr.dev_opt = main_opts.link_policy;
+	if (ioctl(dd, HCISETLINKPOL, (unsigned long) &dr) < 0 &&
+							errno != ENETDOWN) {
+		error("Can't set link policy 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;
+	}
+
+	if (hci_devinfo(index, &di) < 0)
+		goto fail;
+
+	if (hci_test_bit(HCI_RAW, &di.flags))
+		goto done;
+
+done:
+	hci_close_dev(dd);
+	exit(0);
+
+fail:
+	hci_close_dev(dd);
+	exit(1);
+}
+
+static void device_devreg_setup(int index)
+{
+	struct hci_dev_info di;
+	gboolean devup;
+
+	init_device(index);
+
+	memset(&di, 0, sizeof(di));
+
+	if (hci_devinfo(index, &di) < 0)
+		return;
+
+	devup = hci_test_bit(HCI_UP, &di.flags);
+
+	if (!hci_test_bit(HCI_RAW, &di.flags))
+		manager_register_adapter(index, devup);
+}
+
+static void device_devup_setup(int index)
+{
+	configure_device(index);
+
+	start_security_manager(index);
+
+	/* Return value 1 means ioctl(DEVDOWN) was performed */
+	if (manager_start_adapter(index) == 1)
+		stop_security_manager(index);
+}
+
+static void device_event(int event, int index)
+{
+	switch (event) {
+	case HCI_DEV_REG:
+		info("HCI dev %d registered", index);
+		device_devreg_setup(index);
+		break;
+
+	case HCI_DEV_UNREG:
+		info("HCI dev %d unregistered", index);
+		manager_unregister_adapter(index);
+		break;
+
+	case HCI_DEV_UP:
+		info("HCI dev %d up", index);
+		device_devup_setup(index);
+		break;
+
+	case HCI_DEV_DOWN:
+		info("HCI dev %d down", index);
+		manager_stop_adapter(index);
+		stop_security_manager(index);
+		break;
+	}
+}
+
+static int init_known_adapters(int ctl)
+{
+	struct hci_dev_list_req *dl;
+	struct hci_dev_req *dr;
+	int i, err;
+
+	dl = g_try_malloc0(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t));
+	if (!dl) {
+		err = -errno;
+		error("Can't allocate devlist buffer: %s (%d)",
+							strerror(errno), errno);
+		return err;
+	}
+
+	dl->dev_num = HCI_MAX_DEV;
+	dr = dl->dev_req;
+
+	if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
+		err = -errno;
+		error("Can't get device list: %s (%d)",
+							strerror(errno), errno);
+		return err;
+	}
+
+	for (i = 0; i < dl->dev_num; i++, dr++) {
+		gboolean devup;
+
+		device_event(HCI_DEV_REG, dr->dev_id);
+
+		devup = hci_test_bit(HCI_UP, &dr->dev_opt);
+		if (devup)
+			device_event(HCI_DEV_UP, dr->dev_id);
+	}
+
+	g_free(dl);
+	return 0;
+}
+
+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;
+	size_t len;
+	GIOError err;
+
+	ptr = buf;
+
+	err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len);
+	if (err) {
+		if (err == G_IO_ERROR_AGAIN)
+			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;
+
+	if (child_pipe[0] != -1)
+		return -EALREADY;
+
+	if (pipe(child_pipe) < 0) {
+		err = -errno;
+		error("pipe(): %s (%d)", strerror(errno), errno);
+		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(errno),
+								errno);
+		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(errno), errno);
+		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(errno), errno);
+		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);
+
+	/* Initialize already connected devices */
+	return init_known_adapters(sock);
+}
+
+static void hciops_cleanup(void)
+{
+	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_start(int index)
+{
+	int dd;
+	int err = 0;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	if (ioctl(dd, HCIDEVUP, index) == 0)
+		goto done; /* on success */
+
+	if (errno != EALREADY) {
+		err = -errno;
+		error("Can't init device hci%d: %s (%d)",
+				index, strerror(errno), errno);
+	}
+
+done:
+	hci_close_dev(dd);
+	return err;
+}
+
+static int hciops_stop(int index)
+{
+	int dd;
+	int err = 0;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	if (ioctl(dd, HCIDEVDOWN, index) == 0)
+		goto done; /* on success */
+
+	if (errno != EALREADY) {
+		err = -errno;
+		error("Can't stop device hci%d: %s (%d)",
+				index, strerror(errno), errno);
+	}
+
+done:
+	hci_close_dev(dd);
+	return err;
+}
+
+static int hciops_powered(int index, gboolean powered)
+{
+	int dd;
+	uint8_t mode = SCAN_DISABLED;
+
+	if (powered)
+		return hciops_start(index);
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE,
+					1, &mode);
+
+	hci_close_dev(dd);
+
+	return hciops_stop(index);
+}
+
+static int hciops_connectable(int index)
+{
+	int dd;
+	uint8_t mode = SCAN_PAGE;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE,
+					1, &mode);
+
+	hci_close_dev(dd);
+
+	return 0;
+}
+
+static int hciops_discoverable(int index)
+{
+	int dd;
+	uint8_t mode = (SCAN_PAGE | SCAN_INQUIRY);
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE,
+					1, &mode);
+
+	hci_close_dev(dd);
+
+	return 0;
+}
+
+static int hciops_set_limited_discoverable(int index, const uint8_t *cls,
+							gboolean limited)
+{
+	int dd, err = 0;
+	uint32_t dev_class;
+	int num = (limited ? 2 : 1);
+	uint8_t lap[] = { 0x33, 0x8b, 0x9e, 0x00, 0x8b, 0x9e };
+	/*
+	 * 1: giac
+	 * 2: giac + liac
+	 */
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	if (hci_write_current_iac_lap(dd, num, lap, HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't write current IAC LAP: %s(%d)",
+						strerror(errno), errno);
+		goto done;
+	}
+
+	if (limited) {
+		if (cls[1] & 0x20)
+			goto done; /* Already limited */
+
+		dev_class = (cls[2] << 16) | ((cls[1] | 0x20) << 8) | cls[0];
+	} else {
+		if (!(cls[1] & 0x20))
+			goto done; /* Already clear */
+
+		dev_class = (cls[2] << 16) | ((cls[1] & 0xdf) << 8) | cls[0];
+	}
+
+	if (hci_write_class_of_dev(dd, dev_class, HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't write class of device: %s (%d)",
+						strerror(errno), errno);
+		goto done;
+	}
+done:
+	hci_close_dev(dd);
+	return err;
+}
+
+static int hciops_start_discovery(int index, gboolean periodic)
+{
+	uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
+	int dd, err = 0;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	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  = 0x08;
+		cp.num_rsp = 0x00;
+
+		err = hci_send_cmd(dd, 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 = 0x08;
+		inq_cp.num_rsp = 0x00;
+
+		err = hci_send_cmd(dd, OGF_LINK_CTL, OCF_INQUIRY,
+					INQUIRY_CP_SIZE, &inq_cp);
+	}
+
+	if (err < 0)
+		err = -errno;
+
+	hci_close_dev(dd);
+
+	return err;
+}
+
+static int hciops_stop_discovery(int index)
+{
+	struct hci_dev_info di;
+	int dd, err = 0;
+
+	if (hci_devinfo(index, &di) < 0)
+		return -errno;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	if (hci_test_bit(HCI_INQUIRY, &di.flags))
+		err = hci_send_cmd(dd, OGF_LINK_CTL, OCF_INQUIRY_CANCEL,
+				0, 0);
+	else
+		err = hci_send_cmd(dd, OGF_LINK_CTL, OCF_EXIT_PERIODIC_INQUIRY,
+				0, 0);
+	if (err < 0)
+		err = -errno;
+
+	hci_close_dev(dd);
+
+	return err;
+}
+
+static int hciops_resolve_name(int index, bdaddr_t *bdaddr)
+{
+	remote_name_req_cp cp;
+	int dd, err = 0;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	memset(&cp, 0, sizeof(cp));
+	bacpy(&cp.bdaddr, bdaddr);
+	cp.pscan_rep_mode = 0x02;
+
+	err = hci_send_cmd(dd, OGF_LINK_CTL, OCF_REMOTE_NAME_REQ,
+					REMOTE_NAME_REQ_CP_SIZE, &cp);
+	if (err < 0)
+		err = -errno;
+
+	hci_close_dev(dd);
+
+	return err;
+}
+
+static int hciops_set_name(int index, const char *name)
+{
+	change_local_name_cp cp;
+	int dd, err = 0;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	memset(&cp, 0, sizeof(cp));
+	strncpy((char *) cp.name, name, sizeof(cp.name));
+
+	err = hci_send_cmd(dd, OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME,
+					CHANGE_LOCAL_NAME_CP_SIZE, &cp);
+	if (err < 0)
+		err = -errno;
+
+	hci_close_dev(dd);
+
+	return err;
+}
+
+static int hciops_read_name(int index)
+{
+	int dd, err = 0;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	err = hci_send_cmd(dd, OGF_HOST_CTL, OCF_READ_LOCAL_NAME, 0, 0);
+	if (err < 0)
+		err = -errno;
+
+	hci_close_dev(dd);
+
+	return err;
+}
+
+static int hciops_cancel_resolve_name(int index, bdaddr_t *bdaddr)
+{
+	remote_name_req_cancel_cp cp;
+	int dd, err = 0;
+
+	dd = hci_open_dev(index);
+	if (dd < 0)
+		return -EIO;
+
+	memset(&cp, 0, sizeof(cp));
+	bacpy(&cp.bdaddr, bdaddr);
+
+	err = hci_send_cmd(dd, OGF_LINK_CTL, OCF_REMOTE_NAME_REQ_CANCEL,
+					REMOTE_NAME_REQ_CANCEL_CP_SIZE, &cp);
+	if (err < 0)
+		err = -errno;
+
+	hci_close_dev(dd);
+
+	return err;
+}
+
+static struct btd_adapter_ops hci_ops = {
+	.setup = hciops_setup,
+	.cleanup = hciops_cleanup,
+	.start = hciops_start,
+	.stop = hciops_stop,
+	.set_powered = hciops_powered,
+	.set_connectable = hciops_connectable,
+	.set_discoverable = hciops_discoverable,
+	.set_limited_discoverable = hciops_set_limited_discoverable,
+	.start_discovery = hciops_start_discovery,
+	.stop_discovery = hciops_stop_discovery,
+	.resolve_name = hciops_resolve_name,
+	.cancel_resolve_name = hciops_cancel_resolve_name,
+	.set_name = hciops_set_name,
+	.read_name = hciops_read_name,
+};
+
+static int hciops_init(void)
+{
+	return btd_register_adapter_ops(&hci_ops);
+}
+static void hciops_exit(void)
+{
+	btd_adapter_cleanup_ops(&hci_ops);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(hciops, VERSION,
+		BLUETOOTH_PLUGIN_PRIORITY_LOW, hciops_init, hciops_exit)
diff --git a/plugins/netlink.c b/plugins/netlink.c
new file mode 100644
index 0000000..e85d264
--- /dev/null
+++ b/plugins/netlink.c
@@ -0,0 +1,127 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#include "plugin.h"
+#include "logging.h"
+
+static struct nl_handle *handle;
+static struct nl_cache *cache;
+static struct genl_family *family;
+
+static GIOChannel *channel;
+
+static gboolean channel_callback(GIOChannel *chan,
+					GIOCondition cond, void *user_data)
+{
+	int err;
+
+	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+		return FALSE;
+
+	debug("Message available on netlink channel");
+
+	err = nl_recvmsgs_default(handle);
+
+	return TRUE;
+}
+
+static int create_channel(int fd)
+{
+	channel = g_io_channel_unix_new(fd);
+	if (channel == NULL)
+		return -ENOMEM;
+
+	g_io_add_watch(channel, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+						channel_callback, NULL);
+
+	return 0;
+}
+
+static int netlink_init(void)
+{
+	info("Starting experimental netlink support");
+
+	handle = nl_handle_alloc();
+	if (!handle) {
+		error("Failed to allocate netlink handle");
+		return -ENOMEM;
+	}
+
+	if (genl_connect(handle) < 0) {
+		error("Failed to connect to generic netlink");
+		nl_handle_destroy(handle);
+		return -ENOLINK;
+	}
+
+	cache = genl_ctrl_alloc_cache(handle);
+	if (!cache) {
+		error("Failed to allocate generic netlink cache");
+		return -ENOMEM;
+		nl_handle_destroy(handle);
+	}
+
+	family = genl_ctrl_search_by_name(cache, "bluetooth");
+	if (!family) {
+		error("Failed to find Bluetooth netlink family");
+		nl_cache_free(cache);
+		nl_handle_destroy(handle);
+		return -ENOENT;
+	}
+
+	if (create_channel(nl_socket_get_fd(handle)) < 0)  {
+		error("Failed to create netlink IO channel");
+		genl_family_put(family);
+		nl_cache_free(cache);
+		nl_handle_destroy(handle);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void netlink_exit(void)
+{
+	g_io_channel_unref(channel);
+
+	genl_family_put(family);
+	nl_cache_free(cache);
+	nl_handle_destroy(handle);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(netlink, VERSION,
+		BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, netlink_init, netlink_exit)
diff --git a/plugins/service.c b/plugins/service.c
new file mode 100644
index 0000000..f89a7d6
--- /dev/null
+++ b/plugins/service.c
@@ -0,0 +1,865 @@
+/*
+ *
+ *  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 <stdlib.h>
+#include <string.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.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 "logging.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;
+			}
+		}
+		debug("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)
+				debug("Trouble adding attribute\n");
+
+			ctx_data->stack_head->data = NULL;
+			sdp_xml_data_free(ctx_data->stack_head);
+			ctx_data->stack_head = NULL;
+		} else {
+			debug("No data for attribute 0x%04x\n", 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;
+
+	debug("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 inline DBusMessage *invalid_arguments(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+					"Invalid arguments in method call");
+}
+
+static inline DBusMessage *not_available(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+							"Not Available");
+}
+
+static inline DBusMessage *failed(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "Failed");
+}
+
+static inline DBusMessage *failed_strerror(DBusMessage *msg, int err)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+			strerror(err));
+}
+
+static inline DBusMessage *not_authorized(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized",
+					"Not Authorized");
+}
+
+static inline DBusMessage *does_not_exist(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist",
+					"Does Not Exist");
+}
+
+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);
+
+	debug("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 g_dbus_create_error(msg,
+				ERROR_INTERFACE ".NotAvailable",
+				"Not Available");
+	}
+
+	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 g_dbus_create_error(msg,
+				ERROR_INTERFACE ".Failed",
+				strerror(EIO));
+	}
+
+	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 invalid_arguments(msg);
+
+	user_record = find_record(serv_adapter, handle,
+				dbus_message_get_sender(msg));
+	if (!user_record)
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".NotAvailable",
+				"Not Available");
+
+	sdp_record = sdp_xml_parse_record(record, len);
+	if (!sdp_record) {
+		error("Parsing of XML service record failed");
+		sdp_record_free(sdp_record);
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".Failed",
+				strerror(EIO));
+	}
+
+	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;
+
+	debug("remove record 0x%x", handle);
+
+	user_record = find_record(serv_adapter, handle, sender);
+	if (!user_record)
+		return -1;
+
+	debug("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 failed_strerror(msg, 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 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 = 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 failed(msg);
+
+	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 (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);
+
+	auth = g_new0(struct pending_auth, 1);
+	auth->msg = dbus_message_ref(msg);
+	auth->conn = dbus_connection_ref(connection);
+	auth->sender = user_record->sender;
+	memcpy(auth->uuid, uuid_str, MAX_LEN_UUID_STR);
+	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 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 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 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 = 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);
+}
+
+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	},
+	{ }
+};
+
+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)
+{
+	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;
+	}
+
+	debug("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
index 0000000..99b4952
--- /dev/null
+++ b/plugins/storage.c
@@ -0,0 +1,43 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <bluetooth/bluetooth.h>
+
+#include "plugin.h"
+#include "logging.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/rfcomm/Android.mk b/rfcomm/Android.mk
new file mode 100755
index 0000000..c6bb00a
--- /dev/null
+++ b/rfcomm/Android.mk
@@ -0,0 +1,29 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	kword.c \
+	main.c \
+	parser.c \
+	lexer.c
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\" \
+	-DCONFIGDIR=\"/etc/bluez\" \
+	-DNEED_PPOLL
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../common \
+	$(LOCAL_PATH)/../include
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_STATIC_LIBRARIES := \
+	libbluez-common-static
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=rfcomm
+
+include $(BUILD_EXECUTABLE)
diff --git a/rfcomm/Makefile.am b/rfcomm/Makefile.am
new file mode 100644
index 0000000..9baa8e6
--- /dev/null
+++ b/rfcomm/Makefile.am
@@ -0,0 +1,34 @@
+
+if TOOLS
+if CONFIGFILES
+confdir = $(sysconfdir)/bluetooth
+
+conf_DATA = rfcomm.conf
+endif
+
+bin_PROGRAMS = rfcomm
+
+rfcomm_SOURCES = main.c parser.h parser.y lexer.l kword.h kword.c
+
+rfcomm_LDADD = @BLUEZ_LIBS@
+endif
+
+AM_CFLAGS = @BLUEZ_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common
+
+BUILT_SOURCES = parser.h
+
+if TOOLS
+if MANPAGES
+man_MANS = rfcomm.1
+endif
+endif
+
+AM_YFLAGS = -d
+
+CLEANFILES = lexer.c parser.c parser.h
+
+EXTRA_DIST = rfcomm.1 rfcomm.conf
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/rfcomm/kword.c b/rfcomm/kword.c
new file mode 100644
index 0000000..659f293
--- /dev/null
+++ b/rfcomm/kword.c
@@ -0,0 +1,65 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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 <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/rfcomm/kword.h b/rfcomm/kword.h
new file mode 100644
index 0000000..ccc0cc7
--- /dev/null
+++ b/rfcomm/kword.h
@@ -0,0 +1,46 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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
+ *
+ */
+
+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/rfcomm/lexer.c b/rfcomm/lexer.c
new file mode 100644
index 0000000..0fb9bfb
--- /dev/null
+++ b/rfcomm/lexer.c
@@ -0,0 +1,1763 @@
+
+#line 3 "lex.yy.c"
+
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 31
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t; 
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else	/* ! __cplusplus */
+
+#if __STDC__
+
+#define YY_USE_CONST
+
+#endif	/* __STDC__ */
+#endif	/* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index.  If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition.  This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN (yy_start) = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.  The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START (((yy_start) - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart(yyin  )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+extern int yyleng;
+
+extern FILE *yyin, *yyout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+    #define YY_LESS_LINENO(n)
+    
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+		*yy_cp = (yy_hold_char); \
+		YY_RESTORE_YY_MORE_OFFSET \
+		(yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+		YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+		} \
+	while ( 0 )
+
+#define unput(c) yyunput( c, (yytext_ptr)  )
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef unsigned int yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+	{
+	FILE *yy_input_file;
+
+	char *yy_ch_buf;		/* input buffer */
+	char *yy_buf_pos;		/* current position in input buffer */
+
+	/* Size of input buffer in bytes, not including room for EOB
+	 * characters.
+	 */
+	yy_size_t yy_buf_size;
+
+	/* Number of characters read into yy_ch_buf, not including EOB
+	 * characters.
+	 */
+	int yy_n_chars;
+
+	/* Whether we "own" the buffer - i.e., we know we created it,
+	 * and can realloc() it to grow it, and should free() it to
+	 * delete it.
+	 */
+	int yy_is_our_buffer;
+
+	/* Whether this is an "interactive" input source; if so, and
+	 * if we're using stdio for input, then we want to use getc()
+	 * instead of fread(), to make sure we stop fetching input after
+	 * each newline.
+	 */
+	int yy_is_interactive;
+
+	/* Whether we're considered to be at the beginning of a line.
+	 * If so, '^' rules will be active on the next match, otherwise
+	 * not.
+	 */
+	int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+    
+	/* Whether to try to fill the input buffer when we reach the
+	 * end of it.
+	 */
+	int yy_fill_buffer;
+
+	int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+	/* When an EOF's been seen but there's still some text to process
+	 * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+	 * shouldn't try reading from the input source any more.  We might
+	 * still have a bunch of tokens to match, though, because of
+	 * possible backing-up.
+	 *
+	 * When we actually see the EOF, we change the status to "new"
+	 * (via yyrestart()), so that the user can continue scanning by
+	 * just pointing yyin at a new input file.
+	 */
+#define YY_BUFFER_EOF_PENDING 2
+
+	};
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* Stack of input buffers. */
+static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */
+static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */
+static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
+                          ? (yy_buffer_stack)[(yy_buffer_stack_top)] \
+                          : NULL)
+
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)]
+
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+static int yy_n_chars;		/* number of characters read into yy_ch_buf */
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = (char *) 0;
+static int yy_init = 1;		/* whether we need to initialize */
+static int yy_start = 0;	/* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin.  A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+void yyrestart (FILE *input_file  );
+void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer  );
+YY_BUFFER_STATE yy_create_buffer (FILE *file,int size  );
+void yy_delete_buffer (YY_BUFFER_STATE b  );
+void yy_flush_buffer (YY_BUFFER_STATE b  );
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer  );
+void yypop_buffer_state (void );
+
+static void yyensure_buffer_stack (void );
+static void yy_load_buffer_state (void );
+static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file  );
+
+#define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER )
+
+YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size  );
+YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str  );
+YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,int len  );
+
+void *yyalloc (yy_size_t  );
+void *yyrealloc (void *,yy_size_t  );
+void yyfree (void *  );
+
+#define yy_new_buffer yy_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+	{ \
+	if ( ! YY_CURRENT_BUFFER ){ \
+        yyensure_buffer_stack (); \
+		YY_CURRENT_BUFFER_LVALUE =    \
+            yy_create_buffer(yyin,YY_BUF_SIZE ); \
+	} \
+	YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+	}
+
+#define yy_set_bol(at_bol) \
+	{ \
+	if ( ! YY_CURRENT_BUFFER ){\
+        yyensure_buffer_stack (); \
+		YY_CURRENT_BUFFER_LVALUE =    \
+            yy_create_buffer(yyin,YY_BUF_SIZE ); \
+	} \
+	YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+	}
+
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+typedef unsigned char YY_CHAR;
+
+FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0;
+
+typedef int yy_state_type;
+
+extern int yylineno;
+
+int yylineno = 1;
+
+extern char *yytext;
+#define yytext_ptr yytext
+
+static yy_state_type yy_get_previous_state (void );
+static yy_state_type yy_try_NUL_trans (yy_state_type current_state  );
+static int yy_get_next_buffer (void );
+static void yy_fatal_error (yyconst char msg[]  );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+	(yytext_ptr) = yy_bp; \
+	yyleng = (size_t) (yy_cp - yy_bp); \
+	(yy_hold_char) = *yy_cp; \
+	*yy_cp = '\0'; \
+	(yy_c_buf_p) = yy_cp;
+
+#define YY_NUM_RULES 9
+#define YY_END_OF_BUFFER 10
+/* This struct is not used in this scanner,
+   but its presence is necessary. */
+struct yy_trans_info
+	{
+	flex_int32_t yy_verify;
+	flex_int32_t yy_nxt;
+	};
+static yyconst flex_int16_t yy_accept[36] =
+    {   0,
+        0,    0,   10,    8,    1,    7,    8,    8,    6,    3,
+        6,    0,    4,    0,    2,    6,    3,    6,    3,    0,
+        0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+        0,    0,    0,    5,    0
+    } ;
+
+static yyconst flex_int32_t yy_ec[256] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    2,    3,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    2,    1,    4,    5,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    6,    1,    1,    7,    7,    7,
+        7,    7,    7,    7,    7,    7,    7,    8,    1,    1,
+        1,    1,    1,    1,    9,    9,    9,    9,    9,    9,
+        9,    9,    9,    9,    9,    9,    9,    9,    9,    9,
+        9,    9,    9,    9,    9,    9,    9,    9,    9,    9,
+        1,    1,    1,    1,    6,    1,    9,    9,    9,    9,
+
+        9,    9,    9,    9,    9,    9,    9,    9,    9,    9,
+        9,    9,    9,    9,    9,    9,    9,    9,    9,    9,
+        9,    9,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1
+    } ;
+
+static yyconst flex_int32_t yy_meta[10] =
+    {   0,
+        1,    1,    2,    1,    1,    3,    4,    1,    4
+    } ;
+
+static yyconst flex_int16_t yy_base[49] =
+    {   0,
+        0,    0,   46,   47,   47,   47,   41,   41,    0,    4,
+       36,   38,   37,   37,   47,    0,    7,   31,   31,    0,
+        0,   29,    0,    0,   28,    0,    0,   27,    0,    0,
+       26,    0,    0,   47,   47,   15,   19,   21,   29,   28,
+       27,   26,   25,   24,   23,   22,   13,    8
+    } ;
+
+static yyconst flex_int16_t yy_def[49] =
+    {   0,
+       35,    1,   35,   35,   35,   35,   36,   37,   38,   35,
+       10,   36,   36,   37,   35,   38,   38,   38,   38,   39,
+       40,   35,   41,   42,   35,   43,   44,   35,   45,   46,
+       35,   47,   48,   35,    0,   35,   35,   35,   35,   35,
+       35,   35,   35,   35,   35,   35,   35,   35
+    } ;
+
+static yyconst flex_int16_t yy_nxt[57] =
+    {   0,
+        4,    5,    6,    7,    8,    9,   10,    4,   11,   16,
+       17,   34,   18,   19,   20,   12,   33,   12,   12,   14,
+       14,   14,   14,   16,   16,   31,   30,   28,   27,   25,
+       24,   22,   21,   32,   29,   26,   23,   19,   20,   15,
+       13,   13,   18,   15,   13,   35,    3,   35,   35,   35,
+       35,   35,   35,   35,   35,   35
+    } ;
+
+static yyconst flex_int16_t yy_chk[57] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,   10,
+       10,   48,   10,   17,   17,   36,   47,   36,   36,   37,
+       37,   37,   37,   38,   38,   46,   45,   44,   43,   42,
+       41,   40,   39,   31,   28,   25,   22,   19,   18,   14,
+       13,   12,   11,    8,    7,    3,   35,   35,   35,   35,
+       35,   35,   35,   35,   35,   35
+    } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+extern int yy_flex_debug;
+int yy_flex_debug = 0;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "lexer.l"
+#line 2 "lexer.l"
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2008  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);
+
+#line 512 "lex.yy.c"
+
+#define INITIAL 0
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap (void );
+#else
+extern int yywrap (void );
+#endif
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * );
+#endif
+
+#ifndef YY_NO_INPUT
+
+#ifdef __cplusplus
+static int yyinput (void );
+#else
+static int input (void );
+#endif
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+		{ \
+		int c = '*'; \
+		size_t n; \
+		for ( n = 0; n < max_size && \
+			     (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+			buf[n] = (char) c; \
+		if ( c == '\n' ) \
+			buf[n++] = (char) c; \
+		if ( c == EOF && ferror( yyin ) ) \
+			YY_FATAL_ERROR( "input in flex scanner failed" ); \
+		result = n; \
+		} \
+	else \
+		{ \
+		errno=0; \
+		while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \
+			{ \
+			if( errno != EINTR) \
+				{ \
+				YY_FATAL_ERROR( "input in flex scanner failed" ); \
+				break; \
+				} \
+			errno=0; \
+			clearerr(yyin); \
+			} \
+		}\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int yylex (void);
+
+#define YY_DECL int yylex (void)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+	YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+	register yy_state_type yy_current_state;
+	register char *yy_cp, *yy_bp;
+	register int yy_act;
+    
+#line 64 "lexer.l"
+
+
+#line 664 "lex.yy.c"
+
+	if ( (yy_init) )
+		{
+		(yy_init) = 0;
+
+#ifdef YY_USER_INIT
+		YY_USER_INIT;
+#endif
+
+		if ( ! (yy_start) )
+			(yy_start) = 1;	/* first start state */
+
+		if ( ! yyin )
+			yyin = stdin;
+
+		if ( ! yyout )
+			yyout = stdout;
+
+		if ( ! YY_CURRENT_BUFFER ) {
+			yyensure_buffer_stack ();
+			YY_CURRENT_BUFFER_LVALUE =
+				yy_create_buffer(yyin,YY_BUF_SIZE );
+		}
+
+		yy_load_buffer_state( );
+		}
+
+	while ( 1 )		/* loops until end-of-file is reached */
+		{
+		yy_cp = (yy_c_buf_p);
+
+		/* Support of yytext. */
+		*yy_cp = (yy_hold_char);
+
+		/* yy_bp points to the position in yy_ch_buf of the start of
+		 * the current run.
+		 */
+		yy_bp = yy_cp;
+
+		yy_current_state = (yy_start);
+yy_match:
+		do
+			{
+			register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+			if ( yy_accept[yy_current_state] )
+				{
+				(yy_last_accepting_state) = yy_current_state;
+				(yy_last_accepting_cpos) = yy_cp;
+				}
+			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+				{
+				yy_current_state = (int) yy_def[yy_current_state];
+				if ( yy_current_state >= 36 )
+					yy_c = yy_meta[(unsigned int) yy_c];
+				}
+			yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+			++yy_cp;
+			}
+		while ( yy_base[yy_current_state] != 47 );
+
+yy_find_action:
+		yy_act = yy_accept[yy_current_state];
+		if ( yy_act == 0 )
+			{ /* have to back up */
+			yy_cp = (yy_last_accepting_cpos);
+			yy_current_state = (yy_last_accepting_state);
+			yy_act = yy_accept[yy_current_state];
+			}
+
+		YY_DO_BEFORE_ACTION;
+
+do_action:	/* This label is used only to access EOF actions. */
+
+		switch ( yy_act )
+	{ /* beginning of action switch */
+			case 0: /* must back up */
+			/* undo the effects of YY_DO_BEFORE_ACTION */
+			*yy_cp = (yy_hold_char);
+			yy_cp = (yy_last_accepting_cpos);
+			yy_current_state = (yy_last_accepting_state);
+			goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 66 "lexer.l"
+{
+			/* Skip spaces and tabs */
+			;
+		}
+	YY_BREAK
+case 2:
+/* rule 2 can match eol */
+YY_RULE_SETUP
+#line 71 "lexer.l"
+{
+			/* Skip comments */
+			lineno++; 
+		}
+	YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 76 "lexer.l"
+{
+			yylval.number = atoi(yytext);
+			return NUMBER;
+		}
+	YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 81 "lexer.l"
+{
+			yylval.string = yytext;
+			return STRING;
+		}
+	YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 86 "lexer.l"
+{
+			bdaddr_t *ba = malloc(sizeof(bdaddr_t));
+			str2ba(yytext, ba);
+			yylval.bdaddr = ba;
+			return BDADDR;
+		}
+	YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 93 "lexer.l"
+{
+			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;
+		}
+	YY_BREAK
+case 7:
+/* rule 7 can match eol */
+YY_RULE_SETUP
+#line 107 "lexer.l"
+{
+			lineno++;
+		}
+	YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 111 "lexer.l"
+{
+			return *yytext;
+		}
+	YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 115 "lexer.l"
+ECHO;
+	YY_BREAK
+#line 827 "lex.yy.c"
+case YY_STATE_EOF(INITIAL):
+	yyterminate();
+
+	case YY_END_OF_BUFFER:
+		{
+		/* Amount of text matched not including the EOB char. */
+		int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1;
+
+		/* Undo the effects of YY_DO_BEFORE_ACTION. */
+		*yy_cp = (yy_hold_char);
+		YY_RESTORE_YY_MORE_OFFSET
+
+		if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+			{
+			/* We're scanning a new file or input source.  It's
+			 * possible that this happened because the user
+			 * just pointed yyin at a new source and called
+			 * yylex().  If so, then we have to assure
+			 * consistency between YY_CURRENT_BUFFER and our
+			 * globals.  Here is the right place to do so, because
+			 * this is the first action (other than possibly a
+			 * back-up) that will match for the new input source.
+			 */
+			(yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+			YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+			YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+			}
+
+		/* Note that here we test for yy_c_buf_p "<=" to the position
+		 * of the first EOB in the buffer, since yy_c_buf_p will
+		 * already have been incremented past the NUL character
+		 * (since all states make transitions on EOB to the
+		 * end-of-buffer state).  Contrast this with the test
+		 * in input().
+		 */
+		if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+			{ /* This was really a NUL. */
+			yy_state_type yy_next_state;
+
+			(yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text;
+
+			yy_current_state = yy_get_previous_state(  );
+
+			/* Okay, we're now positioned to make the NUL
+			 * transition.  We couldn't have
+			 * yy_get_previous_state() go ahead and do it
+			 * for us because it doesn't know how to deal
+			 * with the possibility of jamming (and we don't
+			 * want to build jamming into it because then it
+			 * will run more slowly).
+			 */
+
+			yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+			yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+
+			if ( yy_next_state )
+				{
+				/* Consume the NUL. */
+				yy_cp = ++(yy_c_buf_p);
+				yy_current_state = yy_next_state;
+				goto yy_match;
+				}
+
+			else
+				{
+				yy_cp = (yy_c_buf_p);
+				goto yy_find_action;
+				}
+			}
+
+		else switch ( yy_get_next_buffer(  ) )
+			{
+			case EOB_ACT_END_OF_FILE:
+				{
+				(yy_did_buffer_switch_on_eof) = 0;
+
+				if ( yywrap( ) )
+					{
+					/* Note: because we've taken care in
+					 * yy_get_next_buffer() to have set up
+					 * yytext, we can now set up
+					 * yy_c_buf_p so that if some total
+					 * hoser (like flex itself) wants to
+					 * call the scanner after we return the
+					 * YY_NULL, it'll still work - another
+					 * YY_NULL will get returned.
+					 */
+					(yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ;
+
+					yy_act = YY_STATE_EOF(YY_START);
+					goto do_action;
+					}
+
+				else
+					{
+					if ( ! (yy_did_buffer_switch_on_eof) )
+						YY_NEW_FILE;
+					}
+				break;
+				}
+
+			case EOB_ACT_CONTINUE_SCAN:
+				(yy_c_buf_p) =
+					(yytext_ptr) + yy_amount_of_matched_text;
+
+				yy_current_state = yy_get_previous_state(  );
+
+				yy_cp = (yy_c_buf_p);
+				yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+				goto yy_match;
+
+			case EOB_ACT_LAST_MATCH:
+				(yy_c_buf_p) =
+				&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)];
+
+				yy_current_state = yy_get_previous_state(  );
+
+				yy_cp = (yy_c_buf_p);
+				yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+				goto yy_find_action;
+			}
+		break;
+		}
+
+	default:
+		YY_FATAL_ERROR(
+			"fatal flex scanner internal error--no action found" );
+	} /* end of action switch */
+		} /* end of scanning one token */
+} /* end of yylex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ *	EOB_ACT_LAST_MATCH -
+ *	EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ *	EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (void)
+{
+    	register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+	register char *source = (yytext_ptr);
+	register int number_to_move, i;
+	int ret_val;
+
+	if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
+		YY_FATAL_ERROR(
+		"fatal flex scanner internal error--end of buffer missed" );
+
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+		{ /* Don't try to fill the buffer, so this is an EOF. */
+		if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 )
+			{
+			/* We matched a single character, the EOB, so
+			 * treat this as a final EOF.
+			 */
+			return EOB_ACT_END_OF_FILE;
+			}
+
+		else
+			{
+			/* We matched some text prior to the EOB, first
+			 * process it.
+			 */
+			return EOB_ACT_LAST_MATCH;
+			}
+		}
+
+	/* Try to read more data. */
+
+	/* First move last chars to start of buffer. */
+	number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1;
+
+	for ( i = 0; i < number_to_move; ++i )
+		*(dest++) = *(source++);
+
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+		/* don't do the read, it's not guaranteed to return an EOF,
+		 * just force an EOF
+		 */
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0;
+
+	else
+		{
+			int num_to_read =
+			YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+		while ( num_to_read <= 0 )
+			{ /* Not enough room in the buffer - grow it. */
+
+			/* just a shorter name for the current buffer */
+			YY_BUFFER_STATE b = YY_CURRENT_BUFFER;
+
+			int yy_c_buf_p_offset =
+				(int) ((yy_c_buf_p) - b->yy_ch_buf);
+
+			if ( b->yy_is_our_buffer )
+				{
+				int new_size = b->yy_buf_size * 2;
+
+				if ( new_size <= 0 )
+					b->yy_buf_size += b->yy_buf_size / 8;
+				else
+					b->yy_buf_size *= 2;
+
+				b->yy_ch_buf = (char *)
+					/* Include room in for 2 EOB chars. */
+					yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2  );
+				}
+			else
+				/* Can't grow it, we don't own it. */
+				b->yy_ch_buf = 0;
+
+			if ( ! b->yy_ch_buf )
+				YY_FATAL_ERROR(
+				"fatal error - scanner input buffer overflow" );
+
+			(yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+			num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+						number_to_move - 1;
+
+			}
+
+		if ( num_to_read > YY_READ_BUF_SIZE )
+			num_to_read = YY_READ_BUF_SIZE;
+
+		/* Read in more data. */
+		YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+			(yy_n_chars), num_to_read );
+
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+		}
+
+	if ( (yy_n_chars) == 0 )
+		{
+		if ( number_to_move == YY_MORE_ADJ )
+			{
+			ret_val = EOB_ACT_END_OF_FILE;
+			yyrestart(yyin  );
+			}
+
+		else
+			{
+			ret_val = EOB_ACT_LAST_MATCH;
+			YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+				YY_BUFFER_EOF_PENDING;
+			}
+		}
+
+	else
+		ret_val = EOB_ACT_CONTINUE_SCAN;
+
+	(yy_n_chars) += number_to_move;
+	YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR;
+	YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR;
+
+	(yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+	return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+    static yy_state_type yy_get_previous_state (void)
+{
+	register yy_state_type yy_current_state;
+	register char *yy_cp;
+    
+	yy_current_state = (yy_start);
+
+	for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
+		{
+		register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+		if ( yy_accept[yy_current_state] )
+			{
+			(yy_last_accepting_state) = yy_current_state;
+			(yy_last_accepting_cpos) = yy_cp;
+			}
+		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+			{
+			yy_current_state = (int) yy_def[yy_current_state];
+			if ( yy_current_state >= 36 )
+				yy_c = yy_meta[(unsigned int) yy_c];
+			}
+		yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+		}
+
+	return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ *	next_state = yy_try_NUL_trans( current_state );
+ */
+    static yy_state_type yy_try_NUL_trans  (yy_state_type yy_current_state )
+{
+	register int yy_is_jam;
+    	register char *yy_cp = (yy_c_buf_p);
+
+	register YY_CHAR yy_c = 1;
+	if ( yy_accept[yy_current_state] )
+		{
+		(yy_last_accepting_state) = yy_current_state;
+		(yy_last_accepting_cpos) = yy_cp;
+		}
+	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+		{
+		yy_current_state = (int) yy_def[yy_current_state];
+		if ( yy_current_state >= 36 )
+			yy_c = yy_meta[(unsigned int) yy_c];
+		}
+	yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+	yy_is_jam = (yy_current_state == 35);
+
+	return yy_is_jam ? 0 : yy_current_state;
+}
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+    static int yyinput (void)
+#else
+    static int input  (void)
+#endif
+
+{
+	int c;
+    
+	*(yy_c_buf_p) = (yy_hold_char);
+
+	if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR )
+		{
+		/* yy_c_buf_p now points to the character we want to return.
+		 * If this occurs *before* the EOB characters, then it's a
+		 * valid NUL; if not, then we've hit the end of the buffer.
+		 */
+		if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+			/* This was really a NUL. */
+			*(yy_c_buf_p) = '\0';
+
+		else
+			{ /* need more input */
+			int offset = (yy_c_buf_p) - (yytext_ptr);
+			++(yy_c_buf_p);
+
+			switch ( yy_get_next_buffer(  ) )
+				{
+				case EOB_ACT_LAST_MATCH:
+					/* This happens because yy_g_n_b()
+					 * sees that we've accumulated a
+					 * token and flags that we need to
+					 * try matching the token before
+					 * proceeding.  But for input(),
+					 * there's no matching to consider.
+					 * So convert the EOB_ACT_LAST_MATCH
+					 * to EOB_ACT_END_OF_FILE.
+					 */
+
+					/* Reset buffer status. */
+					yyrestart(yyin );
+
+					/*FALLTHROUGH*/
+
+				case EOB_ACT_END_OF_FILE:
+					{
+					if ( yywrap( ) )
+						return EOF;
+
+					if ( ! (yy_did_buffer_switch_on_eof) )
+						YY_NEW_FILE;
+#ifdef __cplusplus
+					return yyinput();
+#else
+					return input();
+#endif
+					}
+
+				case EOB_ACT_CONTINUE_SCAN:
+					(yy_c_buf_p) = (yytext_ptr) + offset;
+					break;
+				}
+			}
+		}
+
+	c = *(unsigned char *) (yy_c_buf_p);	/* cast for 8-bit char's */
+	*(yy_c_buf_p) = '\0';	/* preserve yytext */
+	(yy_hold_char) = *++(yy_c_buf_p);
+
+	return c;
+}
+#endif	/* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ * 
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+    void yyrestart  (FILE * input_file )
+{
+    
+	if ( ! YY_CURRENT_BUFFER ){
+        yyensure_buffer_stack ();
+		YY_CURRENT_BUFFER_LVALUE =
+            yy_create_buffer(yyin,YY_BUF_SIZE );
+	}
+
+	yy_init_buffer(YY_CURRENT_BUFFER,input_file );
+	yy_load_buffer_state( );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ * 
+ */
+    void yy_switch_to_buffer  (YY_BUFFER_STATE  new_buffer )
+{
+    
+	/* TODO. We should be able to replace this entire function body
+	 * with
+	 *		yypop_buffer_state();
+	 *		yypush_buffer_state(new_buffer);
+     */
+	yyensure_buffer_stack ();
+	if ( YY_CURRENT_BUFFER == new_buffer )
+		return;
+
+	if ( YY_CURRENT_BUFFER )
+		{
+		/* Flush out information for old buffer. */
+		*(yy_c_buf_p) = (yy_hold_char);
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+		}
+
+	YY_CURRENT_BUFFER_LVALUE = new_buffer;
+	yy_load_buffer_state( );
+
+	/* We don't actually know whether we did this switch during
+	 * EOF (yywrap()) processing, but the only time this flag
+	 * is looked at is after yywrap() is called, so it's safe
+	 * to go ahead and always set it.
+	 */
+	(yy_did_buffer_switch_on_eof) = 1;
+}
+
+static void yy_load_buffer_state  (void)
+{
+    	(yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+	(yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+	yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+	(yy_hold_char) = *(yy_c_buf_p);
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ * 
+ * @return the allocated buffer state.
+ */
+    YY_BUFFER_STATE yy_create_buffer  (FILE * file, int  size )
+{
+	YY_BUFFER_STATE b;
+    
+	b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state )  );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+	b->yy_buf_size = size;
+
+	/* yy_ch_buf has to be 2 characters longer than the size given because
+	 * we need to put in 2 end-of-buffer characters.
+	 */
+	b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2  );
+	if ( ! b->yy_ch_buf )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+	b->yy_is_our_buffer = 1;
+
+	yy_init_buffer(b,file );
+
+	return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ * 
+ */
+    void yy_delete_buffer (YY_BUFFER_STATE  b )
+{
+    
+	if ( ! b )
+		return;
+
+	if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+		YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+	if ( b->yy_is_our_buffer )
+		yyfree((void *) b->yy_ch_buf  );
+
+	yyfree((void *) b  );
+}
+
+#ifndef __cplusplus
+extern int isatty (int );
+#endif /* __cplusplus */
+    
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+    static void yy_init_buffer  (YY_BUFFER_STATE  b, FILE * file )
+
+{
+	int oerrno = errno;
+    
+	yy_flush_buffer(b );
+
+	b->yy_input_file = file;
+	b->yy_fill_buffer = 1;
+
+    /* If b is the current buffer, then yy_init_buffer was _probably_
+     * called from yyrestart() or through yy_get_next_buffer.
+     * In that case, we don't want to reset the lineno or column.
+     */
+    if (b != YY_CURRENT_BUFFER){
+        b->yy_bs_lineno = 1;
+        b->yy_bs_column = 0;
+    }
+
+        b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+    
+	errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ * 
+ */
+    void yy_flush_buffer (YY_BUFFER_STATE  b )
+{
+    	if ( ! b )
+		return;
+
+	b->yy_n_chars = 0;
+
+	/* We always need two end-of-buffer characters.  The first causes
+	 * a transition to the end-of-buffer state.  The second causes
+	 * a jam in that state.
+	 */
+	b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+	b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+	b->yy_buf_pos = &b->yy_ch_buf[0];
+
+	b->yy_at_bol = 1;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	if ( b == YY_CURRENT_BUFFER )
+		yy_load_buffer_state( );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ *  the current state. This function will allocate the stack
+ *  if necessary.
+ *  @param new_buffer The new state.
+ *  
+ */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer )
+{
+    	if (new_buffer == NULL)
+		return;
+
+	yyensure_buffer_stack();
+
+	/* This block is copied from yy_switch_to_buffer. */
+	if ( YY_CURRENT_BUFFER )
+		{
+		/* Flush out information for old buffer. */
+		*(yy_c_buf_p) = (yy_hold_char);
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+		}
+
+	/* Only push if top exists. Otherwise, replace top. */
+	if (YY_CURRENT_BUFFER)
+		(yy_buffer_stack_top)++;
+	YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+	/* copied from yy_switch_to_buffer. */
+	yy_load_buffer_state( );
+	(yy_did_buffer_switch_on_eof) = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ *  The next element becomes the new top.
+ *  
+ */
+void yypop_buffer_state (void)
+{
+    	if (!YY_CURRENT_BUFFER)
+		return;
+
+	yy_delete_buffer(YY_CURRENT_BUFFER );
+	YY_CURRENT_BUFFER_LVALUE = NULL;
+	if ((yy_buffer_stack_top) > 0)
+		--(yy_buffer_stack_top);
+
+	if (YY_CURRENT_BUFFER) {
+		yy_load_buffer_state( );
+		(yy_did_buffer_switch_on_eof) = 1;
+	}
+}
+
+/* Allocates the stack if it does not exist.
+ *  Guarantees space for at least one push.
+ */
+static void yyensure_buffer_stack (void)
+{
+	int num_to_alloc;
+    
+	if (!(yy_buffer_stack)) {
+
+		/* First allocation is just for 2 elements, since we don't know if this
+		 * scanner will even need a stack. We use 2 instead of 1 to avoid an
+		 * immediate realloc on the next call.
+         */
+		num_to_alloc = 1;
+		(yy_buffer_stack) = (struct yy_buffer_state**)yyalloc
+								(num_to_alloc * sizeof(struct yy_buffer_state*)
+								);
+		
+		memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+				
+		(yy_buffer_stack_max) = num_to_alloc;
+		(yy_buffer_stack_top) = 0;
+		return;
+	}
+
+	if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){
+
+		/* Increase the buffer to prepare for a possible push. */
+		int grow_size = 8 /* arbitrary grow size */;
+
+		num_to_alloc = (yy_buffer_stack_max) + grow_size;
+		(yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc
+								((yy_buffer_stack),
+								num_to_alloc * sizeof(struct yy_buffer_state*)
+								);
+
+		/* zero only the new slots.*/
+		memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
+		(yy_buffer_stack_max) = num_to_alloc;
+	}
+}
+
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ * 
+ * @return the newly allocated buffer state object. 
+ */
+YY_BUFFER_STATE yy_scan_buffer  (char * base, yy_size_t  size )
+{
+	YY_BUFFER_STATE b;
+    
+	if ( size < 2 ||
+	     base[size-2] != YY_END_OF_BUFFER_CHAR ||
+	     base[size-1] != YY_END_OF_BUFFER_CHAR )
+		/* They forgot to leave room for the EOB's. */
+		return 0;
+
+	b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state )  );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+	b->yy_buf_size = size - 2;	/* "- 2" to take care of EOB's */
+	b->yy_buf_pos = b->yy_ch_buf = base;
+	b->yy_is_our_buffer = 0;
+	b->yy_input_file = 0;
+	b->yy_n_chars = b->yy_buf_size;
+	b->yy_is_interactive = 0;
+	b->yy_at_bol = 1;
+	b->yy_fill_buffer = 0;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	yy_switch_to_buffer(b  );
+
+	return b;
+}
+
+/** Setup the input buffer state to scan a string. The next call to yylex() will
+ * scan from a @e copy of @a str.
+ * @param str a NUL-terminated string to scan
+ * 
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ *       yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE yy_scan_string (yyconst char * yy_str )
+{
+    
+	return yy_scan_bytes(yy_str,strlen(yy_str) );
+}
+
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param bytes the byte buffer to scan
+ * @param len the number of bytes in the buffer pointed to by @a bytes.
+ * 
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_bytes  (yyconst char * bytes, int  len )
+{
+	YY_BUFFER_STATE b;
+	char *buf;
+	yy_size_t n;
+	int i;
+    
+	/* Get memory for full buffer, including space for trailing EOB's. */
+	n = len + 2;
+	buf = (char *) yyalloc(n  );
+	if ( ! buf )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+	for ( i = 0; i < len; ++i )
+		buf[i] = bytes[i];
+
+	buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR;
+
+	b = yy_scan_buffer(buf,n );
+	if ( ! b )
+		YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+	/* It's okay to grow etc. this buffer, and we should throw it
+	 * away when we're done.
+	 */
+	b->yy_is_our_buffer = 1;
+
+	return b;
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yy_fatal_error (yyconst char* msg )
+{
+    	(void) fprintf( stderr, "%s\n", msg );
+	exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+		yytext[yyleng] = (yy_hold_char); \
+		(yy_c_buf_p) = yytext + yyless_macro_arg; \
+		(yy_hold_char) = *(yy_c_buf_p); \
+		*(yy_c_buf_p) = '\0'; \
+		yyleng = yyless_macro_arg; \
+		} \
+	while ( 0 )
+
+/* Accessor  methods (get/set functions) to struct members. */
+
+/** Get the current line number.
+ * 
+ */
+int yyget_lineno  (void)
+{
+        
+    return yylineno;
+}
+
+/** Get the input stream.
+ * 
+ */
+FILE *yyget_in  (void)
+{
+        return yyin;
+}
+
+/** Get the output stream.
+ * 
+ */
+FILE *yyget_out  (void)
+{
+        return yyout;
+}
+
+/** Get the length of the current token.
+ * 
+ */
+int yyget_leng  (void)
+{
+        return yyleng;
+}
+
+/** Get the current token.
+ * 
+ */
+
+char *yyget_text  (void)
+{
+        return yytext;
+}
+
+/** Set the current line number.
+ * @param line_number
+ * 
+ */
+void yyset_lineno (int  line_number )
+{
+    
+    yylineno = line_number;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param in_str A readable stream.
+ * 
+ * @see yy_switch_to_buffer
+ */
+void yyset_in (FILE *  in_str )
+{
+        yyin = in_str ;
+}
+
+void yyset_out (FILE *  out_str )
+{
+        yyout = out_str ;
+}
+
+int yyget_debug  (void)
+{
+        return yy_flex_debug;
+}
+
+void yyset_debug (int  bdebug )
+{
+        yy_flex_debug = bdebug ;
+}
+
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy  (void)
+{
+    
+    /* Pop the buffer stack, destroying each element. */
+	while(YY_CURRENT_BUFFER){
+		yy_delete_buffer(YY_CURRENT_BUFFER  );
+		YY_CURRENT_BUFFER_LVALUE = NULL;
+		yypop_buffer_state();
+	}
+
+	/* Destroy the stack itself. */
+	yyfree((yy_buffer_stack) );
+	(yy_buffer_stack) = NULL;
+
+    return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, yyconst char * s2, int n )
+{
+	register int i;
+    	for ( i = 0; i < n; ++i )
+		s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * s )
+{
+	register int n;
+    	for ( n = 0; s[n]; ++n )
+		;
+
+	return n;
+}
+#endif
+
+void *yyalloc (yy_size_t  size )
+{
+	return (void *) malloc( size );
+}
+
+void *yyrealloc  (void * ptr, yy_size_t  size )
+{
+	/* The cast to (char *) in the following accommodates both
+	 * implementations that use char* generic pointers, and those
+	 * that use void* generic pointers.  It works with the latter
+	 * because both ANSI C and C++ allow castless assignment from
+	 * any pointer type to void*, and deal with argument conversions
+	 * as though doing an assignment.
+	 */
+	return (void *) realloc( (char *) ptr, size );
+}
+
+void yyfree (void * ptr )
+{
+	free( (char *) ptr );	/* see yyrealloc() for (char *) cast */
+}
+
+#define YYTABLES_NAME "yytables"
+
+#undef YY_NEW_FILE
+#undef YY_FLUSH_BUFFER
+#undef yy_set_bol
+#undef yy_new_buffer
+#undef yy_set_interactive
+#undef yytext_ptr
+#undef YY_DO_BEFORE_ACTION
+
+#ifdef YY_DECL_IS_OURS
+#undef YY_DECL_IS_OURS
+#undef YY_DECL
+#endif
+#line 115 "lexer.l"
+
+
+
+int yywrap(void) 
+{
+	return 1;
+}
+
diff --git a/rfcomm/lexer.l b/rfcomm/lexer.l
new file mode 100644
index 0000000..e15c8ab
--- /dev/null
+++ b/rfcomm/lexer.l
@@ -0,0 +1,120 @@
+%{
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2008  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/rfcomm/main.c b/rfcomm/main.c
new file mode 100644
index 0000000..ed82432
--- /dev/null
+++ b/rfcomm/main.c
@@ -0,0 +1,845 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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
+
+#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");
+		exit(1);
+	}
+
+	for (i = 0; i < dl->dev_num; i++)
+		print_dev_info(di + i);
+}
+
+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");
+		exit(1);
+	}
+
+	for (i = 0; i < dl->dev_num; i++)
+		release_dev(ctl, (di + i)->id, 0);
+
+	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/rfcomm/parser.c b/rfcomm/parser.c
new file mode 100644
index 0000000..7aae443
--- /dev/null
+++ b/rfcomm/parser.c
@@ -0,0 +1,595 @@
+#ifndef lint
+static const char yysccsid[] = "@(#)yaccpar	1.9 (Berkeley) 02/21/93";
+#endif
+
+#include <stdlib.h>
+
+#define YYBYACC 1
+#define YYMAJOR 1
+#define YYMINOR 9
+#define YYPATCH 20050813
+
+#define YYEMPTY (-1)
+#define yyclearin    (yychar = YYEMPTY)
+#define yyerrok      (yyerrflag = 0)
+#define YYRECOVERING (yyerrflag != 0)
+
+extern int yyparse(void);
+
+static int yygrowstack(void);
+#define YYPREFIX "yy"
+#line 2 "parser.y"
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2008  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;
+
+#line 49 "parser.y"
+typedef union {
+	int number;
+	char *string;
+	bdaddr_t *bdaddr;
+} YYSTYPE;
+#line 74 "y.tab.c"
+#define K_BIND 257
+#define K_DEVICE 258
+#define K_CHANNEL 259
+#define K_COMMENT 260
+#define K_YES 261
+#define K_NO 262
+#define NUMBER 263
+#define RFCOMM 264
+#define STRING 265
+#define WORD 266
+#define BDADDR 267
+#define YYERRCODE 256
+short yylhs[] = {                                        -1,
+    0,    0,    0,    2,    2,    2,    2,    3,    5,    4,
+    4,    4,    6,    6,    6,    6,    6,    1,    1,
+};
+short yylen[] = {                                         2,
+    0,    1,    2,    4,    4,    1,    1,    1,    1,    2,
+    2,    3,    2,    2,    2,    2,    1,    1,    1,
+};
+short yydefred[] = {                                      0,
+    7,    9,    0,    0,    2,    0,    0,    3,    0,    0,
+    0,    0,    0,    0,    0,   17,    0,    0,    0,   11,
+   18,   19,   13,   14,   15,   16,    4,    0,   10,    5,
+   12,
+};
+short yydgoto[] = {                                       4,
+   23,    5,    6,   17,    7,   18,
+};
+short yysindex[] = {                                   -254,
+    0,    0,    0, -254,    0, -120, -109,    0, -251, -251,
+  -41, -245, -248, -243, -244,    0, -125,  -37, -121,    0,
+    0,    0,    0,    0,    0,    0,    0,  -36,    0,    0,
+    0,
+};
+short yyrindex[] = {                                     24,
+    0,    0,    1,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,
+};
+short yygindex[] = {                                      0,
+    0,   21,    0,   16,    0,   -6,
+};
+#define YYTABLESIZE 267
+short yytable[] = {                                      27,
+    6,    1,    9,   30,   11,   12,   13,   14,   15,    2,
+   28,    3,   28,   10,   16,   21,   22,   20,   24,   25,
+   26,   29,   31,    1,    8,   19,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    8,    0,    0,    0,    0,    0,    0,
+    0,   12,   13,   14,   15,   12,   13,   14,   15,    0,
+   16,    0,    0,    0,   16,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    6,    0,    0,    0,
+    0,    0,    0,    0,    6,    0,    6,
+};
+short yycheck[] = {                                     125,
+    0,  256,  123,  125,  256,  257,  258,  259,  260,  264,
+   17,  266,   19,  123,  266,  261,  262,   59,  267,  263,
+  265,   59,   59,    0,    4,   10,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,  123,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,  257,  258,  259,  260,  257,  258,  259,  260,   -1,
+  266,   -1,   -1,   -1,  266,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,  256,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,  264,   -1,  266,
+};
+#define YYFINAL 4
+#ifndef YYDEBUG
+#define YYDEBUG 0
+#endif
+#define YYMAXTOKEN 267
+#if YYDEBUG
+char *yyname[] = {
+"end-of-file",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"';'",0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,"'{'",0,"'}'",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"K_BIND","K_DEVICE",
+"K_CHANNEL","K_COMMENT","K_YES","K_NO","NUMBER","RFCOMM","STRING","WORD",
+"BDADDR",
+};
+char *yyrule[] = {
+"$accept : config",
+"config :",
+"config : statement",
+"config : config statement",
+"statement : section '{' rfcomm_options '}'",
+"statement : rfcomm '{' rfcomm_options '}'",
+"statement : WORD",
+"statement : error",
+"section : WORD",
+"rfcomm : RFCOMM",
+"rfcomm_options : rfcomm_option ';'",
+"rfcomm_options : error ';'",
+"rfcomm_options : rfcomm_options rfcomm_option ';'",
+"rfcomm_option : K_BIND bool",
+"rfcomm_option : K_DEVICE BDADDR",
+"rfcomm_option : K_CHANNEL NUMBER",
+"rfcomm_option : K_COMMENT STRING",
+"rfcomm_option : WORD",
+"bool : K_YES",
+"bool : K_NO",
+};
+#endif
+#if YYDEBUG
+#include <stdio.h>
+#endif
+
+/* define the initial stack-sizes */
+#ifdef YYSTACKSIZE
+#undef YYMAXDEPTH
+#define YYMAXDEPTH  YYSTACKSIZE
+#else
+#ifdef YYMAXDEPTH
+#define YYSTACKSIZE YYMAXDEPTH
+#else
+#define YYSTACKSIZE 500
+#define YYMAXDEPTH  500
+#endif
+#endif
+
+#define YYINITSTACKSIZE 500
+
+int      yydebug;
+int      yynerrs;
+int      yyerrflag;
+int      yychar;
+short   *yyssp;
+YYSTYPE *yyvsp;
+YYSTYPE  yyval;
+YYSTYPE  yylval;
+
+/* variables for the parser stack */
+static short   *yyss;
+static short   *yysslim;
+static YYSTYPE *yyvs;
+static int      yystacksize;
+#line 134 "parser.y"
+
+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;
+}
+#line 290 "y.tab.c"
+/* allocate initial stack or double stack size, up to YYMAXDEPTH */
+static int yygrowstack(void)
+{
+    int newsize, i;
+    short *newss;
+    YYSTYPE *newvs;
+
+    if ((newsize = yystacksize) == 0)
+        newsize = YYINITSTACKSIZE;
+    else if (newsize >= YYMAXDEPTH)
+        return -1;
+    else if ((newsize *= 2) > YYMAXDEPTH)
+        newsize = YYMAXDEPTH;
+
+    i = yyssp - yyss;
+    newss = (yyss != 0)
+          ? (short *)realloc(yyss, newsize * sizeof(*newss))
+          : (short *)malloc(newsize * sizeof(*newss));
+    if (newss == 0)
+        return -1;
+
+    yyss  = newss;
+    yyssp = newss + i;
+    newvs = (yyvs != 0)
+          ? (YYSTYPE *)realloc(yyvs, newsize * sizeof(*newvs))
+          : (YYSTYPE *)malloc(newsize * sizeof(*newvs));
+    if (newvs == 0)
+        return -1;
+
+    yyvs = newvs;
+    yyvsp = newvs + i;
+    yystacksize = newsize;
+    yysslim = yyss + newsize - 1;
+    return 0;
+}
+
+#define YYABORT goto yyabort
+#define YYREJECT goto yyabort
+#define YYACCEPT goto yyaccept
+#define YYERROR goto yyerrlab
+int
+yyparse(void)
+{
+    register int yym, yyn, yystate;
+#if YYDEBUG
+    register const char *yys;
+
+    if ((yys = getenv("YYDEBUG")) != 0)
+    {
+        yyn = *yys;
+        if (yyn >= '0' && yyn <= '9')
+            yydebug = yyn - '0';
+    }
+#endif
+
+    yynerrs = 0;
+    yyerrflag = 0;
+    yychar = YYEMPTY;
+
+    if (yyss == NULL && yygrowstack()) goto yyoverflow;
+    yyssp = yyss;
+    yyvsp = yyvs;
+    *yyssp = yystate = 0;
+
+yyloop:
+    if ((yyn = yydefred[yystate]) != 0) goto yyreduce;
+    if (yychar < 0)
+    {
+        if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+        if (yydebug)
+        {
+            yys = 0;
+            if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+            if (!yys) yys = "illegal-symbol";
+            printf("%sdebug: state %d, reading %d (%s)\n",
+                    YYPREFIX, yystate, yychar, yys);
+        }
+#endif
+    }
+    if ((yyn = yysindex[yystate]) && (yyn += yychar) >= 0 &&
+            yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+    {
+#if YYDEBUG
+        if (yydebug)
+            printf("%sdebug: state %d, shifting to state %d\n",
+                    YYPREFIX, yystate, yytable[yyn]);
+#endif
+        if (yyssp >= yysslim && yygrowstack())
+        {
+            goto yyoverflow;
+        }
+        *++yyssp = yystate = yytable[yyn];
+        *++yyvsp = yylval;
+        yychar = YYEMPTY;
+        if (yyerrflag > 0)  --yyerrflag;
+        goto yyloop;
+    }
+    if ((yyn = yyrindex[yystate]) && (yyn += yychar) >= 0 &&
+            yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+    {
+        yyn = yytable[yyn];
+        goto yyreduce;
+    }
+    if (yyerrflag) goto yyinrecovery;
+
+    yyerror("syntax error");
+
+#ifdef lint
+    goto yyerrlab;
+#endif
+
+yyerrlab:
+    ++yynerrs;
+
+yyinrecovery:
+    if (yyerrflag < 3)
+    {
+        yyerrflag = 3;
+        for (;;)
+        {
+            if ((yyn = yysindex[*yyssp]) && (yyn += YYERRCODE) >= 0 &&
+                    yyn <= YYTABLESIZE && yycheck[yyn] == YYERRCODE)
+            {
+#if YYDEBUG
+                if (yydebug)
+                    printf("%sdebug: state %d, error recovery shifting\
+ to state %d\n", YYPREFIX, *yyssp, yytable[yyn]);
+#endif
+                if (yyssp >= yysslim && yygrowstack())
+                {
+                    goto yyoverflow;
+                }
+                *++yyssp = yystate = yytable[yyn];
+                *++yyvsp = yylval;
+                goto yyloop;
+            }
+            else
+            {
+#if YYDEBUG
+                if (yydebug)
+                    printf("%sdebug: error recovery discarding state %d\n",
+                            YYPREFIX, *yyssp);
+#endif
+                if (yyssp <= yyss) goto yyabort;
+                --yyssp;
+                --yyvsp;
+            }
+        }
+    }
+    else
+    {
+        if (yychar == 0) goto yyabort;
+#if YYDEBUG
+        if (yydebug)
+        {
+            yys = 0;
+            if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+            if (!yys) yys = "illegal-symbol";
+            printf("%sdebug: state %d, error recovery discards token %d (%s)\n",
+                    YYPREFIX, yystate, yychar, yys);
+        }
+#endif
+        yychar = YYEMPTY;
+        goto yyloop;
+    }
+
+yyreduce:
+#if YYDEBUG
+    if (yydebug)
+        printf("%sdebug: state %d, reducing by rule %d (%s)\n",
+                YYPREFIX, yystate, yyn, yyrule[yyn]);
+#endif
+    yym = yylen[yyn];
+    yyval = yyvsp[1-yym];
+    switch (yyn)
+    {
+case 6:
+#line 74 "parser.y"
+{
+			}
+break;
+case 7:
+#line 77 "parser.y"
+{
+				yyclearin;
+				yyerrok;
+			}
+break;
+case 8:
+#line 84 "parser.y"
+{
+				opts = NULL;
+			}
+break;
+case 9:
+#line 90 "parser.y"
+{
+				if ((yyvsp[0].number >= 0) && (yyvsp[0].number < RFCOMM_MAX_DEV))
+					opts = &rfcomm_opts[yyvsp[0].number];
+				else
+					opts = NULL;
+			}
+break;
+case 13:
+#line 104 "parser.y"
+{
+				if (opts)
+					opts->bind = yyvsp[0].number;
+			}
+break;
+case 14:
+#line 109 "parser.y"
+{
+				if (opts)
+					bacpy(&opts->bdaddr, yyvsp[0].bdaddr);
+			}
+break;
+case 15:
+#line 114 "parser.y"
+{
+				if (opts)
+					opts->channel = yyvsp[0].number;
+			}
+break;
+case 16:
+#line 119 "parser.y"
+{
+				if (opts)
+					snprintf(opts->comment, MAXCOMMENTLEN, "%s", yyvsp[0].string);
+			}
+break;
+case 17:
+#line 124 "parser.y"
+{
+				/* Unknown option*/
+			}
+break;
+case 18:
+#line 129 "parser.y"
+{ yyval.number = 1; }
+break;
+case 19:
+#line 130 "parser.y"
+{ yyval.number = 0; }
+break;
+#line 537 "y.tab.c"
+    }
+    yyssp -= yym;
+    yystate = *yyssp;
+    yyvsp -= yym;
+    yym = yylhs[yyn];
+    if (yystate == 0 && yym == 0)
+    {
+#if YYDEBUG
+        if (yydebug)
+            printf("%sdebug: after reduction, shifting from state 0 to\
+ state %d\n", YYPREFIX, YYFINAL);
+#endif
+        yystate = YYFINAL;
+        *++yyssp = YYFINAL;
+        *++yyvsp = yyval;
+        if (yychar < 0)
+        {
+            if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+            if (yydebug)
+            {
+                yys = 0;
+                if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+                if (!yys) yys = "illegal-symbol";
+                printf("%sdebug: state %d, reading %d (%s)\n",
+                        YYPREFIX, YYFINAL, yychar, yys);
+            }
+#endif
+        }
+        if (yychar == 0) goto yyaccept;
+        goto yyloop;
+    }
+    if ((yyn = yygindex[yym]) && (yyn += yystate) >= 0 &&
+            yyn <= YYTABLESIZE && yycheck[yyn] == yystate)
+        yystate = yytable[yyn];
+    else
+        yystate = yydgoto[yym];
+#if YYDEBUG
+    if (yydebug)
+        printf("%sdebug: after reduction, shifting from state %d \
+to state %d\n", YYPREFIX, *yyssp, yystate);
+#endif
+    if (yyssp >= yysslim && yygrowstack())
+    {
+        goto yyoverflow;
+    }
+    *++yyssp = yystate;
+    *++yyvsp = yyval;
+    goto yyloop;
+
+yyoverflow:
+    yyerror("yacc stack overflow");
+
+yyabort:
+    return (1);
+
+yyaccept:
+    return (0);
+}
diff --git a/rfcomm/parser.h b/rfcomm/parser.h
new file mode 100644
index 0000000..7a1951f
--- /dev/null
+++ b/rfcomm/parser.h
@@ -0,0 +1,88 @@
+/* A Bison parser, made by GNU Bison 2.3.  */
+
+/* Skeleton interface for Bison's Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+   Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+/* Tokens.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+   /* Put the tokens into the symbol table, so that GDB and other debuggers
+      know about them.  */
+   enum yytokentype {
+     K_BIND = 258,
+     K_DEVICE = 259,
+     K_CHANNEL = 260,
+     K_COMMENT = 261,
+     K_YES = 262,
+     K_NO = 263,
+     NUMBER = 264,
+     RFCOMM = 265,
+     STRING = 266,
+     WORD = 267,
+     BDADDR = 268
+   };
+#endif
+/* Tokens.  */
+#define K_BIND 258
+#define K_DEVICE 259
+#define K_CHANNEL 260
+#define K_COMMENT 261
+#define K_YES 262
+#define K_NO 263
+#define NUMBER 264
+#define RFCOMM 265
+#define STRING 266
+#define WORD 267
+#define BDADDR 268
+
+
+
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+#line 50 "parser.y"
+{
+	int number;
+	char *string;
+	bdaddr_t *bdaddr;
+}
+/* Line 1489 of yacc.c.  */
+#line 81 "parser.h"
+	YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+extern YYSTYPE yylval;
+
diff --git a/rfcomm/parser.y b/rfcomm/parser.y
new file mode 100644
index 0000000..c550e2f
--- /dev/null
+++ b/rfcomm/parser.y
@@ -0,0 +1,171 @@
+%{
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2008  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/rfcomm/rfcomm.1 b/rfcomm/rfcomm.1
new file mode 100644
index 0000000..06252e5
--- /dev/null
+++ b/rfcomm/rfcomm.1
@@ -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/rfcomm/rfcomm.conf b/rfcomm/rfcomm.conf
new file mode 100644
index 0000000..6179ef7
--- /dev/null
+++ b/rfcomm/rfcomm.conf
@@ -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/sbc/Makefile.am b/sbc/Makefile.am
new file mode 100644
index 0000000..f870164
--- /dev/null
+++ b/sbc/Makefile.am
@@ -0,0 +1,33 @@
+
+if SNDFILE
+sndfile_programs = sbctester
+else
+sndfile_programs =
+endif
+
+if SBC
+noinst_LTLIBRARIES = libsbc.la
+
+libsbc_la_SOURCES = sbc.h sbc.c sbc_math.h sbc_tables.h \
+	sbc_primitives.h sbc_primitives_mmx.h sbc_primitives_neon.h \
+	sbc_primitives.c sbc_primitives_mmx.c sbc_primitives_neon.c
+
+libsbc_la_CFLAGS = -finline-functions -fgcse-after-reload \
+				-funswitch-loops -funroll-loops
+
+noinst_PROGRAMS = sbcinfo sbcdec sbcenc $(sndfile_programs)
+
+sbcdec_SOURCES = sbcdec.c formats.h
+sbcdec_LDADD = libsbc.la
+
+sbcenc_SOURCES = sbcenc.c formats.h
+sbcenc_LDADD = libsbc.la
+
+if SNDFILE
+sbctester_LDADD = @SNDFILE_LIBS@
+endif
+endif
+
+AM_CFLAGS = @SNDFILE_CFLAGS@
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/sbc/formats.h b/sbc/formats.h
new file mode 100644
index 0000000..6c1960d
--- /dev/null
+++ b/sbc/formats.h
@@ -0,0 +1,55 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  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
+ *
+ */
+
+#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
index 0000000..14e5869
--- /dev/null
+++ b/sbc/sbc.c
@@ -0,0 +1,1258 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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 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 void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8])
+{
+	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 < frame->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 < frame->subbands; sb++) {
+					if (frame->scale_factor[ch][sb] == 0)
+						bitneed[ch][sb] = -5;
+					else {
+						if (frame->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 < frame->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 < frame->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 < frame->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 < frame->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 < frame->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 < frame->subbands; sb++) {
+					if (frame->scale_factor[ch][sb] == 0)
+						bitneed[ch][sb] = -5;
+					else {
+						if (frame->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 < frame->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 < frame->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 >= frame->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 >= frame->subbands) break;
+			} else
+				ch = 1;
+		}
+
+	}
+
+}
+
+/*
+ * 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 int sbc_pack_frame_internal(uint8_t *data,
+					struct sbc_frame *frame, size_t len,
+					int frame_subbands, int frame_channels)
+{
+	/* 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) {
+		/* like frame->sb_sample but joint stereo */
+		int32_t sb_sample_j[16][2];
+		/* scalefactor and scale_factor in joint case */
+		uint32_t scalefactor_j[2];
+		uint8_t scale_factor_j[2];
+
+		uint8_t joint = 0;
+		frame->joint = 0;
+
+		for (sb = 0; sb < frame_subbands - 1; sb++) {
+			scale_factor_j[0] = 0;
+			scalefactor_j[0] = 2 << SCALE_OUT_BITS;
+			scale_factor_j[1] = 0;
+			scalefactor_j[1] = 2 << SCALE_OUT_BITS;
+
+			for (blk = 0; blk < frame->blocks; blk++) {
+				uint32_t tmp;
+				/* Calculate joint stereo signal */
+				sb_sample_j[blk][0] =
+					ASR(frame->sb_sample_f[blk][0][sb], 1) +
+					ASR(frame->sb_sample_f[blk][1][sb], 1);
+				sb_sample_j[blk][1] =
+					ASR(frame->sb_sample_f[blk][0][sb], 1) -
+					ASR(frame->sb_sample_f[blk][1][sb], 1);
+
+				/* calculate scale_factor_j and scalefactor_j for joint case */
+				tmp = fabs(sb_sample_j[blk][0]);
+				while (scalefactor_j[0] < tmp) {
+					scale_factor_j[0]++;
+					scalefactor_j[0] *= 2;
+				}
+				tmp = fabs(sb_sample_j[blk][1]);
+				while (scalefactor_j[1] < tmp) {
+					scale_factor_j[1]++;
+					scalefactor_j[1] *= 2;
+				}
+			}
+
+			/* decide whether to join this subband */
+			if ((frame->scale_factor[0][sb] +
+					frame->scale_factor[1][sb]) >
+					(scale_factor_j[0] +
+					scale_factor_j[1])) {
+				/* use joint stereo for this subband */
+				joint |= 1 << (frame_subbands - 1 - sb);
+				frame->joint |= 1 << sb;
+				frame->scale_factor[0][sb] = scale_factor_j[0];
+				frame->scale_factor[1][sb] = scale_factor_j[1];
+				for (blk = 0; blk < frame->blocks; blk++) {
+					frame->sb_sample_f[blk][0][sb] =
+							sb_sample_j[blk][0];
+					frame->sb_sample_f[blk][1][sb] =
+							sb_sample_j[blk][1];
+				}
+			}
+		}
+
+		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 int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len)
+{
+	if (frame->subbands == 4) {
+		if (frame->channels == 1)
+			return sbc_pack_frame_internal(data, frame, len, 4, 1);
+		else
+			return sbc_pack_frame_internal(data, frame, len, 4, 2);
+	} else {
+		if (frame->channels == 1)
+			return sbc_pack_frame_internal(data, frame, len, 8, 1);
+		else
+			return sbc_pack_frame_internal(data, frame, len, 8, 2);
+	}
+}
+
+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;
+	}
+
+	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, size_t *written)
+{
+	struct sbc_priv *priv;
+	int framelen, samples;
+	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;
+	}
+
+	/* 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);
+
+	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);
+
+	if (written)
+		*written = framelen;
+
+	return samples * priv->frame.channels * 2;
+}
+
+void sbc_finish(sbc_t *sbc)
+{
+	if (!sbc)
+		return;
+
+	if (sbc->priv_alloc_base)
+		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)
+		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
index 0000000..0d834d6
--- /dev/null
+++ b/sbc/sbc.h
@@ -0,0 +1,112 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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, size_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
index 0000000..b87bc81
--- /dev/null
+++ b/sbc/sbc_math.h
@@ -0,0 +1,60 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..2105280
--- /dev/null
+++ b/sbc/sbc_primitives.c
@@ -0,0 +1,470 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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_neon.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);
+		}
+	}
+}
+
+/*
+ * 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->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_NEON_SUPPORT
+	sbc_init_primitives_neon(state);
+#endif
+}
diff --git a/sbc/sbc_primitives.h b/sbc/sbc_primitives.h
new file mode 100644
index 0000000..3d01c11
--- /dev/null
+++ b/sbc/sbc_primitives.h
@@ -0,0 +1,75 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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);
+	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_mmx.c b/sbc/sbc_primitives_mmx.c
new file mode 100644
index 0000000..08e9ca2
--- /dev/null
+++ b/sbc/sbc_primitives_mmx.c
@@ -0,0 +1,320 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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)
+		: "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)
+		: "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 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->implementation_info = "MMX";
+	}
+}
+
+#endif
diff --git a/sbc/sbc_primitives_mmx.h b/sbc/sbc_primitives_mmx.h
new file mode 100644
index 0000000..c1e44a5
--- /dev/null
+++ b/sbc/sbc_primitives_mmx.h
@@ -0,0 +1,40 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..f1bc7b4
--- /dev/null
+++ b/sbc/sbc_primitives_neon.c
@@ -0,0 +1,246 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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);
+}
+
+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->implementation_info = "NEON";
+}
+
+#endif
diff --git a/sbc/sbc_primitives_neon.h b/sbc/sbc_primitives_neon.h
new file mode 100644
index 0000000..30766ed
--- /dev/null
+++ b/sbc/sbc_primitives_neon.h
@@ -0,0 +1,40 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..0057c73
--- /dev/null
+++ b/sbc/sbc_tables.h
@@ -0,0 +1,659 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2004-2009  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
index 0000000..9e87d63
--- /dev/null
+++ b/sbc/sbcdec.c
@@ -0,0 +1,295 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) decoder
+ *
+ *  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 <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-2009  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':
+			if (output)
+				free(output);
+			output = strdup(optarg);
+			tofile = 0;
+			break;
+
+		case 'f' :
+			if (output)
+				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);
+
+	if (output)
+		free(output);
+
+	return 0;
+}
diff --git a/sbc/sbcenc.c b/sbc/sbcenc.c
new file mode 100644
index 0000000..0e3b6fb
--- /dev/null
+++ b/sbc/sbcenc.c
@@ -0,0 +1,307 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) encoder
+ *
+ *  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 <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;
+	size_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-2009  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
index 0000000..645de9b
--- /dev/null
+++ b/sbc/sbcinfo.c
@@ -0,0 +1,321 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  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 <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
index 0000000..a7cf85f
--- /dev/null
+++ b/sbc/sbctester.c
@@ -0,0 +1,357 @@
+/*
+ *
+ *  Bluetooth low-complexity, subband codec (SBC) library
+ *
+ *  Copyright (C) 2007-2009  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-2009  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/Makefile.am b/scripts/Makefile.am
new file mode 100644
index 0000000..cbd30b1
--- /dev/null
+++ b/scripts/Makefile.am
@@ -0,0 +1,35 @@
+
+if UDEVRULES
+rulesdir = @UDEV_DATADIR@
+
+rules_DATA = 97-bluetooth.rules
+
+if HID2HCI
+rules_DATA += 97-bluetooth-hid2hci.rules
+endif
+
+if PCMCIA
+rules_DATA += 97-bluetooth-serial.rules
+endif
+endif
+
+if PCMCIA
+udevdir = $(libexecdir)/udev
+
+udev_SCRIPTS = bluetooth_serial
+endif
+
+CLEANFILES = $(rules_DATA)
+
+EXTRA_DIST = bluetooth-hid2hci.rules bluetooth-serial.rules bluetooth_serial
+
+MAINTAINERCLEANFILES = Makefile.in
+
+97-bluetooth.rules: bluetooth.rules
+	cp $< $@
+
+97-bluetooth-hid2hci.rules: bluetooth-hid2hci.rules
+	cp $< $@
+
+97-bluetooth-serial.rules: bluetooth-serial.rules
+	cp $< $@
diff --git a/scripts/bluetooth-hid2hci.rules b/scripts/bluetooth-hid2hci.rules
new file mode 100644
index 0000000..1b231d1
--- /dev/null
+++ b/scripts/bluetooth-hid2hci.rules
@@ -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
index 0000000..072335f
--- /dev/null
+++ b/scripts/bluetooth-serial.rules
@@ -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
index 0000000..6809e9d
--- /dev/null
+++ b/scripts/bluetooth.rules.in
@@ -0,0 +1,3 @@
+# 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"
diff --git a/scripts/bluetooth_serial b/scripts/bluetooth_serial
new file mode 100644
index 0000000..e5be6c2
--- /dev/null
+++ b/scripts/bluetooth_serial
@@ -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/Makefile.am b/serial/Makefile.am
new file mode 100644
index 0000000..f8f26e2
--- /dev/null
+++ b/serial/Makefile.am
@@ -0,0 +1,24 @@
+
+if SERIALPLUGIN
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES = serial.la
+
+serial_la_SOURCES = main.c \
+			manager.h manager.c port.h port.c \
+			proxy.h proxy.c
+
+LDADD = $(top_builddir)/common/libhelper.a \
+		@GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+endif
+
+AM_LDFLAGS = -module -avoid-version -no-undefined
+
+AM_CFLAGS = -fvisibility=hidden \
+		@BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/src
+
+EXTRA_DIST = serial.conf
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/serial/main.c b/serial/main.c
new file mode 100644
index 0000000..71e5aa3
--- /dev/null
+++ b/serial/main.c
@@ -0,0 +1,59 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..71f1a5e
--- /dev/null
+++ b/serial/manager.c
@@ -0,0 +1,183 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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/hci.h>
+#include <bluetooth/hci_lib.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 "logging.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 SERIAL_PORT_UUID	"00001101-0000-1000-8000-00805F9B34FB"
+#define DIALUP_NET_UUID		"00001103-0000-1000-8000-00805F9B34FB"
+#define OBJECT_PUSH_UUID	"00001105-0000-1000-8000-00805F9B34FB"
+#define FILE_TRANSFER_UUID	"00001106-0000-1000-8000-00805F9B34FB"
+#define RFCOMM_UUID_STR		"00000003-0000-1000-8000-00805F9B34FB"
+
+static DBusConnection *connection = NULL;
+GSList *adapters = 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
index 0000000..112cf2f
--- /dev/null
+++ b/serial/manager.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+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
index 0000000..42a4f3d
--- /dev/null
+++ b/serial/port.c
@@ -0,0 +1,644 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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 "logging.h"
+#include "glib-helper.h"
+#include "btio.h"
+
+#include "error.h"
+#include "manager.h"
+#include "storage.h"
+#include "port.h"
+
+#define SERIAL_PORT_INTERFACE	"org.bluez.Serial"
+#define ERROR_INVALID_ARGS	"org.bluez.Error.InvalidArguments"
+#define ERROR_DOES_NOT_EXIST	"org.bluez.Error.DoesNotExist"
+
+#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;
+	}
+
+	debug("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;
+
+	debug("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 inline DBusMessage *does_not_exist(DBusMessage *msg,
+					const char *description)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist",
+				description);
+}
+
+static inline DBusMessage *invalid_arguments(DBusMessage *msg,
+					const char *description)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+				description);
+}
+
+static inline DBusMessage *failed(DBusMessage *msg, const char *description)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+				description);
+}
+
+static void open_notify(int fd, int err, struct serial_port *port)
+{
+	struct serial_device *device = port->device;
+	DBusMessage *reply;
+
+	if (err) {
+		/* Max tries exceeded */
+		port_release(port);
+		reply = 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 = 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 = failed(port->msg, strerror(err));
+		g_io_channel_shutdown(chan, TRUE, NULL);
+		goto fail;
+	}
+
+	port->dev = g_strdup_printf("/dev/rfcomm%d", port->id);
+
+	debug("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 = failed(port->msg, strerror(-err));
+		goto failed;
+	}
+
+	if (!recs || !recs->data) {
+		error("No record found");
+		reply = 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 = 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 = 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 does_not_exist(msg, "Does not match");
+
+		port = create_port(device, NULL, channel);
+	}
+
+	if (port->listener_id)
+		return 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) {
+		DBusMessage *reply;
+
+		error("%s", strerror(-err));
+		g_dbus_remove_watch(conn, port->listener_id);
+		port->listener_id = 0;
+		reply = failed(msg, strerror(-err));
+		return reply;
+	}
+
+	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 does_not_exist(msg, "Port does not exist");
+
+	if (!port->listener_id)
+		return failed(msg, "Not connected");
+
+	owner = dbus_message_get_sender(port->msg);
+	caller = dbus_message_get_sender(msg);
+	if (!g_str_equal(owner, caller))
+		return failed(msg, "Operation not permited");
+
+	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;
+	}
+
+	debug("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
index 0000000..6b638cb
--- /dev/null
+++ b/serial/port.h
@@ -0,0 +1,29 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+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
index 0000000..88a414d
--- /dev/null
+++ b/serial/proxy.c
@@ -0,0 +1,1311 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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/hci.h>
+#include <bluetooth/hci_lib.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 "logging.h"
+#include "textfile.h"
+
+#include "error.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "proxy.h"
+
+#define SERIAL_PORT_NAME	"spp"
+#define SERIAL_PORT_UUID	"00001101-0000-1000-8000-00805F9B34FB"
+
+#define DIALUP_NET_NAME		"dun"
+#define DIALUP_NET_UUID		"00001103-0000-1000-8000-00805F9B34FB"
+
+#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 inline DBusMessage *does_not_exist(DBusMessage *msg,
+					const char *description)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist",
+				description);
+}
+
+static inline DBusMessage *invalid_arguments(DBusMessage *msg,
+					const char *description)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+				description);
+}
+
+static inline DBusMessage *failed(DBusMessage *msg, const char *description)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+				description);
+}
+
+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 GIOError channel_write(GIOChannel *chan, char *buf, size_t size)
+{
+	GIOError err = G_IO_ERROR_NONE;
+	gsize wbytes, written;
+
+	wbytes = written = 0;
+	while (wbytes < size) {
+		err = g_io_channel_write(chan,
+				buf + wbytes,
+				size - wbytes,
+				&written);
+
+		if (err != G_IO_ERROR_NONE)
+			return err;
+
+		wbytes += written;
+	}
+
+	return err;
+}
+
+static gboolean forward_data(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+	char buf[BUF_SIZE];
+	struct serial_proxy *prx = data;
+	GIOChannel *dest;
+	GIOError err;
+	size_t rbytes;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	dest = (chan == prx->rfcomm) ? prx->local : prx->rfcomm;
+
+	if (cond & (G_IO_HUP | G_IO_ERR)) {
+		/* Try forward remaining data */
+		do {
+			rbytes = 0;
+			err = g_io_channel_read(chan, buf, sizeof(buf), &rbytes);
+			if (err != G_IO_ERROR_NONE || rbytes == 0)
+				break;
+
+			err = channel_write(dest, buf, rbytes);
+		} while (err == G_IO_ERROR_NONE);
+
+		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 = 0;
+	err = g_io_channel_read(chan, buf, sizeof(buf), &rbytes);
+	if (err != G_IO_ERROR_NONE)
+		return FALSE;
+
+	err = channel_write(dest, buf, rbytes);
+	if (err != G_IO_ERROR_NONE)
+		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;
+	}
+
+	debug("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;
+	}
+
+	debug("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 == -EALREADY)
+		return failed(msg, "Already enabled");
+	else if (err == -ENOMEM)
+		return failed(msg, "Unable to allocate new service record");
+	else if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE "Failed",
+				"Proxy enable failed (%s)", 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 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 failed(msg, "Not allowed");
+
+	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 invalid_arguments(msg, "Invalid baud rate");
+
+	ctrl = prx->proxy_ti.c_cflag;
+	if (set_databits(databits, &ctrl) < 0)
+		return invalid_arguments(msg, "Invalid data bits");
+
+	if (set_stopbits(stopbits, &ctrl) < 0)
+		return invalid_arguments(msg, "Invalid stop bits");
+
+	if (set_parity(paritystr, &ctrl) < 0)
+		return invalid_arguments(msg, "Invalid parity");
+
+	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;
+
+	debug("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);
+
+	debug("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 invalid_arguments(msg, "Invalid UUID");
+
+	err = register_proxy(adapter, uuid_str, address, &proxy);
+	g_free(uuid_str);
+
+	if (err == -EINVAL)
+		return invalid_arguments(msg, "Invalid address");
+	else if (err == -EALREADY)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExist",
+						"Proxy already exists");
+	else if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE "Failed",
+				"Proxy creation failed (%s)", 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 does_not_exist(msg, "Invalid proxy path");
+
+	prx = l->data;
+
+	sender = dbus_message_get_sender(msg);
+	if (g_strcmp0(prx->owner, sender) != 0)
+		return failed(msg, "Permission denied");
+
+	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) {
+			debug("%s: %s", file, gerr->message);
+			g_error_free(gerr);
+			g_key_file_free(config);
+			return;
+		}
+
+		address = g_key_file_get_string(config, group_str, "Address",
+									&gerr);
+		if (gerr) {
+			debug("%s: %s", file, gerr->message);
+			g_error_free(gerr);
+			g_key_file_free(config);
+			g_free(uuid_str);
+			return;
+		}
+
+		err = register_proxy(adapter, uuid_str, address, &prx);
+		if (err == -EINVAL)
+			error("Invalid address.");
+		else if (err == -EALREADY)
+			debug("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);
+
+	debug("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
index 0000000..721e97d
--- /dev/null
+++ b/serial/proxy.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+
+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
index 0000000..43ee6af
--- /dev/null
+++ b/serial/serial.conf
@@ -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/Android.mk b/src/Android.mk
new file mode 100755
index 0000000..23135fa
--- /dev/null
+++ b/src/Android.mk
@@ -0,0 +1,72 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# libbluetoothd
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	adapter.c \
+	agent.c \
+	dbus-common.c \
+	dbus-hci.c \
+	device.c \
+	error.c \
+	main.c \
+	manager.c \
+	plugin.c \
+	rfkill.c \
+	sdpd-request.c \
+	sdpd-service.c \
+	sdpd-server.c \
+	sdpd-database.c \
+	security.c \
+	storage.c
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\" \
+	-DSTORAGEDIR=\"/data/misc/bluetoothd\" \
+	-DCONFIGDIR=\"/etc/bluez\" \
+	-DSERVICEDIR=\"/system/bin\" \
+	-DPLUGINDIR=\"/system/lib/bluez-plugin\" \
+	-DANDROID_SET_AID_AND_CAP \
+	-DANDROID_EXPAND_NAME
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+	$(LOCAL_PATH)/../gdbus \
+	$(LOCAL_PATH)/../plugins \
+	$(call include-path-for, glib) \
+	$(call include-path-for, glib)/glib \
+	$(call include-path-for, dbus)
+
+LOCAL_SHARED_LIBRARIES := \
+	libdl \
+	libbluetooth \
+	libdbus \
+	libcutils
+
+LOCAL_STATIC_LIBRARIES := \
+	libglib_static \
+	libbluez-common-static \
+	libbuiltinplugin \
+	libgdbus_static
+
+LOCAL_MODULE:=libbluetoothd
+
+include $(BUILD_SHARED_LIBRARY)
+
+#
+# bluetoothd
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetoothd
+
+LOCAL_MODULE:=bluetoothd
+
+include $(BUILD_EXECUTABLE)
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..569c060
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,47 @@
+
+if CONFIGFILES
+dbusdir = $(sysconfdir)/dbus-1/system.d
+
+dbus_DATA = bluetooth.conf
+
+confdir = $(sysconfdir)/bluetooth
+
+conf_DATA = main.conf
+
+statedir = $(localstatedir)/lib/bluetooth
+
+state_DATA =
+endif
+
+sbin_PROGRAMS = bluetoothd
+
+bluetoothd_SOURCES = main.c security.c hcid.h sdpd.h \
+	sdpd-server.c sdpd-request.c sdpd-service.c sdpd-database.c \
+	plugin.h plugin.c storage.h storage.c agent.h agent.c rfkill.c \
+	error.h error.c manager.h manager.c adapter.h adapter.c \
+	device.h device.c dbus-common.c dbus-common.h dbus-hci.h dbus-hci.c
+
+bluetoothd_LDADD = $(top_builddir)/common/libhelper.a \
+			$(top_builddir)/plugins/libbuiltin.la \
+			@GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@ -ldl
+
+bluetoothd_LDFLAGS = -Wl,--export-dynamic
+
+if MAINTAINER_MODE
+plugindir = $(abs_top_srcdir)/plugins
+else
+plugindir = $(libdir)/bluetooth/plugins
+endif
+
+AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@ \
+						-DPLUGINDIR=\""$(plugindir)"\"
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_builddir)/plugins
+
+if MANPAGES
+man_MANS = bluetoothd.8
+endif
+
+EXTRA_DIST = bluetooth.conf bluetoothd.8 main.conf
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/adapter.c b/src/adapter.c
new file mode 100644
index 0000000..9e9bacd
--- /dev/null
+++ b/src/adapter.c
@@ -0,0 +1,3175 @@
+/*
+ *
+ *  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
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/ioctl.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 <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+
+#include "hcid.h"
+#include "sdpd.h"
+#include "sdp-xml.h"
+#include "manager.h"
+#include "adapter.h"
+#include "device.h"
+#include "dbus-common.h"
+#include "dbus-hci.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "agent.h"
+#include "storage.h"
+
+#define NUM_ELEMENTS(table) (sizeof(table)/sizeof(const char *))
+
+#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
+
+#define check_address(address) bachk(address)
+
+static DBusConnection *connection = NULL;
+static GSList *adapter_drivers = 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 */
+};
+
+struct service_auth {
+	service_auth_cb cb;
+	void *user_data;
+	struct btd_device *device;
+};
+
+struct btd_adapter {
+	uint16_t dev_id;
+	int up;
+	char *path;			/* adapter object path */
+	bdaddr_t bdaddr;		/* adapter Bluetooth Address */
+	guint discov_timeout_id;	/* discoverable timeout 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 */
+	uint8_t mode;			/* off, connectable, discoverable,
+					 * limited */
+	uint8_t global_mode;		/* last valid global mode */
+	int state;			/* standard inq, periodic inq, name
+					 * resloving */
+	GSList *found_devices;
+	GSList *oor_devices;		/* out of range device list */
+	DBusMessage *discovery_cancel;	/* discovery cancel message request */
+	GSList *passkey_agents;
+	struct agent *agent;		/* For the new API */
+	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 */
+
+	struct hci_dev dev;		/* hci info */
+	gboolean pairable;		/* pairable state */
+
+	gboolean initialized;
+	gboolean already_up;		/* adapter was already up on init */
+
+	gboolean off_requested;		/* DEVDOWN ioctl was called */
+
+	uint8_t svc_cache;		/* Service Class cache */
+	gboolean cache_enable;
+
+	gint ref;
+};
+
+static void adapter_set_pairable_timeout(struct btd_adapter *adapter,
+					guint interval);
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+			"Invalid arguments in method call");
+}
+
+static inline DBusMessage *not_available(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+			"Not Available");
+}
+
+static inline DBusMessage *adapter_not_ready(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady",
+			"Adapter is not ready");
+}
+
+static inline DBusMessage *no_such_adapter(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter",
+							"No such adapter");
+}
+
+static inline DBusMessage *failed_strerror(DBusMessage *msg, int err)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+							strerror(err));
+}
+
+static inline DBusMessage *in_progress(DBusMessage *msg, const char *str)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str);
+}
+
+static inline DBusMessage *not_in_progress(DBusMessage *msg, const char *str)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotInProgress", str);
+}
+
+static inline DBusMessage *not_authorized(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized",
+			"Not authorized");
+}
+
+static inline DBusMessage *unsupported_major_class(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg,
+			ERROR_INTERFACE ".UnsupportedMajorClass",
+			"Unsupported Major Class");
+}
+
+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_free(dev);
+}
+
+void clear_found_devices_list(struct btd_adapter *adapter)
+{
+	if (!adapter->found_devices)
+		return;
+
+	g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL);
+	g_slist_free(adapter->found_devices);
+	adapter->found_devices = NULL;
+}
+
+static int set_service_classes(struct btd_adapter *adapter, uint8_t value)
+{
+	struct hci_dev *dev = &adapter->dev;
+	const uint8_t *cls = dev->class;
+	uint32_t dev_class;
+	int dd, err;
+
+	if (cls[2] == value)
+		return 0; /* Already set */
+
+	dd = hci_open_dev(adapter->dev_id);
+	if (dd < 0) {
+		err = -errno;
+		error("Can't open device hci%d: %s (%d)",
+				adapter->dev_id, strerror(errno), errno);
+		return err;
+	}
+
+	dev_class = (value << 16) | (cls[1] << 8) | cls[0];
+
+	debug("Changing service classes to 0x%06x", dev_class);
+
+	if (hci_write_class_of_dev(dd, dev_class, HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't write class of device: %s (%d)",
+						strerror(errno), errno);
+		hci_close_dev(dd);
+		return err;
+	}
+
+	hci_close_dev(dd);
+
+	return 0;
+}
+
+int set_major_and_minor_class(struct btd_adapter *adapter, uint8_t major,
+								uint8_t minor)
+{
+	struct hci_dev *dev = &adapter->dev;
+	const uint8_t *cls = dev->class;
+	uint32_t dev_class;
+	int dd, err;
+
+	dd = hci_open_dev(adapter->dev_id);
+	if (dd < 0) {
+		err = -errno;
+		error("Can't open device hci%d: %s (%d)",
+				adapter->dev_id, strerror(errno), errno);
+		return err;
+	}
+
+	dev_class = (cls[2] << 16) | ((cls[1] & 0x20) << 8) |
+						((major & 0xdf) << 8) | minor;
+
+	debug("Changing major/minor class to 0x%06x", dev_class);
+
+	if (hci_write_class_of_dev(dd, dev_class, HCI_REQ_TIMEOUT) < 0) {
+		int err = -errno;
+		error("Can't write class of device: %s (%d)",
+						strerror(errno), errno);
+		hci_close_dev(dd);
+		return err;
+	}
+
+	hci_close_dev(dd);
+	return 0;
+}
+
+int pending_remote_name_cancel(struct btd_adapter *adapter)
+{
+	struct remote_dev_info *dev, match;
+	int err = 0;
+
+	/* 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;
+
+	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:
+	case MODE_LIMITED:
+		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("limited", mode) == 0)
+		return MODE_LIMITED;
+	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_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_connectable(adapter->dev_id);
+
+	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)
+		return;
+
+	adapter->discov_timeout_id = g_timeout_add_seconds(interval,
+							discov_timeout_handler,
+							adapter);
+}
+
+static int set_mode(struct btd_adapter *adapter, uint8_t new_mode)
+{
+	int err;
+	const char *modestr;
+
+	if (!adapter->up && new_mode != MODE_OFF) {
+		err = adapter_ops->set_powered(adapter->dev_id, TRUE);
+		if (err < 0)
+			return err;
+	}
+
+	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;
+
+	if (new_mode == MODE_CONNECTABLE)
+		err = adapter_ops->set_connectable(adapter->dev_id);
+	else
+		err = adapter_ops->set_discoverable(adapter->dev_id);
+
+	if (err < 0)
+		return err;
+
+	if (new_mode > MODE_CONNECTABLE) {
+		adapter_remove_discov_timeout(adapter);
+
+		if (adapter->discov_timeout)
+			adapter_set_discov_timeout(adapter,
+						adapter->discov_timeout);
+
+		if (new_mode != MODE_LIMITED && adapter->mode == MODE_LIMITED)
+			adapter_ops->set_limited_discoverable(adapter->dev_id,
+						adapter->dev.class, FALSE);
+	}
+
+done:
+	modestr = mode2str(new_mode);
+
+	write_device_mode(&adapter->bdaddr, modestr);
+
+	adapter->mode = new_mode;
+
+	return 0;
+}
+
+static DBusMessage *set_powered(DBusConnection *conn, DBusMessage *msg,
+				gboolean powered, void *data)
+{
+	struct btd_adapter *adapter = data;
+	uint8_t mode;
+	int err;
+
+	mode = powered ? get_mode(&adapter->bdaddr, "on") : MODE_OFF;
+
+	if (mode == adapter->mode)
+		return dbus_message_new_method_return(msg);
+
+	err = set_mode(adapter, mode);
+	if (err < 0)
+		return failed_strerror(msg, -err);
+
+	return dbus_message_new_method_return(msg);
+}
+
+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 == MODE_DISCOVERABLE && adapter->pairable &&
+					adapter->discov_timeout > 0 &&
+					adapter->discov_timeout <= 60)
+		mode = MODE_LIMITED;
+
+	if (mode == adapter->mode)
+		return dbus_message_new_method_return(msg);
+
+	err = set_mode(adapter, mode);
+	if (err < 0)
+		return failed_strerror(msg, -err);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_pairable(DBusConnection *conn, DBusMessage *msg,
+				gboolean pairable, void *data)
+{
+	struct btd_adapter *adapter = data;
+	uint8_t mode;
+	int err;
+
+	if (adapter->scan_mode == SCAN_DISABLED)
+		return adapter_not_ready(msg);
+
+	if (pairable == adapter->pairable)
+		goto done;
+
+	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);
+
+	if (!(adapter->scan_mode & SCAN_INQUIRY))
+		goto done;
+
+	mode = (pairable && adapter->discov_timeout > 0 &&
+				adapter->discov_timeout <= 60) ?
+					MODE_LIMITED : MODE_DISCOVERABLE;
+
+	err = set_mode(adapter, mode);
+	if (err < 0 && msg)
+		return failed_strerror(msg, -err);
+
+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 void session_remove(struct session_req *req)
+{
+	struct btd_adapter *adapter = req->adapter;
+
+	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;
+
+		debug("Switching to '%s' mode", mode2str(mode));
+
+		set_mode(adapter, mode);
+	} else {
+		adapter->disc_sessions = g_slist_remove(adapter->disc_sessions,
+							req);
+
+		if (adapter->disc_sessions)
+			return;
+
+		debug("Stopping discovery");
+
+		pending_remote_name_cancel(adapter);
+
+		clear_found_devices_list(adapter);
+
+		g_slist_free(adapter->oor_devices);
+		adapter->oor_devices = NULL;
+
+		if (adapter->scheduler_id)
+			g_source_remove(adapter->scheduler_id);
+
+		adapter_ops->stop_discovery(adapter->dev_id);
+	}
+}
+
+static void session_free(struct session_req *req)
+{
+	debug("%s session %p with %s deactivated",
+		req->mode ? "Mode" : "Discovery", req, req->owner);
+
+	if (req->id)
+		g_dbus_remove_watch(req->conn, req->id);
+
+	session_remove(req);
+
+	if (req->msg)
+		dbus_message_unref(req->msg);
+	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 struct session_req *session_ref(struct session_req *req)
+{
+	req->refcount++;
+
+	debug("session_ref(%p): ref=%d", req, req->refcount);
+
+	return req;
+}
+
+static void session_unref(struct session_req *req)
+{
+	req->refcount--;
+
+	debug("session_unref(%p): ref=%d", req, req->refcount);
+
+	if (req->refcount)
+		return;
+
+	session_free(req);
+}
+
+static struct session_req *create_session(struct btd_adapter *adapter,
+					DBusConnection *conn, DBusMessage *msg,
+					uint8_t mode, GDBusWatchFunction cb)
+{
+	struct session_req *req;
+	const char *sender = dbus_message_get_sender(msg);
+
+	req = g_new0(struct session_req, 1);
+	req->adapter = adapter;
+	req->conn = dbus_connection_ref(conn);
+	req->msg = dbus_message_ref(msg);
+	req->owner = g_strdup(dbus_message_get_sender(msg));
+	req->mode = mode;
+
+	if (cb)
+		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 void confirm_mode_cb(struct agent *agent, DBusError *derr, void *data)
+{
+	struct session_req *req = data;
+	int err;
+	DBusMessage *reply;
+
+	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);
+	if (err < 0)
+		reply = failed_strerror(req->msg, -err);
+	else
+		reply = dbus_message_new_method_return(req->msg);
+
+	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);
+}
+
+static void update_ext_inquiry_response(struct btd_adapter *adapter)
+{
+	uint8_t fec = 0, data[240];
+	struct hci_dev *dev = &adapter->dev;
+	int dd;
+
+	if (!(dev->features[6] & LMP_EXT_INQ))
+		return;
+
+	memset(data, 0, sizeof(data));
+
+	dd = hci_open_dev(adapter->dev_id);
+	if (dd < 0)
+		return;
+
+	if (dev->ssp_mode > 0)
+		create_ext_inquiry_response((char *) dev->name, data);
+
+	if (hci_write_ext_inquiry_response(dd, fec, data,
+						HCI_REQ_TIMEOUT) < 0)
+		error("Can't write extended inquiry response: %s (%d)",
+						strerror(errno), errno);
+
+	hci_close_dev(dd);
+}
+
+void adapter_update_local_name(bdaddr_t *bdaddr, uint8_t status, void *ptr)
+{
+	read_local_name_rp rp;
+	struct hci_dev *dev;
+	struct btd_adapter *adapter;
+	gchar *name;
+
+	if (status)
+		return;
+
+	adapter = manager_find_adapter(bdaddr);
+	if (!adapter) {
+		error("Unable to find matching adapter");
+		return;
+	}
+
+	dev = &adapter->dev;
+
+	memcpy(&rp, ptr, MAX_NAME_LENGTH);
+	if (strncmp((char *) rp.name, (char *) dev->name, MAX_NAME_LENGTH) == 0)
+		return;
+
+	strncpy((char *) dev->name, (char *) rp.name, MAX_NAME_LENGTH);
+
+	write_local_name(bdaddr, (char *) dev->name);
+
+	update_ext_inquiry_response(adapter);
+
+	name = g_strdup((char *) dev->name);
+
+	if (connection)
+		emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE,
+				"Name", DBUS_TYPE_STRING, &name);
+	g_free(name);
+}
+
+void adapter_setname_complete(bdaddr_t *local, uint8_t status)
+{
+	struct btd_adapter *adapter;
+	int err;
+
+	if (status)
+		return;
+
+	adapter = manager_find_adapter(local);
+	if (!adapter) {
+		error("No matching adapter found");
+		return;
+	}
+
+	err = adapter_ops->read_name(adapter->dev_id);
+	if (err < 0)
+		error("Sending getting name command failed: %s (%d)",
+						strerror(errno), errno);
+
+}
+
+static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg,
+					const char *name, void *data)
+{
+	struct btd_adapter *adapter = data;
+	struct hci_dev *dev = &adapter->dev;
+	int err;
+
+	if (!g_utf8_validate(name, -1, NULL)) {
+		error("Name change failed: supplied name isn't valid UTF-8");
+		return invalid_args(msg);
+	}
+
+	if (strncmp(name, (char *) dev->name, MAX_NAME_LENGTH) == 0)
+		goto done;
+
+	if (!adapter->up)
+		return failed_strerror(msg, -EHOSTDOWN);
+
+	err = adapter_ops->set_name(adapter->dev_id, name);
+	if (err < 0)
+		return failed_strerror(msg, err);
+
+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;
+}
+
+struct btd_device *adapter_find_connection(struct btd_adapter *adapter,
+						uint16_t handle)
+{
+	GSList *l;
+
+	for (l = adapter->connections; l; l = l->next) {
+		struct btd_device *device = l->data;
+
+		if (device_has_connection(device, handle))
+			return device;
+	}
+
+	return NULL;
+}
+
+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);
+	g_free(devices);
+}
+
+struct btd_device *adapter_create_device(DBusConnection *conn,
+						struct btd_adapter *adapter,
+						const char *address)
+{
+	struct btd_device *device;
+	const char *path;
+
+	debug("adapter_create_device(%s)", address);
+
+	device = device_create(conn, adapter, address);
+	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)
+{
+	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)
+		agent = adapter->agent;
+
+	if (agent && device_is_authorizing(device))
+		agent_cancel(agent);
+
+	agent = device_get_agent(device);
+
+	if (agent) {
+		agent_destroy(agent, FALSE);
+		device_set_agent(device, NULL);
+	}
+
+	device_remove(device, conn, TRUE);
+}
+
+struct btd_device *adapter_get_device(DBusConnection *conn,
+						struct btd_adapter *adapter,
+						const gchar *address)
+{
+	struct btd_device *device;
+
+	debug("adapter_get_device(%s)", address);
+
+	if (!adapter)
+		return NULL;
+
+	device = adapter_find_device(adapter, address);
+	if (device)
+		return device;
+
+	return adapter_create_device(conn, adapter, address);
+}
+
+static int adapter_start_inquiry(struct btd_adapter *adapter)
+{
+	gboolean periodic = TRUE;
+
+	if (main_opts.discov_interval)
+		periodic = FALSE;
+
+	pending_remote_name_cancel(adapter);
+
+	return adapter_ops->start_discovery(adapter->dev_id, periodic);
+}
+
+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;
+
+	if (!adapter->up)
+		return adapter_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;
+
+	if (main_opts.name_resolv)
+		adapter->state |= RESOLVE_NAME;
+
+	err = adapter_start_inquiry(adapter);
+	if (err < 0)
+		return failed_strerror(msg, -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 adapter_not_ready(msg);
+
+	req = find_session(adapter->disc_sessions, sender);
+	if (!req)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+				"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];
+	uint32_t class;
+	gboolean value;
+	char **devices;
+	int i;
+	GSList *l;
+
+	ba2str(&adapter->bdaddr, srcaddr);
+
+	if (check_address(srcaddr) < 0)
+		return adapter_not_ready(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 */
+	class = adapter->dev.class[0] |
+			adapter->dev.class[1] << 8 |
+			adapter->dev.class[2] << 16;
+	dict_append_entry(&dict, "Class", DBUS_TYPE_UINT32, &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);
+
+	/* 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 & PERIODIC_INQUIRY || adapter->state & STD_INQUIRY)
+		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);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static DBusMessage *set_property(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	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 invalid_args(msg);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+		return invalid_args(msg);
+
+	dbus_message_iter_get_basic(&iter, &property);
+	dbus_message_iter_next(&iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+		return invalid_args(msg);
+	dbus_message_iter_recurse(&iter, &sub);
+
+	if (g_str_equal("Name", property)) {
+		const char *name;
+
+		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
+			return 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 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 invalid_args(msg);
+
+		dbus_message_iter_get_basic(&sub, &discoverable);
+
+		return set_discoverable(conn, msg, discoverable, data);
+	} else if (g_str_equal("DiscoverableTimeout", property)) {
+		uint32_t timeout;
+
+		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)
+			return 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 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 invalid_args(msg);
+
+		dbus_message_iter_get_basic(&sub, &timeout);
+
+		return set_pairable_timeout(conn, msg, timeout, data);
+	}
+
+	return 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 ret;
+
+	if (!adapter->agent)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"No agent registered");
+
+	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);
+
+	ret = agent_confirm_mode_change(adapter->agent, mode2str(new_mode),
+					confirm_mode_cb, req, NULL);
+	if (ret < 0) {
+		session_unref(req);
+		return failed_strerror(msg, -ret);
+	}
+
+	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 g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+				"No Mode to release");
+
+	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 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 invalid_args(msg);
+
+	if (check_address(address) < 0)
+		return invalid_args(msg);
+
+	device = adapter_find_device(adapter, address);
+	if (!device || !device_is_creating(device, NULL))
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".NotInProgress",
+				"Device creation not in progress");
+
+	if (!device_is_creating(device, sender))
+		return 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);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *create_device(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct btd_adapter *adapter = data;
+	struct btd_device *device;
+	const gchar *address;
+
+	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
+						DBUS_TYPE_INVALID) == FALSE)
+		return invalid_args(msg);
+
+	if (check_address(address) < 0)
+		return invalid_args(msg);
+
+	if (adapter_find_device(adapter, address))
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".AlreadyExists",
+				"Device already exists");
+
+	debug("create_device(%s)", address);
+
+	device = adapter_create_device(conn, adapter, address);
+	if (!device)
+		return NULL;
+
+	device_browse(device, conn, msg, NULL, FALSE);
+
+	return NULL;
+}
+
+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;
+}
+
+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;
+
+	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 invalid_args(msg);
+
+	if (check_address(address) < 0)
+		return invalid_args(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 invalid_args(msg);
+	}
+
+	cap = parse_io_capability(capability);
+	if (cap == IO_CAPABILITY_INVALID)
+		return invalid_args(msg);
+
+	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");
+
+	return device_create_bonding(device, conn, msg, agent_path, cap);
+}
+
+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 invalid_args(msg);
+
+	l = g_slist_find_custom(adapter->devices,
+			path, (GCompareFunc) device_path_cmp);
+	if (!l)
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".DoesNotExist",
+				"Device does not exist");
+	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);
+		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 invalid_args(msg);
+
+	l = g_slist_find_custom(adapter->devices,
+			address, (GCompareFunc) device_address_cmp);
+	if (!l)
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".DoesNotExist",
+				"Device does not exist");
+
+	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->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 g_dbus_create_error(msg,
+				ERROR_INTERFACE ".AlreadyExists",
+				"Agent already exists");
+
+	cap = parse_io_capability(capability);
+	if (cap == IO_CAPABILITY_INVALID)
+		return 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 g_dbus_create_error(msg,
+				ERROR_INTERFACE ".Failed",
+				"Failed to create a new agent");
+
+	adapter->agent = agent;
+
+	debug("Agent registered for hci%d at %s:%s", adapter->dev_id, name,
+			path);
+
+	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 g_dbus_create_error(msg,
+				ERROR_INTERFACE ".DoesNotExist",
+				"No such agent");
+
+	agent_destroy(adapter->agent, FALSE);
+	adapter->agent = NULL;
+
+	return dbus_message_new_method_return(msg);
+}
+
+static sdp_record_t *create_rfcomm_record(struct btd_adapter *adapter,
+					const char *name, uuid_t uuid, uint8_t channel)
+{
+	uuid_t root_uuid, l2cap_uuid, rfcomm_uuid;
+	sdp_list_t *svclass, *root, *proto;
+	sdp_record_t *record;
+
+	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(&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));
+
+	svclass = sdp_list_append(NULL, &uuid);
+	sdp_set_service_classes(record, svclass);
+
+	sdp_set_info_attr(record, name, NULL, NULL);
+
+	return record;
+}
+
+static DBusMessage *add_rfcomm_service_record(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	uuid_t uuid;
+	const char *name;
+	uint8_t channel;
+	uint32_t *uuid_p;
+	uint32_t uuid_net[4];   // network order
+	uint64_t uuid_host[2];  // host
+	sdp_record_t *record;
+	struct btd_adapter *adapter = data;
+
+	DBusMessage *reply;
+
+	if (!dbus_message_get_args(msg, NULL,
+			DBUS_TYPE_STRING, &name,
+			DBUS_TYPE_UINT64, &uuid_host[0],
+			DBUS_TYPE_UINT64, &uuid_host[1],
+			DBUS_TYPE_UINT16, &channel,
+			DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	uuid_p = (uint32_t *)uuid_host;
+	uuid_net[1] = htonl(*uuid_p++);
+	uuid_net[0] = htonl(*uuid_p++);
+	uuid_net[3] = htonl(*uuid_p++);
+	uuid_net[2] = htonl(*uuid_p++);
+
+	sdp_uuid128_create(&uuid, (void *)uuid_net);
+
+	record = create_rfcomm_record(adapter, name, uuid, channel);
+
+	if (!record)
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".Failed",
+				"Failed to create sdp record");
+
+	if (add_record_to_server(&adapter->bdaddr, record))
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".Failed",
+				"Failed to register sdp record");
+
+	printf("npelly new handle %X\n", record->handle);
+	reply = dbus_message_new_method_return(msg);
+	dbus_message_append_args(reply,
+			DBUS_TYPE_UINT32, &record->handle,
+			DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static DBusMessage *remove_service_record(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct btd_adapter *adapter = data;
+	dbus_uint32_t handle;
+
+	if (!dbus_message_get_args(msg, NULL,
+			DBUS_TYPE_UINT32, &handle,
+			DBUS_TYPE_INVALID))
+		return invalid_args(msg);
+
+	if (remove_record_from_server(handle))
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".Failed",
+				"Failed to remove sdp record");
+
+	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	},
+	{ "AddRfcommServiceRecord",	"sttq",	"u",	add_rfcomm_service_record },
+	{ "RemoveServiceRecord",	"u",	"",	remove_service_record },
+	{ }
+};
+
+static GDBusSignalTable adapter_signals[] = {
+	{ "PropertyChanged",		"sv"		},
+	{ "DeviceCreated",		"o"		},
+	{ "DeviceRemoved",		"o"		},
+	{ "DeviceFound",		"sa{sv}"	},
+	{ "DeviceDisappeared",		"s"		},
+	{ }
+};
+
+static inline uint8_t get_inquiry_mode(struct hci_dev *dev)
+{
+	if (dev->features[6] & LMP_EXT_INQ)
+		return 2;
+
+	if (dev->features[3] & LMP_RSSI_INQ)
+		return 1;
+
+	if (dev->manufacturer == 11 &&
+			dev->hci_rev == 0x00 && dev->lmp_subver == 0x0757)
+		return 1;
+
+	if (dev->manufacturer == 15) {
+		if (dev->hci_rev == 0x03 && dev->lmp_subver == 0x6963)
+			return 1;
+		if (dev->hci_rev == 0x09 && dev->lmp_subver == 0x6963)
+			return 1;
+		if (dev->hci_rev == 0x00 && dev->lmp_subver == 0x6965)
+			return 1;
+	}
+
+	if (dev->manufacturer == 31 &&
+			dev->hci_rev == 0x2005 && dev->lmp_subver == 0x1805)
+		return 1;
+
+	return 0;
+}
+
+static int adapter_read_bdaddr(uint16_t dev_id, bdaddr_t *bdaddr)
+{
+	int dd, err;
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		err = -errno;
+		error("Can't open device hci%d: %s (%d)",
+					dev_id, strerror(errno), errno);
+		return err;
+	}
+
+	if (hci_read_bd_addr(dd, bdaddr, HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't read address for hci%d: %s (%d)",
+					dev_id, strerror(errno), errno);
+		hci_close_dev(dd);
+		return err;
+	}
+
+	hci_close_dev(dd);
+
+	return 0;
+}
+
+static int adapter_setup(struct btd_adapter *adapter)
+{
+	struct hci_dev *dev = &adapter->dev;
+	uint8_t events[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00 };
+	uint8_t inqmode;
+	int err , dd;
+	char name[MAX_NAME_LENGTH + 1];
+
+	dd = hci_open_dev(adapter->dev_id);
+	if (dd < 0) {
+		err = -errno;
+		error("Can't open device hci%d: %s (%d)", adapter->dev_id,
+						strerror(errno), errno);
+		return err;
+	}
+
+	if (dev->lmp_ver > 1) {
+		if (dev->features[5] & LMP_SNIFF_SUBR)
+			events[5] |= 0x20;
+
+		if (dev->features[5] & LMP_PAUSE_ENC)
+			events[5] |= 0x80;
+
+		if (dev->features[6] & LMP_EXT_INQ)
+			events[5] |= 0x40;
+
+		if (dev->features[6] & LMP_NFLUSH_PKTS)
+			events[7] |= 0x01;
+
+		if (dev->features[7] & LMP_LSTO)
+			events[6] |= 0x80;
+
+		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 */
+		}
+
+		hci_send_cmd(dd, OGF_HOST_CTL, OCF_SET_EVENT_MASK,
+						sizeof(events), events);
+	}
+
+	if (read_local_name(&adapter->bdaddr, name) == 0)
+		adapter_ops->set_name(adapter->dev_id, name);
+
+	inqmode = get_inquiry_mode(dev);
+	if (inqmode < 1)
+		goto done;
+
+	if (hci_write_inquiry_mode(dd, inqmode, HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't write inquiry mode for %s: %s (%d)",
+					adapter->path, strerror(errno), errno);
+		hci_close_dev(dd);
+		return err;
+	}
+
+done:
+	hci_close_dev(dd);
+	return 0;
+}
+
+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;
+	bdaddr_t dst;
+	char srcaddr[18], dstaddr[18];
+
+	ba2str(&adapter->bdaddr, srcaddr);
+
+	if (g_slist_find_custom(adapter->devices,
+				key, (GCompareFunc) device_address_cmp))
+		return;
+
+	device = device_create(connection, adapter, key);
+	if (!device)
+		return;
+
+	device_set_temporary(device, FALSE);
+	adapter->devices = g_slist_append(adapter->devices, device);
+
+	device_get_address(device, &dst);
+	ba2str(&dst, dstaddr);
+
+	device_probe_drivers(device, uuids);
+
+	g_slist_foreach(uuids, (GFunc) g_free, NULL);
+	g_slist_free(uuids);
+}
+
+static void create_stored_device_from_linkkeys(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);
+	if (device) {
+		device_set_temporary(device, FALSE);
+		adapter->devices = g_slist_append(adapter->devices, device);
+	}
+}
+
+static void load_devices(struct btd_adapter *adapter)
+{
+	char filename[PATH_MAX + 1];
+	char srcaddr[18];
+
+	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, "linkkeys");
+	textfile_foreach(filename, create_stored_device_from_linkkeys,
+								adapter);
+}
+
+static void probe_driver(gpointer data, gpointer user_data)
+{
+	struct btd_adapter *adapter = data;
+	struct btd_adapter_driver *driver = user_data;
+	int err;
+
+	if (!adapter->up)
+		return;
+
+	err = driver->probe(adapter);
+	if (err < 0)
+		error("%s: %s (%d)", driver->name, strerror(-err), -err);
+}
+
+static void load_drivers(struct btd_adapter *adapter)
+{
+	GSList *l;
+
+	for (l = adapter_drivers; l; l = l->next) {
+		struct btd_adapter_driver *driver = l->data;
+
+		if (driver->probe == NULL)
+			continue;
+
+		probe_driver(adapter, driver);
+	}
+}
+
+static void load_connections(struct btd_adapter *adapter)
+{
+	struct hci_conn_list_req *cl = NULL;
+	struct hci_conn_info *ci;
+	int i, dd;
+
+	dd = hci_open_dev(adapter->dev_id);
+	if (dd < 0)
+		return;
+
+	cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
+
+	cl->dev_id = adapter->dev_id;
+	cl->conn_num = 10;
+	ci = cl->conn_info;
+
+	if (ioctl(dd, HCIGETCONNLIST, cl) != 0) {
+		g_free(cl);
+		hci_close_dev(dd);
+		return;
+	}
+
+	for (i = 0; i < cl->conn_num; i++, ci++) {
+		struct btd_device *device;
+		char address[18];
+
+		ba2str(&ci->bdaddr, address);
+		device = adapter_get_device(connection, adapter, address);
+		if (device)
+			adapter_add_connection(adapter, device, ci->handle);
+	}
+
+	g_free(cl);
+	hci_close_dev(dd);
+}
+
+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 int adapter_up(struct btd_adapter *adapter)
+{
+	char mode[14], srcaddr[18];
+	uint8_t scan_mode;
+	gboolean powered, dev_down = FALSE;
+	int err;
+
+	ba2str(&adapter->bdaddr, srcaddr);
+
+	adapter->off_requested = FALSE;
+	adapter->up = 1;
+	adapter->discov_timeout = get_discoverable_timeout(srcaddr);
+	adapter->pairable_timeout = get_pairable_timeout(srcaddr);
+	adapter->state = DISCOVER_TYPE_NONE;
+	adapter->mode = MODE_CONNECTABLE;
+	adapter->cache_enable = TRUE;
+	scan_mode = SCAN_PAGE;
+	powered = TRUE;
+
+	/* Set pairable mode */
+	if (read_device_pairable(&adapter->bdaddr, &adapter->pairable) < 0)
+		adapter->pairable = TRUE;
+
+	if (!adapter->initialized && !main_opts.remember_powered) {
+		if (main_opts.mode == MODE_OFF)
+			strcpy(mode, "off");
+		else
+			strcpy(mode, "connectable");
+	} else if (read_device_mode(srcaddr, mode, sizeof(mode)) < 0) {
+		if (!adapter->initialized && main_opts.mode == MODE_OFF)
+			strcpy(mode, "off");
+		else
+			goto proceed;
+	}
+
+	if (g_str_equal(mode, "off")) {
+		powered = FALSE;
+
+		if (!adapter->initialized) {
+			dev_down = TRUE;
+			goto proceed;
+		}
+
+		if (read_on_mode(srcaddr, mode, sizeof(mode)) < 0 ||
+						g_str_equal(mode, "off"))
+			write_device_mode(&adapter->bdaddr, "connectable");
+		else
+			write_device_mode(&adapter->bdaddr, mode);
+
+		return adapter_up(adapter);
+	} else if (!g_str_equal(mode, "connectable") &&
+			adapter->discov_timeout == 0) {
+		/* Set discoverable only if timeout is 0 */
+		adapter->mode = MODE_DISCOVERABLE;
+		scan_mode = SCAN_PAGE | SCAN_INQUIRY;
+	}
+
+proceed:
+	if (scan_mode == SCAN_PAGE)
+		err = adapter_ops->set_connectable(adapter->dev_id);
+	else
+		err = adapter_ops->set_discoverable(adapter->dev_id);
+
+	if (err < 0)
+		return err;
+
+	if (adapter->initialized == FALSE) {
+		load_drivers(adapter);
+		load_devices(adapter);
+
+		/* retrieve the active connections: address the scenario where
+		 * the are active connections before the daemon've started */
+		load_connections(adapter);
+
+		adapter->initialized = TRUE;
+
+		manager_add_adapter(adapter->path);
+
+	}
+
+	if (dev_down) {
+		adapter_ops->stop(adapter->dev_id);
+		adapter->off_requested = TRUE;
+		return 1;
+	} else
+		emit_property_changed(connection, adapter->path,
+					ADAPTER_INTERFACE, "Powered",
+					DBUS_TYPE_BOOLEAN, &powered);
+
+	adapter_disable_svc_cache(adapter);
+	return 0;
+}
+
+int adapter_start(struct btd_adapter *adapter)
+{
+	struct hci_dev *dev = &adapter->dev;
+	struct hci_dev_info di;
+	struct hci_version ver;
+	uint8_t features[8];
+	int dd, err;
+
+	if (hci_devinfo(adapter->dev_id, &di) < 0)
+		return -errno;
+
+	if (hci_test_bit(HCI_RAW, &di.flags)) {
+		dev->ignore = 1;
+		return -1;
+	}
+
+	if (!bacmp(&di.bdaddr, BDADDR_ANY)) {
+		int err;
+
+		debug("Adapter %s without an address", adapter->path);
+
+		err = adapter_read_bdaddr(adapter->dev_id, &di.bdaddr);
+		if (err < 0)
+			return err;
+	}
+
+	bacpy(&adapter->bdaddr, &di.bdaddr);
+	memcpy(dev->features, di.features, 8);
+
+	dd = hci_open_dev(adapter->dev_id);
+	if (dd < 0) {
+		err = -errno;
+		error("Can't open adapter %s: %s (%d)",
+					adapter->path, strerror(errno), errno);
+		return err;
+	}
+
+	if (hci_read_local_version(dd, &ver, HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't read version info for %s: %s (%d)",
+					adapter->path, strerror(errno), errno);
+		hci_close_dev(dd);
+		return err;
+	}
+
+	dev->hci_rev = ver.hci_rev;
+	dev->lmp_ver = ver.lmp_ver;
+	dev->lmp_subver = ver.lmp_subver;
+	dev->manufacturer = ver.manufacturer;
+
+	if (hci_read_local_features(dd, features, HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't read features for %s: %s (%d)",
+					adapter->path, strerror(errno), errno);
+		hci_close_dev(dd);
+		return err;
+	}
+
+	memcpy(dev->features, features, 8);
+
+	if (hci_read_class_of_dev(dd, dev->class, HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't read class of adapter on %s: %s (%d)",
+					adapter->path, strerror(errno), errno);
+		hci_close_dev(dd);
+		return err;
+	}
+
+	adapter_ops->read_name(adapter->dev_id);
+
+	if (!(features[6] & LMP_SIMPLE_PAIR))
+		goto setup;
+
+	if (ioctl(dd, HCIGETAUTHINFO, NULL) < 0 && errno != EINVAL)
+		hci_write_simple_pairing_mode(dd, 0x01, HCI_REQ_TIMEOUT);
+
+	if (hci_read_simple_pairing_mode(dd, &dev->ssp_mode,
+						HCI_REQ_TIMEOUT) < 0) {
+		err = -errno;
+		error("Can't read simple pairing mode on %s: %s (%d)",
+					adapter->path, strerror(errno), errno);
+		/* Fall through since some chips have broken
+		 * read_simple_pairing_mode behavior */
+	}
+
+setup:
+	hci_send_cmd(dd, OGF_LINK_POLICY, OCF_READ_DEFAULT_LINK_POLICY,
+								0, NULL);
+	hci_close_dev(dd);
+
+	adapter_setup(adapter);
+
+	if (!adapter->initialized && adapter->already_up) {
+		debug("Stopping Inquiry at adapter startup");
+		adapter_ops->stop_discovery(adapter->dev_id);
+	}
+
+	err = adapter_up(adapter);
+
+	info("Adapter %s has been enabled", adapter->path);
+
+	return err;
+}
+
+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);
+	}
+
+	if (adapter->state & STD_INQUIRY || adapter->state & PERIODIC_INQUIRY) {
+		/* Cancel inquiry initiated by D-Bus client */
+		if (adapter->disc_sessions)
+			adapter_ops->stop_discovery(adapter->dev_id);
+	}
+}
+
+static void unload_drivers(struct btd_adapter *adapter)
+{
+	GSList *l;
+
+	for (l = adapter_drivers; l; l = l->next) {
+		struct btd_adapter_driver *driver = l->data;
+
+		if (driver->remove)
+			driver->remove(adapter);
+	}
+}
+
+int adapter_stop(struct btd_adapter *adapter)
+{
+	gboolean powered, discoverable, pairable;
+
+	/* 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);
+
+	if (adapter->disc_sessions) {
+		g_slist_foreach(adapter->disc_sessions, (GFunc) session_free,
+				NULL);
+		g_slist_free(adapter->disc_sessions);
+		adapter->disc_sessions = NULL;
+	}
+
+	clear_found_devices_list(adapter);
+
+	if (adapter->oor_devices) {
+		g_slist_free(adapter->oor_devices);
+		adapter->oor_devices = NULL;
+	}
+
+	while (adapter->connections) {
+		struct btd_device *device = adapter->connections->data;
+		adapter_remove_connection(adapter, device, 0);
+	}
+
+	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 = DISCOVER_TYPE_NONE;
+	adapter->cache_enable = TRUE;
+
+	info("Adapter %s has been disabled", adapter->path);
+
+	return 0;
+}
+
+int adapter_update(struct btd_adapter *adapter, uint8_t new_svc)
+{
+	struct hci_dev *dev = &adapter->dev;
+
+	if (dev->ignore)
+		return 0;
+
+	if (adapter->cache_enable) {
+		adapter->svc_cache = new_svc;
+		return 0;
+	}
+
+	set_service_classes(adapter, new_svc);
+
+	update_ext_inquiry_response(adapter);
+
+	return 0;
+}
+
+void adapter_disable_svc_cache(struct btd_adapter *adapter)
+{
+	if (!adapter)
+		return;
+
+	if (!adapter->cache_enable)
+		return;
+
+	/* Disable and flush svc cache. All successive service class updates
+	   will be written to the device */
+	adapter->cache_enable = FALSE;
+
+	set_service_classes(adapter, adapter->svc_cache);
+
+	update_ext_inquiry_response(adapter);
+}
+
+int adapter_get_class(struct btd_adapter *adapter, uint8_t *cls)
+{
+	struct hci_dev *dev = &adapter->dev;
+
+	memcpy(cls, dev->class, 3);
+
+	return 0;
+}
+
+int adapter_set_class(struct btd_adapter *adapter, uint8_t *cls)
+{
+	struct hci_dev *dev = &adapter->dev;
+	uint32_t class;
+
+	if (memcmp(dev->class, cls, 3) == 0)
+		return 0;
+
+	memcpy(dev->class, cls, 3);
+
+	write_local_class(&adapter->bdaddr, cls);
+
+	class = cls[0] | (cls[1] << 8) | (cls[2] << 16);
+
+	emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE,
+				"Class", DBUS_TYPE_UINT32, &class);
+
+	return 0;
+}
+
+int adapter_update_ssp_mode(struct btd_adapter *adapter, uint8_t mode)
+{
+	struct hci_dev *dev = &adapter->dev;
+
+	dev->ssp_mode = mode;
+
+	update_ext_inquiry_response(adapter);
+
+	return 0;
+}
+
+static void adapter_free(gpointer user_data)
+{
+	struct btd_adapter *adapter = user_data;
+
+	agent_destroy(adapter->agent, FALSE);
+	adapter->agent = NULL;
+
+	debug("adapter_free(%p)", adapter);
+
+	g_free(adapter->path);
+	g_free(adapter);
+}
+
+struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter)
+{
+	adapter->ref++;
+
+	debug("btd_adapter_ref(%p): ref=%d", adapter, adapter->ref);
+
+	return adapter;
+}
+
+void btd_adapter_unref(struct btd_adapter *adapter)
+{
+	gchar *path;
+
+	adapter->ref--;
+
+	debug("btd_adapter_unref(%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);
+}
+
+struct btd_adapter *adapter_create(DBusConnection *conn, int id,
+				gboolean devup)
+{
+	char path[MAX_PATH_LENGTH];
+	struct btd_adapter *adapter;
+	const char *base_path = manager_get_base_path();
+
+	if (!connection)
+		connection = conn;
+
+	snprintf(path, sizeof(path), "%s/hci%d", base_path, id);
+
+	adapter = g_try_new0(struct btd_adapter, 1);
+	if (!adapter) {
+		error("adapter_create: failed to alloc memory for %s", path);
+		return NULL;
+	}
+
+	adapter->dev_id = id;
+	if (main_opts.name_resolv)
+		adapter->state |= RESOLVE_NAME;
+	adapter->path = g_strdup(path);
+	adapter->already_up = devup;
+
+	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;
+
+	debug("Removing adapter %s", adapter->path);
+
+	for (l = adapter->devices; l; l = l->next)
+		device_remove(l->data, connection, FALSE);
+	g_slist_free(adapter->devices);
+
+	unload_drivers(adapter);
+
+	/* Return adapter to down state if it was not up on init */
+	if (adapter->up && !adapter->already_up)
+		adapter_ops->stop(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)
+{
+	gboolean discov_active = FALSE;
+	const char *path = adapter->path;
+
+	if (adapter->state == state)
+		return;
+
+	if (state & PERIODIC_INQUIRY || state & STD_INQUIRY)
+		discov_active = TRUE;
+	else if (adapter->disc_sessions && main_opts.discov_interval)
+		adapter->scheduler_id = g_timeout_add_seconds(
+						main_opts.discov_interval,
+						(GSourceFunc) adapter_start_inquiry,
+						adapter);
+
+	/* Send out of range */
+	if (!discov_active)
+		adapter_update_oor_devices(adapter);
+
+	emit_property_changed(connection, path,
+				ADAPTER_INTERFACE, "Discovering",
+				DBUS_TYPE_BOOLEAN, &discov_active);
+
+	adapter->state = state;
+}
+
+int adapter_get_state(struct btd_adapter *adapter)
+{
+	return adapter->state;
+}
+
+gboolean adapter_is_ready(struct btd_adapter *adapter)
+{
+	return adapter->initialized;
+}
+
+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;
+	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 *);
+		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);
+}
+
+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;
+
+	ba2str(&dev->bdaddr, peer_addr);
+	ba2str(&adapter->bdaddr, local_addr);
+
+	device = adapter_find_device(adapter, paddr);
+	if (device)
+		paired = device_is_paired(device);
+
+	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,
+			NULL);
+
+	g_free(alias);
+}
+
+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,
+				name_status_t name_status)
+{
+	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) {
+		if (rssi == dev->rssi)
+			return;
+
+		/* Out of range list update */
+		adapter->oor_devices = g_slist_remove(adapter->oor_devices,
+							dev);
+
+		goto done;
+	}
+
+	dev = g_new0(struct remote_dev_info, 1);
+
+	bacpy(&dev->bdaddr, bdaddr);
+	dev->class = class;
+	if (name)
+		dev->name = g_strdup(name);
+	if (alias)
+		dev->alias = g_strdup(alias);
+	dev->legacy = legacy;
+	dev->name_status = name_status;
+
+	adapter->found_devices = g_slist_prepend(adapter->found_devices, dev);
+
+done:
+	dev->rssi = rssi;
+
+	adapter->found_devices = g_slist_sort(adapter->found_devices,
+						(GCompareFunc) dev_rssi_cmp);
+
+	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_update_oor_devices(struct btd_adapter *adapter)
+{
+	GSList *l;
+
+	for (l = adapter->oor_devices; l; l = l->next) {
+		char address[18];
+		const char *paddr = address;
+		struct remote_dev_info *dev = l->data;
+
+		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);
+		dev_info_free(dev);
+	}
+
+	g_slist_free(adapter->oor_devices);
+	adapter->oor_devices = NULL;
+
+	adapter->oor_devices = g_slist_copy(adapter->found_devices);
+}
+
+void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode)
+{
+	const gchar *path = adapter_get_path(adapter);
+	gboolean discoverable, pairable;
+	uint8_t real_class[3];
+
+	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;
+		break;
+	case SCAN_PAGE:
+		adapter->mode = MODE_CONNECTABLE;
+		discoverable = FALSE;
+		pairable = adapter->pairable;
+		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);
+
+	memcpy(real_class, adapter->dev.class, 3);
+	if (adapter->svc_cache)
+		real_class[2] = adapter->svc_cache;
+
+	if (discoverable && adapter->pairable && adapter->discov_timeout > 0 &&
+						adapter->discov_timeout <= 60)
+		adapter_ops->set_limited_discoverable(adapter->dev_id,
+							real_class, TRUE);
+	else if (!discoverable)
+		adapter_ops->set_limited_discoverable(adapter->dev_id,
+							real_class, FALSE);
+
+	emit_property_changed(connection, path,
+				ADAPTER_INTERFACE, "Discoverable",
+				DBUS_TYPE_BOOLEAN, &discoverable);
+
+	adapter->scan_mode = scan_mode;
+}
+
+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, uint16_t handle)
+{
+	if (g_slist_find(adapter->connections, device)) {
+		error("Unable to add connection %d", handle);
+		return;
+	}
+
+	device_add_connection(device, connection, handle);
+
+	adapter->connections = g_slist_append(adapter->connections, device);
+}
+
+void adapter_remove_connection(struct btd_adapter *adapter,
+				struct btd_device *device, uint16_t handle)
+{
+	bdaddr_t bdaddr;
+
+	if (!g_slist_find(adapter->connections, device)) {
+		error("No matching connection for handle %u", handle);
+		return;
+	}
+
+	device_remove_connection(device, connection, handle);
+
+	adapter->connections = g_slist_remove(adapter->connections, device);
+
+	/* clean pending HCI cmds */
+	device_get_address(device, &bdaddr);
+	hci_req_queue_remove(adapter->dev_id, &bdaddr);
+
+	if (device_is_authenticating(device))
+		device_cancel_authentication(device, TRUE);
+
+	if (device_is_temporary(device)) {
+		const char *path = device_get_path(device);
+
+		debug("Removing temporary device %s", path);
+		adapter_remove_device(connection, adapter, device);
+	}
+}
+
+gboolean adapter_has_discov_sessions(struct btd_adapter *adapter)
+{
+	if (!adapter || !adapter->disc_sessions)
+		return FALSE;
+
+	return TRUE;
+}
+
+int btd_register_adapter_driver(struct btd_adapter_driver *driver)
+{
+	GSList *adapters;
+
+	adapter_drivers = g_slist_append(adapter_drivers, driver);
+
+	if (driver->probe == NULL)
+		return 0;
+
+	adapters = manager_get_adapters();
+	g_slist_foreach(adapters, probe_driver, driver);
+
+	return 0;
+}
+
+void btd_unregister_adapter_driver(struct btd_adapter_driver *driver)
+{
+	adapter_drivers = g_slist_remove(adapter_drivers, 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 int btd_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];
+	gboolean trusted;
+	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;
+
+	trusted = read_trust(&adapter->bdaddr, address, GLOBAL_TRUST);
+
+	if (trusted) {
+		cb(NULL, user_data);
+		return 0;
+	}
+
+	device = adapter_find_device(adapter, address);
+	if (!device)
+		return -EPERM;
+
+	agent = device_get_agent(device);
+
+	if (!agent)
+		agent = adapter->agent;
+
+	if (!agent)
+		return -EPERM;
+
+	auth = g_try_new0(struct service_auth, 1);
+	if (!auth)
+		return -ENOMEM;
+
+	auth->cb = cb;
+	auth->user_data = user_data;
+	auth->device = device;
+
+	dev_path = device_get_path(device);
+
+	err = agent_authorize(agent, dev_path, uuid, agent_auth_cb, auth, g_free);
+
+	if (err == 0)
+		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 *adapters;
+
+	if (src == NULL || dst == NULL)
+		return -EINVAL;
+
+	if (bacmp(src, BDADDR_ANY) != 0)
+		goto proceed;
+
+	/* Handle request authorization for ANY adapter */
+	adapters = manager_get_adapters();
+
+	for (; adapters; adapters = adapters->next) {
+		int err;
+		adapter = adapters->data;
+
+		err = btd_adapter_authorize(adapter, dst, uuid, cb, user_data);
+		if (err == 0)
+			return 0;
+	}
+
+	return -EPERM;
+
+proceed:
+	adapter = manager_find_adapter(src);
+	if (!adapter)
+		return -EPERM;
+
+	return btd_adapter_authorize(adapter, dst, uuid, cb, user_data);
+}
+
+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;
+
+	/*
+	 * 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)
+		agent = adapter->agent;
+
+	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());
+	adapter_any_refcount++;
+
+	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];
+
+	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;
+
+	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 *btd_adapter_ops)
+{
+	/* Already registered */
+	if (adapter_ops)
+		return -EALREADY;
+
+	if (btd_adapter_ops->setup == NULL)
+		return -EINVAL;
+
+	adapter_ops = btd_adapter_ops;
+
+	return 0;
+}
+
+void btd_adapter_cleanup_ops(struct btd_adapter_ops *btd_adapter_ops)
+{
+	adapter_ops->cleanup();
+}
+
+int adapter_ops_setup(void)
+{
+	if (!adapter_ops)
+		return -EINVAL;
+
+	return adapter_ops->setup();
+}
diff --git a/src/adapter.h b/src/adapter.h
new file mode 100644
index 0000000..83f987e
--- /dev/null
+++ b/src/adapter.h
@@ -0,0 +1,187 @@
+/*
+ *
+ *  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
+ *
+ */
+
+#include <bluetooth/bluetooth.h>
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#define ADAPTER_INTERFACE	"org.bluez.Adapter"
+
+/* Discover types */
+#define DISCOVER_TYPE_NONE	0x00
+#define STD_INQUIRY		0x01
+#define PERIODIC_INQUIRY	0x02
+
+/* Actions executed after inquiry complete */
+#define RESOLVE_NAME		0x10
+
+#define MAX_NAME_LENGTH		248
+
+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_SENT          /* D-Bus signal RemoteNameUpdated sent */
+} name_status_t;
+
+struct btd_adapter;
+
+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;
+};
+
+struct hci_dev {
+	int ignore;
+
+	uint8_t  features[8];
+	uint8_t  lmp_ver;
+	uint16_t lmp_subver;
+	uint16_t hci_rev;
+	uint16_t manufacturer;
+
+	uint8_t  ssp_mode;
+	uint8_t  name[MAX_NAME_LENGTH];
+	uint8_t  class[3];
+};
+
+int adapter_start(struct btd_adapter *adapter);
+
+int adapter_stop(struct btd_adapter *adapter);
+
+int adapter_update(struct btd_adapter *adapter, uint8_t cls);
+
+int adapter_get_class(struct btd_adapter *adapter, uint8_t *cls);
+
+int adapter_set_class(struct btd_adapter *adapter, uint8_t *cls);
+
+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);
+
+struct btd_device *adapter_find_connection(struct btd_adapter *adapter, uint16_t handle);
+
+void adapter_disable_svc_cache(struct btd_adapter *adapter);
+
+void adapter_remove_device(DBusConnection *conn, struct btd_adapter *adapter,
+				struct btd_device *device);
+struct btd_device *adapter_create_device(DBusConnection *conn,
+				struct btd_adapter *adapter, const char *address);
+
+int pending_remote_name_cancel(struct btd_adapter *adapter);
+
+int adapter_resolve_names(struct btd_adapter *adapter);
+
+void clear_found_devices_list(struct btd_adapter *adapter);
+
+struct btd_adapter *adapter_create(DBusConnection *conn, int id,
+				gboolean devup);
+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);
+gboolean adapter_is_ready(struct btd_adapter *adapter);
+struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter,
+						struct remote_dev_info *match);
+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,
+				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_update_oor_devices(struct btd_adapter *adapter);
+void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode);
+void adapter_setname_complete(bdaddr_t *local, uint8_t status);
+void adapter_update_local_name(bdaddr_t *bdaddr, uint8_t status, void *ptr);
+
+struct agent *adapter_get_agent(struct btd_adapter *adapter);
+void adapter_add_connection(struct btd_adapter *adapter,
+				struct btd_device *device, uint16_t handle);
+void adapter_remove_connection(struct btd_adapter *adapter,
+				struct btd_device *device, uint16_t handle);
+gboolean adapter_has_discov_sessions(struct btd_adapter *adapter);
+
+struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter);
+void btd_adapter_unref(struct btd_adapter *adapter);
+int set_major_and_minor_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_offline(struct btd_adapter *adapter);
+
+struct btd_adapter_ops {
+	int (*setup) (void);
+	void (*cleanup) (void);
+	int (*start) (int index);
+	int (*stop) (int index);
+	int (*set_powered) (int index, gboolean powered);
+	int (*set_connectable) (int index);
+	int (*set_discoverable) (int index);
+	int (*set_limited_discoverable) (int index, const uint8_t *cls,
+						gboolean limited);
+	int (*start_discovery) (int index, gboolean periodic);
+	int (*stop_discovery) (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 (*read_name) (int index);
+};
+
+int btd_register_adapter_ops(struct btd_adapter_ops *btd_adapter_ops);
+void btd_adapter_cleanup_ops(struct btd_adapter_ops *btd_adapter_ops);
+int adapter_ops_setup(void);
diff --git a/src/agent.c b/src/agent.c
new file mode 100644
index 0000000..4f9ba82
--- /dev/null
+++ b/src/agent.c
@@ -0,0 +1,860 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2008  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 <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.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_PAIRING_CONSENT,
+} 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;
+
+	debug("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;
+
+	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;
+
+	debug("Agent exited without calling Unregister");
+
+	agent_destroy(agent, TRUE);
+}
+
+static 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;
+}
+
+int agent_destroy(struct agent *agent, gboolean exited)
+{
+	if (!agent)
+		return 0;
+
+	agent->exited = exited;
+	agent_free(agent);
+	return 0;
+}
+
+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);
+
+		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;
+
+	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;
+
+	debug("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);
+
+		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 passkey length from handler");
+		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;
+	}
+
+	set_pin_length(&sba, len);
+
+	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;
+
+	debug("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);
+		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;
+
+	debug("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;
+
+	debug("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 pairing_consent_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", "RequestPairingConsent");
+	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, simple_agent_reply, req, NULL);
+
+	return 0;
+}
+
+int agent_request_pairing_consent(struct agent *agent, struct btd_device *device,
+				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;
+
+	debug("Calling Agent.RequestPairingConsent: name=%s, path=%s",
+			agent->name, agent->path);
+
+	req = agent_request_new(agent, AGENT_REQUEST_PAIRING_CONSENT, cb,
+				user_data, destroy);
+
+	err = pairing_consent_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 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);
+
+	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
index 0000000..314baf9
--- /dev/null
+++ b/src/agent.h
@@ -0,0 +1,77 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2008  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 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);
+
+int agent_destroy(struct agent *agent, gboolean exited);
+
+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/bluetooth.conf b/src/bluetooth.conf
new file mode 100644
index 0000000..c047623
--- /dev/null
+++ b/src/bluetooth.conf
@@ -0,0 +1,24 @@
+<!-- This configuration file specifies the required security policies
+     for Bluetooth core daemon to work. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+  <!-- ../system.conf have denied everything, so we just punch some holes -->
+
+  <policy user="root">
+    <allow own="org.bluez"/>
+    <allow send_destination="org.bluez"/>
+    <allow send_interface="org.bluez.Agent"/>
+  </policy>
+
+  <policy at_console="true">
+    <allow send_destination="org.bluez"/>
+  </policy>
+
+  <policy context="default">
+    <deny send_destination="org.bluez"/>
+  </policy>
+
+</busconfig>
diff --git a/src/bluetoothd.8.in b/src/bluetoothd.8.in
new file mode 100644
index 0000000..a7ae77b
--- /dev/null
+++ b/src/bluetoothd.8.in
@@ -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
index 0000000..059c91c
--- /dev/null
+++ b/src/dbus-common.c
@@ -0,0 +1,316 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  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 <bluetooth/hci.h>
+#include <bluetooth/l2cap.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+
+#include "manager.h"
+#include "adapter.h"
+#include "dbus-hci.h"
+#include "dbus-common.h"
+
+#define BLUEZ_NAME "org.bluez"
+
+#define RECONNECT_RETRY_TIMEOUT	5000
+
+static gboolean system_bus_reconnect(void *data)
+{
+	DBusConnection *conn = get_dbus_connection();
+	struct hci_dev_list_req *dl = NULL;
+	struct hci_dev_req *dr;
+	int sk, i;
+	gboolean ret_val = TRUE;
+
+	if (conn) {
+		if (dbus_connection_get_is_connected(conn))
+			return FALSE;
+	}
+
+	if (hcid_dbus_init() < 0)
+		return TRUE;
+
+	/* Create and bind HCI socket */
+	sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+	if (sk < 0) {
+		error("Can't open HCI socket: %s (%d)",
+				strerror(errno), errno);
+		return TRUE;
+	}
+
+	dl = g_malloc0(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl));
+
+	dl->dev_num = HCI_MAX_DEV;
+	dr = dl->dev_req;
+
+	if (ioctl(sk, HCIGETDEVLIST, (void *) dl) < 0) {
+		info("Can't get device list: %s (%d)",
+			strerror(errno), errno);
+		goto failed;
+	}
+
+	/* reset the default device */
+	manager_set_default_adapter(-1);
+
+	/* FIXME: it shouldn't be needed to register adapters again */
+	for (i = 0; i < dl->dev_num; i++, dr++)
+		manager_register_adapter(dr->dev_id, TRUE);
+
+	ret_val = FALSE;
+
+failed:
+	if (sk >= 0)
+		close(sk);
+
+	g_free(dl);
+
+	return ret_val;
+}
+
+static void disconnect_callback(DBusConnection *conn, void *user_data)
+{
+	set_dbus_connection(NULL);
+
+	g_timeout_add(RECONNECT_RETRY_TIMEOUT,
+				system_bus_reconnect, NULL);
+}
+
+void hcid_dbus_unregister(void)
+{
+	DBusConnection *conn = get_dbus_connection();
+	char **children;
+	int i;
+	uint16_t dev_id;
+
+	if (!conn || !dbus_connection_get_is_connected(conn))
+		return;
+
+	/* Unregister all paths in Adapter path hierarchy */
+	if (!dbus_connection_list_registered(conn, "/", &children))
+		return;
+
+	for (i = 0; children[i]; i++) {
+		char path[MAX_PATH_LENGTH];
+		struct btd_adapter *adapter;
+
+		if (children[i][0] != 'h')
+			continue;
+
+		snprintf(path, sizeof(path), "/%s", children[i]);
+
+		adapter = manager_find_adapter_by_path(path);
+		if (!adapter)
+			continue;
+
+		dev_id = adapter_get_dev_id(adapter);
+		manager_unregister_adapter(dev_id);
+	}
+
+	dbus_free_string_array(children);
+}
+
+void hcid_dbus_exit(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);
+}
+
+int hcid_dbus_init(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 (g_dbus_set_disconnect_function(conn, disconnect_callback,
+							NULL, NULL) == FALSE) {
+		dbus_connection_unref(conn);
+		return -EIO;
+	}
+
+	if (!manager_init(conn, "/"))
+		return -EIO;
+
+	set_dbus_connection(conn);
+
+	return 0;
+}
+
+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)
+{
+	DBusMessageIter variant, array;
+	char type_sig[2] = { type, '\0' };
+	char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' };
+	const char ***str_array = val;
+	int i;
+
+	dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
+						array_sig, &variant);
+
+	dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
+						type_sig, &array);
+
+	for (i = 0; (*str_array)[i]; 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);
+
+	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)
+{
+	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);
+
+	return g_dbus_send_message(conn, signal);
+}
diff --git a/src/dbus-common.h b/src/dbus-common.h
new file mode 100644
index 0000000..67c7178
--- /dev/null
+++ b/src/dbus-common.h
@@ -0,0 +1,46 @@
+/* *
+ *  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
+ *
+ */
+
+#define MAX_PATH_LENGTH 64
+
+void hcid_dbus_exit(void);
+int hcid_dbus_init(void);
+void hcid_dbus_unregister(void);
+
+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);
diff --git a/src/dbus-hci.c b/src/dbus-hci.c
new file mode 100644
index 0000000..e9a7f96
--- /dev/null
+++ b/src/dbus-hci.c
@@ -0,0 +1,1006 @@
+/*
+ *
+ *  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
+
+#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/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+
+#include "hcid.h"
+#include "manager.h"
+#include "adapter.h"
+#include "device.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "dbus-common.h"
+#include "agent.h"
+#include "storage.h"
+#include "dbus-hci.h"
+
+static DBusConnection *connection = NULL;
+
+static gboolean get_adapter_and_device(bdaddr_t *src, bdaddr_t *dst,
+					struct btd_adapter **adapter,
+					struct btd_device **device,
+					gboolean create)
+{
+	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(connection, *adapter, peer_addr);
+	else
+		*device = adapter_find_device(*adapter, peer_addr);
+
+	if (create && !*device) {
+		error("Unable to get device object!");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+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 */
+		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;
+}
+
+/*****************************************************************
+ *
+ *  Section reserved to HCI commands confirmation handling and low
+ *  level events(eg: device attached/dettached.
+ *
+ *****************************************************************/
+
+static void pincode_cb(struct agent *agent, DBusError *err, const char *pincode,
+			struct btd_device *device)
+{
+	struct btd_adapter *adapter = device_get_adapter(device);
+	pin_code_reply_cp pr;
+	bdaddr_t sba, dba;
+	size_t len;
+	int dev;
+	uint16_t dev_id = adapter_get_dev_id(adapter);
+
+	dev = hci_open_dev(dev_id);
+	if (dev < 0) {
+		error("hci_open_dev(%d): %s (%d)", dev_id,
+				strerror(errno), errno);
+		return;
+	}
+
+	adapter_get_address(adapter, &sba);
+	device_get_address(device, &dba);
+
+	if (err) {
+		hci_send_cmd(dev, OGF_LINK_CTL,
+				OCF_PIN_CODE_NEG_REPLY, 6, &dba);
+		goto done;
+	}
+
+	len = strlen(pincode);
+
+	set_pin_length(&sba, len);
+
+	memset(&pr, 0, sizeof(pr));
+	bacpy(&pr.bdaddr, &dba);
+	memcpy(pr.pin_code, pincode, len);
+	pr.pin_len = len;
+	hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY,
+						PIN_CODE_REPLY_CP_SIZE, &pr);
+
+done:
+	hci_close_dev(dev);
+}
+
+int hcid_dbus_request_pin(int dev, bdaddr_t *sba, struct hci_conn_info *ci)
+{
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+
+	if (!get_adapter_and_device(sba, &ci->bdaddr, &adapter, &device, TRUE))
+		return -ENODEV;
+
+	/* Check if the adapter is not pairable and if there isn't a bonding in
+	 * progress */
+	if (!adapter_is_pairable(adapter) && !device_is_bonding(device, NULL))
+		return -EPERM;
+
+	return device_request_authentication(device, AUTH_TYPE_PINCODE, 0,
+								pincode_cb);
+}
+
+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);
+	user_confirm_reply_cp cp;
+	int dd;
+	uint16_t dev_id = adapter_get_dev_id(adapter);
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		error("Unable to open hci%d", dev_id);
+		return;
+	}
+
+	memset(&cp, 0, sizeof(cp));
+	device_get_address(device, &cp.bdaddr);
+
+	if (err)
+		hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY,
+					USER_CONFIRM_REPLY_CP_SIZE, &cp);
+	else
+		hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_REPLY,
+					USER_CONFIRM_REPLY_CP_SIZE, &cp);
+
+	hci_close_dev(dd);
+}
+
+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);
+	user_passkey_reply_cp cp;
+	bdaddr_t dba;
+	int dd;
+	uint16_t dev_id = adapter_get_dev_id(adapter);
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		error("Unable to open hci%d", dev_id);
+		return;
+	}
+
+	device_get_address(device, &dba);
+
+	memset(&cp, 0, sizeof(cp));
+	bacpy(&cp.bdaddr, &dba);
+	cp.passkey = passkey;
+
+	if (err)
+		hci_send_cmd(dd, OGF_LINK_CTL,
+				OCF_USER_PASSKEY_NEG_REPLY, 6, &dba);
+	else
+		hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_PASSKEY_REPLY,
+					USER_PASSKEY_REPLY_CP_SIZE, &cp);
+
+	hci_close_dev(dd);
+}
+
+static void pairing_consent_cb(struct agent *agent, DBusError *err,
+					void *user_data)
+{
+	struct btd_device *device = user_data;
+	struct btd_adapter *adapter = device_get_adapter(device);
+	user_confirm_reply_cp cp;
+	int dd;
+	uint16_t dev_id = adapter_get_dev_id(adapter);
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		error("Unable to open hci%d", dev_id);
+		return;
+	}
+
+	memset(&cp, 0, sizeof(cp));
+	device_get_address(device, &cp.bdaddr);
+
+	if (err)
+		hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY,
+					USER_CONFIRM_REPLY_CP_SIZE, &cp);
+	else
+		hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_REPLY,
+					USER_CONFIRM_REPLY_CP_SIZE, &cp);
+
+	hci_close_dev(dd);
+}
+
+static int get_auth_requirements(bdaddr_t *local, bdaddr_t *remote,
+							uint8_t *auth)
+{
+	struct hci_auth_info_req req;
+	char addr[18];
+	int err, dd, dev_id;
+
+	ba2str(local, addr);
+
+	dev_id = hci_devid(addr);
+	if (dev_id < 0)
+		return dev_id;
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0)
+		return dd;
+
+	memset(&req, 0, sizeof(req));
+	bacpy(&req.bdaddr, remote);
+
+	err = ioctl(dd, HCIGETAUTHINFO, (unsigned long) &req);
+	if (err < 0) {
+		debug("HCIGETAUTHINFO failed: %s (%d)",
+					strerror(errno), errno);
+		hci_close_dev(dd);
+		return err;
+	}
+
+	hci_close_dev(dd);
+
+	if (auth)
+		*auth = req.type;
+
+	return 0;
+}
+
+int hcid_dbus_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey)
+{
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+	uint8_t remcap, remauth, type;
+	uint16_t dev_id;
+
+	if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE))
+		return -ENODEV;
+
+	dev_id = adapter_get_dev_id(adapter);
+
+	if (get_auth_requirements(sba, dba, &type) < 0) {
+		int dd;
+
+		dd = hci_open_dev(dev_id);
+		if (dd < 0) {
+			error("Unable to open hci%d", dev_id);
+			return -1;
+		}
+
+		hci_send_cmd(dd, OGF_LINK_CTL,
+					OCF_USER_CONFIRM_NEG_REPLY, 6, dba);
+
+		hci_close_dev(dd);
+
+		return 0;
+	}
+
+	debug("confirm authentication requirement is 0x%02x", type);
+
+	remcap = device_get_cap(device);
+	remauth = device_get_auth(device);
+
+	debug("remote IO capabilities are 0x%02x", remcap);
+	debug("remote authentication requirement is 0x%02x", remauth);
+
+	/* If local IO capabilities are DisplayYesNo and remote IO
+	 * capabiltiies are DisplayOnly or NoInputNoOutput;
+	 * call PairingConsent callback for incoming requests. */
+	struct agent *agent = NULL;
+	agent = device_get_agent(device);
+	if (!agent) {
+		agent = adapter_get_agent(adapter);
+		if ((agent_get_io_capability(agent) & 0x01) &&
+		            (remcap == 0x00 || remcap == 0x03))
+			return device_request_authentication(device,
+					AUTH_TYPE_PAIRING_CONSENT, 0,
+					pairing_consent_cb);
+	}
+
+	/* If no side requires MITM protection; auto-accept */
+	if (!(remauth & 0x01) &&
+			(type == 0xff || !(type & 0x01) || remcap == 0x03)) {
+		int dd;
+
+		/* Wait 5 milliseconds before doing auto-accept */
+		usleep(5000);
+
+		dd = hci_open_dev(dev_id);
+		if (dd < 0) {
+			error("Unable to open hci%d", dev_id);
+			return -1;
+		}
+
+		hci_send_cmd(dd, OGF_LINK_CTL,
+					OCF_USER_CONFIRM_REPLY, 6, dba);
+
+		hci_close_dev(dd);
+
+		debug("auto accept of confirmation");
+
+		return device_request_authentication(device,
+						AUTH_TYPE_AUTO, 0, NULL);
+	}
+
+	return device_request_authentication(device, AUTH_TYPE_CONFIRM,
+							passkey, confirm_cb);
+}
+
+int hcid_dbus_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 hcid_dbus_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 hcid_dbus_bonding_process_complete(bdaddr_t *local, bdaddr_t *peer,
+								uint8_t status)
+{
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+
+	debug("hcid_dbus_bonding_process_complete: status=%02x", status);
+
+	if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE))
+		return;
+
+	if (!device_is_authenticating(device)) {
+		/* This means that there was no pending PIN or SSP token
+		 * request from the controller, i.e. this is not a new
+		 * pairing */
+		debug("hcid_dbus_bonding_process_complete: no pending auth request");
+		return;
+	}
+
+	/* If this is a new pairing send the appropriate reply and signal for
+	 * it and proceed with service discovery */
+	device_bonding_complete(device, status);
+}
+
+void hcid_dbus_simple_pairing_complete(bdaddr_t *local, bdaddr_t *peer,
+					uint8_t status)
+{
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+
+	debug("hcid_dbus_simple_pairing_complete: status=%02x", status);
+
+	if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE))
+		return;
+
+	device_simple_pairing_complete(device, status);
+}
+
+static char *extract_eir_name(uint8_t *data, uint8_t *type)
+{
+	if (!data || !type)
+		return NULL;
+
+	if (data[0] == 0)
+		return NULL;
+
+	*type = data[1];
+
+	switch (*type) {
+	case 0x08:
+	case 0x09:
+		return strndup((char *) (data + 2), data[0] - 1);
+	}
+
+	return NULL;
+}
+
+void hcid_dbus_inquiry_result(bdaddr_t *local, bdaddr_t *peer, uint32_t class,
+				int8_t rssi, uint8_t *data)
+{
+	char filename[PATH_MAX + 1];
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+	char local_addr[18], peer_addr[18], *alias, *name, *tmp_name;
+	struct remote_dev_info *dev, match;
+	uint8_t name_type = 0x00;
+	name_status_t name_status;
+	int state;
+	dbus_bool_t legacy;
+
+	ba2str(local, local_addr);
+	ba2str(peer, peer_addr);
+
+	if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE)) {
+		error("No matching adapter found");
+		return;
+	}
+
+	write_remote_class(local, peer, class);
+
+	if (data)
+		write_remote_eir(local, peer, data);
+
+	/*
+	 * workaround to identify situation when the daemon started and
+	 * a standard inquiry or periodic inquiry was already running
+	 */
+	if (!(adapter_get_state(adapter) & STD_INQUIRY) &&
+			!(adapter_get_state(adapter) & PERIODIC_INQUIRY)) {
+		state = adapter_get_state(adapter);
+		state |= PERIODIC_INQUIRY;
+		adapter_set_state(adapter, state);
+	}
+
+	legacy = (data == NULL);
+
+	memset(&match, 0, sizeof(struct remote_dev_info));
+	bacpy(&match.bdaddr, peer);
+	match.name_status = NAME_SENT;
+	/* if found: don't send the name again */
+	dev = adapter_search_found_devices(adapter, &match);
+	if (dev) {
+		adapter_update_found_devices(adapter, peer, rssi, class,
+						NULL, NULL, legacy,
+						NAME_NOT_REQUIRED);
+		return;
+	}
+
+	/* the inquiry result can be triggered by NON D-Bus client */
+	if (adapter_get_state(adapter) & RESOLVE_NAME)
+		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);
+
+	tmp_name = extract_eir_name(data, &name_type);
+	if (tmp_name) {
+		if (name_type == 0x09) {
+			write_device_name(local, peer, tmp_name);
+			name_status = NAME_NOT_REQUIRED;
+
+			if (name)
+				g_free(name);
+
+			name = tmp_name;
+		} else {
+			if (name)
+				free(tmp_name);
+			else
+				name = tmp_name;
+		}
+	}
+
+
+	if (name && name_type != 0x08)
+		name_status = NAME_SENT;
+
+	/* add in the list to track name sent/pending */
+	adapter_update_found_devices(adapter, peer, rssi, class, name, alias,
+					legacy, name_status);
+
+	g_free(name);
+	g_free(alias);
+}
+
+void hcid_dbus_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;
+
+	read_remote_class(local, peer, &old_class);
+
+	if (old_class == class)
+		return;
+
+	if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE))
+		return;
+
+	if (!device)
+		return;
+
+	dev_path = device_get_path(device);
+
+	emit_property_changed(connection, dev_path, DEVICE_INTERFACE, "Class",
+				DBUS_TYPE_UINT32, &class);
+}
+
+void hcid_dbus_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 (!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 &= ~PERIODIC_INQUIRY;
+	state &= ~STD_INQUIRY;
+	adapter_set_state(adapter, state);
+}
+
+int hcid_dbus_link_key_notify(bdaddr_t *local, bdaddr_t *peer,
+				uint8_t *key, uint8_t key_type,
+				int pin_length, uint8_t old_key_type)
+{
+	struct btd_device *device;
+	struct btd_adapter *adapter;
+	uint8_t local_auth = 0xff, remote_auth, new_key_type;
+	gboolean bonding;
+
+	if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE))
+		return -ENODEV;
+
+	if (key_type == 0x06 && old_key_type != 0xff)
+		new_key_type = old_key_type;
+	else
+		new_key_type = key_type;
+
+	get_auth_requirements(local, peer, &local_auth);
+	remote_auth = device_get_auth(device);
+	bonding = device_is_bonding(device, NULL);
+
+	debug("local auth 0x%02x and remote auth 0x%02x",
+					local_auth, remote_auth);
+
+	/* Only store the link key 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 (key_type < 0x03 || (key_type == 0x06 && old_key_type != 0xff) ||
+				(local_auth > 0x01 && remote_auth > 0x01) ||
+				(local_auth == 0x02 || local_auth == 0x03) ||
+				(remote_auth == 0x02 || remote_auth == 0x03)) {
+		int err;
+
+		debug("storing link key of type 0x%02x", key_type);
+
+		err = write_link_key(local, peer, key, new_key_type,
+								pin_length);
+		if (err < 0) {
+			error("write_link_key: %s (%d)", strerror(-err), -err);
+			return err;
+		}
+	}
+
+	/* If this is not the first link key set a flag so a subsequent auth
+	 * complete event doesn't trigger SDP */
+	if (old_key_type != 0xff)
+		device_set_renewed_key(device, TRUE);
+
+	if (!device_is_connected(device))
+		device_set_secmode3_conn(device, TRUE);
+	else if (!bonding && old_key_type == 0xff)
+		hcid_dbus_bonding_process_complete(local, peer, 0);
+
+	return 0;
+}
+
+void hcid_dbus_conn_complete(bdaddr_t *local, uint8_t status, uint16_t handle,
+				bdaddr_t *peer)
+{
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+
+	if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE))
+		return;
+
+	if (status) {
+		device_set_secmode3_conn(device, FALSE);
+		if (device_is_bonding(device, NULL))
+			device_bonding_complete(device, status);
+		if (device_is_temporary(device))
+			adapter_remove_device(connection, adapter, device);
+		return;
+	}
+
+	/* add in the device connetions list */
+	adapter_add_connection(adapter, device, handle);
+}
+
+void hcid_dbus_disconn_complete(bdaddr_t *local, uint8_t status,
+				uint16_t handle, uint8_t reason)
+{
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+
+	if (status) {
+		error("Disconnection failed: 0x%02x", status);
+		return;
+	}
+
+	adapter = manager_find_adapter(local);
+	if (!adapter) {
+		error("No matching adapter found");
+		return;
+	}
+
+	device = adapter_find_connection(adapter, handle);
+	if (!device) {
+		error("No matching connection found for handle %u", handle);
+		return;
+	}
+
+	adapter_remove_connection(adapter, device, handle);
+}
+
+/* Section reserved to device HCI callbacks */
+
+void hcid_dbus_setscan_enable_complete(bdaddr_t *local)
+{
+	struct btd_adapter *adapter;
+	read_scan_enable_rp rp;
+	struct hci_request rq;
+	int dd = -1;
+	uint16_t dev_id;
+
+	adapter = manager_find_adapter(local);
+	if (!adapter) {
+		error("No matching adapter found");
+		return;
+	}
+
+	if (adapter_powering_down(adapter))
+		return;
+
+	dev_id = adapter_get_dev_id(adapter);
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		error("HCI device open failed: hci%d", dev_id);
+		return;
+	}
+
+	memset(&rq, 0, sizeof(rq));
+	rq.ogf    = OGF_HOST_CTL;
+	rq.ocf    = OCF_READ_SCAN_ENABLE;
+	rq.rparam = &rp;
+	rq.rlen   = READ_SCAN_ENABLE_RP_SIZE;
+	rq.event  = EVT_CMD_COMPLETE;
+
+	if (hci_send_req(dd, &rq, HCI_REQ_TIMEOUT) < 0) {
+		error("Sending read scan enable command failed: %s (%d)",
+				strerror(errno), errno);
+		goto failed;
+	}
+
+	if (rp.status) {
+		error("Getting scan enable failed with status 0x%02x",
+				rp.status);
+		goto failed;
+	}
+
+	adapter_mode_changed(adapter, rp.enable);
+
+failed:
+	if (dd >= 0)
+		hci_close_dev(dd);
+}
+
+void hcid_dbus_write_class_complete(bdaddr_t *local)
+{
+	struct btd_adapter *adapter;
+	int dd;
+	uint8_t cls[3];
+	uint16_t dev_id;
+
+	adapter = manager_find_adapter(local);
+	if (!adapter) {
+		error("No matching adapter found");
+		return;
+	}
+
+	dev_id = adapter_get_dev_id(adapter);
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		error("HCI device open failed: hci%d", dev_id);
+		return;
+	}
+
+	if (hci_read_class_of_dev(dd, cls, HCI_REQ_TIMEOUT) < 0) {
+		error("Can't read class of device on hci%d: %s (%d)",
+			dev_id, strerror(errno), errno);
+		hci_close_dev(dd);
+		return;
+	}
+
+	hci_close_dev(dd);
+
+	adapter_set_class(adapter, cls);
+}
+
+void hcid_dbus_write_simple_pairing_mode_complete(bdaddr_t *local)
+{
+	struct btd_adapter *adapter;
+	int dd;
+	uint8_t mode;
+	uint16_t dev_id;
+	const gchar *path;
+
+	adapter = manager_find_adapter(local);
+	if (!adapter) {
+		error("No matching adapter found");
+		return;
+	}
+
+	dev_id = adapter_get_dev_id(adapter);
+	path = adapter_get_path(adapter);
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		error("HCI adapter open failed: %s", path);
+		return;
+	}
+
+	if (hci_read_simple_pairing_mode(dd, &mode,
+						HCI_REQ_TIMEOUT) < 0) {
+		error("Can't read simple pairing mode for %s: %s(%d)",
+					path, strerror(errno), errno);
+		hci_close_dev(dd);
+		return;
+	}
+
+	hci_close_dev(dd);
+
+	adapter_update_ssp_mode(adapter, mode);
+}
+
+int hcid_dbus_get_io_cap(bdaddr_t *local, bdaddr_t *remote,
+						uint8_t *cap, uint8_t *auth)
+{
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+	struct agent *agent = NULL;
+
+	if (!get_adapter_and_device(local, remote, &adapter, &device, TRUE))
+		return -ENODEV;
+
+	if (get_auth_requirements(local, remote, auth) < 0)
+		return -1;
+
+	debug("initial authentication requirement is 0x%02x", *auth);
+
+	if (*auth == 0xff)
+		*auth = device_get_auth(device);
+
+	/* Check if the adapter is not pairable and if there isn't a bonding
+	 * in progress */
+	if (!adapter_is_pairable(adapter) &&
+				!device_is_bonding(device, NULL)) {
+		if (*auth < 0x02 && device_get_auth(device) < 0x02) {
+			debug("Allowing no bonding in non-bondable mode");
+			/* No input, no output */
+			*cap = 0x03;
+			goto done;
+		}
+		return -EPERM;
+	}
+
+	/* For CreatePairedDevice use dedicated bonding */
+	agent = device_get_agent(device);
+	if (!agent)
+		agent = adapter_get_agent(adapter);
+
+	if (!agent) {
+		/* This is the non bondable mode case */
+		if (device_get_auth(device) > 0x01) {
+			debug("Bonding request, but no agent present");
+			return -1;
+		}
+
+		/* No agent available, and no bonding case */
+		if (*auth == 0x00) {
+			debug("Allowing no bonding without agent");
+			/* No input, no output */
+			*cap = 0x03;
+			goto done;
+		}
+
+		error("No agent available for IO capability");
+		return -1;
+	}
+
+	if (*auth == 0x00) {
+		/* If remote requests dedicated bonding follow that lead */
+		if (device_get_auth(device) == 0x02 ||
+				device_get_auth(device) == 0x03) {
+			uint8_t agent_cap = agent_get_io_capability(agent);
+
+			/* If both remote and local IO capabilities allow MITM
+			 * then require it, otherwise don't */
+			if (device_get_cap(device) == 0x03 ||
+							agent_cap == 0x03)
+				*auth = 0x02;
+			else
+				*auth = 0x03;
+		}
+
+		/* If remote requires MITM then also require it */
+		if (device_get_auth(device) != 0xff &&
+					(device_get_auth(device) & 0x01))
+			*auth |= 0x01;
+	}
+
+	*cap = agent_get_io_capability(agent);
+
+done:
+	debug("final authentication requirement is 0x%02x", *auth);
+
+	return 0;
+}
+
+int hcid_dbus_set_io_cap(bdaddr_t *local, bdaddr_t *remote,
+						uint8_t cap, uint8_t auth)
+{
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+
+	if (!get_adapter_and_device(local, remote, &adapter, &device, TRUE))
+		return -ENODEV;
+
+	device_set_cap(device, cap);
+	device_set_auth(device, auth);
+
+	return 0;
+}
+
+/* Most of the functions in this module require easy access to a connection so
+ * we keep it global here and provide these access functions the other (few)
+ * modules that require access to it */
+
+void set_dbus_connection(DBusConnection *conn)
+{
+	connection = conn;
+}
+
+DBusConnection *get_dbus_connection(void)
+{
+	return connection;
+}
diff --git a/src/dbus-hci.h b/src/dbus-hci.h
new file mode 100644
index 0000000..382e59e
--- /dev/null
+++ b/src/dbus-hci.h
@@ -0,0 +1,53 @@
+/*
+ *
+ *  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
+ *
+ */
+
+int hcid_dbus_request_pin(int dev, bdaddr_t *sba, struct hci_conn_info *ci);
+void hcid_dbus_inquiry_result(bdaddr_t *local, bdaddr_t *peer, uint32_t class, int8_t rssi, uint8_t *data);
+void hcid_dbus_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class);
+void hcid_dbus_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status, char *name);
+void hcid_dbus_conn_complete(bdaddr_t *local, uint8_t status, uint16_t handle, bdaddr_t *peer);
+void hcid_dbus_disconn_complete(bdaddr_t *local, uint8_t status, uint16_t handle, uint8_t reason);
+void hcid_dbus_bonding_process_complete(bdaddr_t *local, bdaddr_t *peer, uint8_t status);
+void hcid_dbus_simple_pairing_complete(bdaddr_t *local, bdaddr_t *peer, uint8_t status);
+void hcid_dbus_setscan_enable_complete(bdaddr_t *local);
+void hcid_dbus_write_class_complete(bdaddr_t *local);
+void hcid_dbus_write_simple_pairing_mode_complete(bdaddr_t *local);
+int hcid_dbus_get_io_cap(bdaddr_t *local, bdaddr_t *remote,
+						uint8_t *cap, uint8_t *auth);
+int hcid_dbus_set_io_cap(bdaddr_t *local, bdaddr_t *remote,
+						uint8_t cap, uint8_t auth);
+int hcid_dbus_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey);
+int hcid_dbus_user_passkey(bdaddr_t *sba, bdaddr_t *dba);
+int hcid_dbus_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey);
+int hcid_dbus_link_key_notify(bdaddr_t *local, bdaddr_t *peer,
+				uint8_t *key, uint8_t key_type,
+				int pin_length, uint8_t old_key_type);
+
+DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status);
+
+const char *class_to_icon(uint32_t class);
+
+void set_dbus_connection(DBusConnection *conn);
+
+DBusConnection *get_dbus_connection(void);
diff --git a/src/device.c b/src/device.c
new file mode 100644
index 0000000..2aaf5b8
--- /dev/null
+++ b/src/device.c
@@ -0,0 +1,2377 @@
+/*
+ *
+ *  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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.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 <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+
+#include "hcid.h"
+#include "adapter.h"
+#include "device.h"
+#include "dbus-common.h"
+#include "dbus-hci.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "agent.h"
+#include "sdp-xml.h"
+#include "storage.h"
+#include "btio.h"
+
+#define DEFAULT_XML_BUF_SIZE	1024
+#define DISCONNECT_TIMER	2
+#define DISCOVERY_TIMER		2
+
+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 io_id;
+	guint listener_id;
+	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;
+	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;
+	guint timer;
+};
+
+struct btd_device {
+	bdaddr_t	bdaddr;
+	gchar		*path;
+	char		name[MAX_NAME_LENGTH + 1];
+	struct btd_adapter	*adapter;
+	GSList		*uuids;
+	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 */
+
+	/* For Secure Simple Pairing */
+	uint8_t		cap;
+	uint8_t		auth;
+
+	uint16_t	handle;			/* Connection handle */
+
+	/* Whether were creating a security mode 3 connection */
+	gboolean	secmode3;
+
+	sdp_list_t	*tmp_records;
+
+	gboolean	renewed_key;
+
+	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 DBusHandlerResult error_connection_attempt_failed(DBusConnection *conn,
+						DBusMessage *msg, int err)
+{
+	return error_common_reply(conn, msg,
+			ERROR_INTERFACE ".ConnectionAttemptFailed",
+			err > 0 ? strerror(err) : "Connection attempt failed");
+}
+
+static DBusHandlerResult error_failed(DBusConnection *conn,
+					DBusMessage *msg, const char * desc)
+{
+	return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc);
+}
+
+static DBusHandlerResult error_failed_errno(DBusConnection *conn,
+						DBusMessage *msg, int err)
+{
+	const char *desc = strerror(err);
+
+	return error_failed(conn, msg, desc);
+}
+
+static inline DBusMessage *no_such_adapter(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter",
+							"No such adapter");
+}
+
+static inline DBusMessage *in_progress(DBusMessage *msg, const char *str)
+{
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str);
+}
+
+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);
+	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);
+	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);
+
+	browse_request_free(req);
+	device->browse = NULL;
+}
+
+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_destroy(device->agent, FALSE);
+
+	if (agent && (agent_is_busy(agent, device) ||
+				agent_is_busy(agent, device->authr)))
+		agent_cancel(agent);
+
+	g_slist_foreach(device->uuids, (GFunc) g_free, NULL);
+	g_slist_free(device->uuids);
+
+	if (device->disconn_timer)
+		g_source_remove(device->disconn_timer);
+
+	debug("device_free(%p)", device);
+
+	g_free(device->authr);
+	g_free(device->path);
+	g_free(device);
+}
+
+gboolean device_is_paired(struct btd_device *device)
+{
+	struct btd_adapter *adapter = device->adapter;
+	char filename[PATH_MAX + 1], *str;
+	char srcaddr[18], dstaddr[18];
+	gboolean ret;
+	bdaddr_t src;
+
+	adapter_get_address(adapter, &src);
+	ba2str(&src, srcaddr);
+	ba2str(&device->bdaddr, dstaddr);
+
+	create_name(filename, PATH_MAX, STORAGEDIR,
+			srcaddr, "linkkeys");
+	str = textfile_caseget(filename, dstaddr);
+	ret = str ? TRUE : FALSE;
+	g_free(str);
+
+	return ret;
+}
+
+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 **uuids;
+	const char *ptr;
+	dbus_bool_t boolean;
+	uint32_t class;
+	int i;
+	GSList *l;
+
+	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 (read_device_alias(srcaddr, dstaddr, name, sizeof(name)) < 1) {
+		if (strlen(ptr) == 0) {
+			g_strdelimit(dstaddr, ":", '-');
+			ptr = dstaddr;
+		}
+	} else
+		ptr = name;
+
+	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);
+
+	/* Trusted */
+	boolean = read_trust(&src, dstaddr, GLOBAL_TRUST);
+	dict_append_entry(&dict, "Trusted", DBUS_TYPE_BOOLEAN, &boolean);
+
+	/* Connected */
+	boolean = (device->handle != 0);
+	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN,
+				&boolean);
+
+	/* UUIDs */
+	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;
+	dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, &uuids, i);
+	g_free(uuids);
+
+	/* 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;
+}
+
+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;
+
+	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 g_dbus_create_error(msg,
+				ERROR_INTERFACE ".Failed",
+				strerror(-err));
+
+	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,
+					dbus_bool_t value, void *data)
+{
+	struct btd_device *device = data;
+	struct btd_adapter *adapter = device->adapter;
+	char srcaddr[18], dstaddr[18];
+	bdaddr_t src;
+
+	adapter_get_address(adapter, &src);
+	ba2str(&src, srcaddr);
+	ba2str(&device->bdaddr, dstaddr);
+
+	write_trust(srcaddr, dstaddr, GLOBAL_TRUST, 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 inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg,
+			ERROR_INTERFACE ".InvalidArguments",
+			"Invalid arguments in method call");
+}
+
+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 invalid_args(msg);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+		return invalid_args(msg);
+
+	dbus_message_iter_get_basic(&iter, &property);
+	dbus_message_iter_next(&iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+		return invalid_args(msg);
+	dbus_message_iter_recurse(&iter, &sub);
+
+	if (g_str_equal("Trusted", property)) {
+		dbus_bool_t value;
+
+		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+			return 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 invalid_args(msg);
+		dbus_message_iter_get_basic(&sub, &alias);
+
+		return set_alias(conn, msg, alias, data);
+	}
+
+	return invalid_args(msg);
+}
+
+static void discover_services_req_exit(DBusConnection *conn, void *user_data)
+{
+	struct browse_req *req = user_data;
+
+	debug("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 g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+						"Discover in progress");
+
+	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern,
+						DBUS_TYPE_INVALID) == FALSE)
+		goto fail;
+
+	if (strlen(pattern) == 0) {
+		err = device_browse(device, conn, msg, NULL, FALSE);
+		if (err < 0)
+			goto fail;
+	} else {
+		uuid_t uuid;
+
+		if (bt_string2uuid(&uuid, pattern) < 0)
+			return invalid_args(msg);
+
+		sdp_uuid128_to_uuid(&uuid);
+
+		err = device_browse(device, conn, msg, &uuid, FALSE);
+		if (err < 0)
+			goto fail;
+	}
+
+	return NULL;
+
+fail:
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+					"Discovery Failed");
+}
+
+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 g_dbus_create_error(msg,
+				ERROR_INTERFACE ".Failed",
+				"No pending discovery");
+
+	if (!dbus_message_is_method_call(device->browse->msg, DEVICE_INTERFACE,
+					"DiscoverServices"))
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".NotAuthorized",
+				"Not Authorized");
+
+	requestor = browse_request_get_requestor(device->browse);
+
+	/* only the discover requestor can cancel the inquiry process */
+	if (!requestor || !g_str_equal(requestor, sender))
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".NotAuthorized",
+				"Not Authorized");
+
+	discover_services_reply(device->browse, -ECANCELED, NULL);
+
+	browse_request_cancel(device->browse);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static gboolean do_disconnect(gpointer user_data)
+{
+	struct btd_device *device = user_data;
+	disconnect_cp cp;
+	int dd;
+	uint16_t dev_id = adapter_get_dev_id(device->adapter);
+
+	device->disconn_timer = 0;
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0)
+		goto fail;
+
+	memset(&cp, 0, sizeof(cp));
+	cp.handle = htobs(device->handle);
+	cp.reason = HCI_OE_USER_ENDED_CONNECTION;
+
+	hci_send_cmd(dd, OGF_LINK_CTL, OCF_DISCONNECT,
+			DISCONNECT_CP_SIZE, &cp);
+
+	close(dd);
+
+fail:
+	return FALSE;
+}
+
+static void bonding_request_cancel(struct bonding_req *bonding)
+{
+	if (!bonding->io)
+		return;
+
+	if (bonding->io_id) {
+		g_source_remove(bonding->io_id);
+		bonding->io_id = 0;
+	}
+
+	g_io_channel_shutdown(bonding->io, TRUE, NULL);
+	g_io_channel_unref(bonding->io);
+	bonding->io = NULL;
+}
+
+void device_request_disconnect(struct btd_device *device, DBusMessage *msg)
+{
+	GSList *l;
+	DBusConnection *conn = get_dbus_connection();
+
+	if (device->bonding)
+		bonding_request_cancel(device->bonding);
+
+	if (device->browse)
+		browse_request_cancel(device->browse);
+
+	if (msg)
+		device->disconnects = g_slist_append(device->disconnects,
+						dbus_message_ref(msg));
+
+	if (device->disconn_timer)
+		return;
+
+	l = device->watches;
+	while (l) {
+		struct btd_disconnect_data *data = l->data;
+
+		l = l->next;
+
+		if (data->watch)
+			/* temporary is set if device is going to be removed */
+			data->watch(device, device->temporary,
+					data->user_data);
+	}
+
+	g_slist_foreach(device->watches, (GFunc) g_free, NULL);
+	g_slist_free(device->watches);
+	device->watches = NULL;
+
+	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->handle)
+		return g_dbus_create_error(msg,
+				ERROR_INTERFACE ".NotConnected",
+				"Device is not connected");
+
+	device_request_disconnect(device, msg);
+
+	return NULL;
+}
+
+static DBusMessage *get_service_attribute_value_reply(DBusMessage *msg, DBusConnection *conn,
+							sdp_data_t *attr)
+{
+	DBusMessage *reply;
+	DBusMessageIter iter;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+	sdp_data_t *curr;
+	sdp_list_t *ap = 0;
+	for (; attr; attr = attr->next) {
+		sdp_list_t *pds = 0;
+		for (curr = attr->val.dataseq; curr; curr = curr->next)
+			pds = sdp_list_append(pds, curr->val.dataseq);
+		ap = sdp_list_append(ap, pds);
+	}
+
+	int ch = sdp_get_proto_port(ap, RFCOMM_UUID);
+	sdp_list_foreach(ap, (sdp_list_func_t) sdp_list_free, NULL);
+	sdp_list_free(ap, NULL);
+	ap = NULL;
+
+	dbus_message_append_args(reply, DBUS_TYPE_INT32, &ch, DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static DBusMessage *get_service_attribute_value(DBusConnection *conn,
+						DBusMessage *msg,
+						void *user_data)
+{
+	struct btd_device *device = user_data;
+	sdp_record_t *rec;
+	sdp_data_t *attr_data;
+	const char *pattern;
+	uint16_t attrId;
+	int err;
+
+	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern,
+					DBUS_TYPE_UINT16, &attrId,
+					DBUS_TYPE_INVALID) == FALSE)
+		goto fail;
+
+	if (strlen(pattern) == 0)
+		return invalid_args(msg);
+
+	rec = btd_device_get_record(device, pattern);
+	if (rec == NULL) {
+		error("rec is NULL");
+		goto fail;
+	}
+
+	attr_data = sdp_data_get(rec, attrId);
+
+	if (attr_data == NULL) {
+		error("attr in null");
+		goto fail;
+	}
+	return get_service_attribute_value_reply(msg, conn, attr_data);
+fail:
+	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+					"GetServiceAttribute Failed");
+}
+
+static GDBusMethodTable device_methods[] = {
+	{ "GetProperties",	"",	"a{sv}",	get_properties	},
+	{ "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},
+	{ "GetServiceAttributeValue",  "sq", "i",       get_service_attribute_value},
+	{ }
+};
+
+static GDBusSignalTable device_signals[] = {
+	{ "PropertyChanged",		"sv"	},
+	{ "DisconnectRequested",	""	},
+	{ }
+};
+
+gboolean device_is_connected(struct btd_device *device)
+{
+	return (device->handle != 0);
+}
+
+static void device_set_connected(struct btd_device *device,
+					DBusConnection *conn,
+					gboolean connected)
+{
+	emit_property_changed(conn, device->path, DEVICE_INTERFACE,
+				"Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+	if (connected && device->secmode3) {
+		struct btd_adapter *adapter = device_get_adapter(device);
+		bdaddr_t sba;
+
+		adapter_get_address(adapter, &sba);
+
+		device->secmode3 = FALSE;
+
+		hcid_dbus_bonding_process_complete(&sba, &device->bdaddr, 0);
+	}
+}
+
+void device_add_connection(struct btd_device *device, DBusConnection *conn,
+				uint16_t handle)
+{
+	if (device->handle) {
+		error("%s: Unable to add connection %u, %u already exist)",
+			device->path, handle, device->handle);
+		return;
+	}
+
+	device->handle = handle;
+
+	device_set_connected(device, conn, TRUE);
+}
+
+void device_remove_connection(struct btd_device *device, DBusConnection *conn,
+				uint16_t handle)
+{
+	if (handle && device->handle != handle) {
+		error("%s: Unable to remove connection %u, handle mismatch (%u)",
+			device->path, handle, device->handle);
+		return;
+	}
+
+	device->handle = 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);
+	}
+
+	device_set_connected(device, conn, FALSE);
+}
+
+gboolean device_has_connection(struct btd_device *device, uint16_t handle)
+{
+	return (handle == device->handle);
+}
+
+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;
+		}
+	}
+}
+
+void device_set_secmode3_conn(struct btd_device *device, gboolean enable)
+{
+	device->secmode3 = enable;
+}
+
+struct btd_device *device_create(DBusConnection *conn,
+					struct btd_adapter *adapter,
+					const gchar *address)
+{
+	gchar *address_up;
+	struct btd_device *device;
+	const gchar *adapter_path = adapter_get_path(adapter);
+	bdaddr_t src;
+	char srcaddr[18];
+
+	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);
+
+	debug("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;
+	adapter_get_address(adapter, &src);
+	ba2str(&src, srcaddr);
+	read_device_name(srcaddr, address, device->name);
+
+	device->auth = 0xff;
+
+	return btd_device_ref(device);
+}
+
+void device_set_name(struct btd_device *device, const char *name)
+{
+	DBusConnection *conn = get_dbus_connection();
+	char alias[MAX_NAME_LENGTH + 1];
+	char srcaddr[18], dstaddr[18];
+	bdaddr_t src;
+
+	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);
+
+	adapter_get_address(device->adapter, &src);
+	ba2str(&src, srcaddr);
+	ba2str(&device->bdaddr, dstaddr);
+
+	if (read_device_alias(srcaddr, dstaddr, alias, sizeof(alias)) == 0)
+		return;
+
+	emit_property_changed(conn, device->path,
+				DEVICE_INTERFACE, "Alias",
+				DBUS_TYPE_STRING, &name);
+}
+
+static void device_remove_bonding(struct btd_device *device,
+							DBusConnection *conn)
+{
+	char filename[PATH_MAX + 1];
+	char *str, srcaddr[18], dstaddr[18];
+	int dd, dev_id;
+	bdaddr_t bdaddr;
+	gboolean paired;
+
+	adapter_get_address(device->adapter, &bdaddr);
+	ba2str(&bdaddr, srcaddr);
+	ba2str(&device->bdaddr, dstaddr);
+
+	create_name(filename, PATH_MAX, STORAGEDIR, srcaddr,
+			"linkkeys");
+
+	/* textfile_del doesn't return an error when the key is not found */
+	str = textfile_caseget(filename, dstaddr);
+	paired = str ? TRUE : FALSE;
+	g_free(str);
+
+	if (!paired)
+		return;
+
+	dev_id = adapter_get_dev_id(device->adapter);
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0)
+		return;
+
+	/* Delete the link key from storage */
+	textfile_casedel(filename, dstaddr);
+
+	/* Delete the link key from the Bluetooth chip */
+	hci_delete_stored_link_key(dd, &device->bdaddr, 0, HCI_REQ_TIMEOUT);
+
+	hci_close_dev(dd);
+
+	paired = FALSE;
+	emit_property_changed(conn, device->path, DEVICE_INTERFACE,
+				"Paired", DBUS_TYPE_BOOLEAN, &paired);
+}
+
+static void device_remove_stored(struct btd_device *device,
+					DBusConnection *conn)
+{
+	bdaddr_t src;
+	char addr[18];
+
+	adapter_get_address(device->adapter, &src);
+	ba2str(&device->bdaddr, addr);
+
+	device_remove_bonding(device, conn);
+	delete_entry(&src, "profiles", addr);
+	delete_entry(&src, "trusts", addr);
+	delete_all_records(&src, &device->bdaddr);
+}
+
+void device_remove(struct btd_device *device, DBusConnection *conn,
+						gboolean remove_stored)
+{
+	GSList *list;
+	struct btd_device_driver *driver;
+
+	debug("Removing device %s", device->path);
+
+	if (device->bonding)
+		device_cancel_bonding(device, HCI_OE_USER_ENDED_CONNECTION);
+
+	if (device->browse)
+		browse_request_cancel(device->browse);
+
+	if (device->handle)
+		do_disconnect(device);
+
+	if (remove_stored)
+		device_remove_stored(device, conn);
+
+	for (list = device->drivers; list; list = list->next) {
+		struct btd_driver_data *driver_data = list->data;
+		driver = driver_data->driver;
+
+		driver->remove(device);
+		g_free(driver_data);
+	}
+
+	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;
+	int err;
+
+	debug("Probe drivers for %s", device->path);
+
+	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("probe failed with driver %s for device %s",
+					driver->name, device->path);
+
+			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);
+	}
+
+	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);
+	}
+
+	if (device->tmp_records) {
+		sdp_list_free(device->tmp_records,
+				(sdp_free_func_t) sdp_record_free);
+		device->tmp_records = NULL;
+	}
+}
+
+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);
+
+	debug("Remove drivers for %s", device->path);
+
+	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;
+
+			debug("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);
+
+	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;
+
+		/* 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 search_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+	struct browse_req *req = user_data;
+	struct btd_device *device = req->device;
+	DBusMessage *reply;
+
+	if (err < 0) {
+		error("%s: error updating services: %s (%d)",
+				device->path, strerror(-err), -err);
+		goto proceed;
+	}
+
+	update_services(req, recs);
+
+	if (device->tmp_records && req->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) {
+		debug("%s: No service update", device->path);
+		goto proceed;
+	}
+
+	/* 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);
+
+proceed:
+	/* Store the device's profiles in the filesystem */
+	store_profiles(device);
+
+	if (!req->msg)
+		goto cleanup;
+
+	if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE,
+					"DiscoverServices")) {
+		discover_services_reply(req, err, req->records);
+		goto cleanup;
+	}
+
+	/* Reply create device request */
+	reply = dbus_message_new_method_return(req->msg);
+	if (!reply)
+		goto cleanup;
+
+	dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &device->path,
+							DBUS_TYPE_INVALID);
+
+	g_dbus_send_message(req->conn, reply);
+
+	device_set_temporary(device, FALSE);
+
+cleanup:
+	browse_request_free(req);
+	device->browse = NULL;
+}
+
+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);
+}
+
+int device_browse(struct btd_device *device, DBusConnection *conn,
+			DBusMessage *msg, uuid_t *search, gboolean reverse)
+{
+	struct btd_adapter *adapter = device->adapter;
+	struct browse_req *req;
+	bdaddr_t src;
+	uuid_t uuid;
+	bt_callback_t cb;
+	int err;
+
+	if (device->browse)
+		return -EBUSY;
+
+	adapter_get_address(adapter, &src);
+
+	req = g_new0(struct browse_req, 1);
+
+	if (conn == NULL)
+		conn = get_dbus_connection();
+
+	req->conn = dbus_connection_ref(conn);
+	req->device = 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;
+	}
+
+	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);
+	}
+
+	err = bt_search_service(&src, &device->bdaddr,
+				&uuid, cb, req, NULL);
+	if (err < 0) {
+		browse_request_free(req);
+		device->browse = NULL;
+	}
+
+	return err;
+}
+
+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;
+
+	return  device->agent;
+}
+
+void device_set_agent(struct btd_device *device, struct agent *agent)
+{
+	if (!device)
+		return;
+
+	device->agent = agent;
+}
+
+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;
+
+	device->temporary = temporary;
+}
+
+void device_set_cap(struct btd_device *device, uint8_t cap)
+{
+	if (!device)
+		return;
+
+	device->cap = cap;
+}
+
+uint8_t device_get_cap(struct btd_device *device)
+{
+	return device->cap;
+}
+
+void device_set_auth(struct btd_device *device, uint8_t auth)
+{
+	if (!device)
+		return;
+
+	device->auth = auth;
+}
+
+uint8_t device_get_auth(struct btd_device *device)
+{
+	return device->auth;
+}
+
+static gboolean start_discovery(gpointer user_data)
+{
+	struct btd_device *device = user_data;
+
+	device_browse(device, NULL, NULL, NULL, TRUE);
+
+	device->discov_timer = 0;
+
+	return FALSE;
+}
+
+DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t 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");
+
+	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_id)
+		g_source_remove(bonding->io_id);
+
+	if (bonding->io)
+		g_io_channel_unref(bonding->io);
+
+	device = bonding->device;
+	g_free(bonding);
+
+	if (!device)
+		return;
+
+	device->bonding = NULL;
+
+	if (!device->agent)
+		return;
+
+	agent_destroy(device->agent, FALSE);
+	device->agent = NULL;
+}
+
+static void device_set_paired(struct btd_device *device, gboolean value)
+{
+	DBusConnection *conn = get_dbus_connection();
+
+	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_set_agent(device, NULL);
+
+	if (device->authr)
+		device->authr->agent = NULL;
+}
+
+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;
+
+	debug("%s: requesting bonding", device->path);
+
+	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;
+
+	debug("Temporary agent registered for %s at %s:%s",
+			device->path, name, agent_path);
+
+proceed:
+	bonding = g_new0(struct bonding_req, 1);
+
+	bonding->conn = dbus_connection_ref(conn);
+	bonding->msg = dbus_message_ref(msg);
+
+	return bonding;
+}
+
+static gboolean bonding_io_cb(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	struct btd_device *device = user_data;
+	DBusMessage *reply;
+
+	if (!device->bonding)
+		return FALSE;
+
+	reply = new_authentication_return(device->bonding->msg,
+					HCI_CONNECTION_TERMINATED);
+	g_dbus_send_message(device->bonding->conn, reply);
+
+	bonding_request_free(device->bonding);
+
+	return FALSE;
+}
+
+static void bonding_connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+	struct btd_device *device = user_data;
+	struct hci_request rq;
+	auth_requested_cp cp;
+	evt_cmd_status rp;
+	int dd;
+	uint16_t handle;
+
+	if (!device->bonding) {
+		if (!err)
+			g_io_channel_shutdown(io, TRUE, NULL);
+		return;
+	}
+
+	if (err) {
+		error("%s", err->message);
+		error_connection_attempt_failed(device->bonding->conn,
+						device->bonding->msg,
+						ENETDOWN);
+		goto cleanup;
+	}
+
+	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);
+		error_connection_attempt_failed(device->bonding->conn,
+						device->bonding->msg,
+						ENETDOWN);
+		g_error_free(err);
+		goto failed;
+	}
+
+	dd = hci_open_dev(adapter_get_dev_id(device->adapter));
+	if (dd < 0) {
+		DBusMessage *reply = no_such_adapter(device->bonding->msg);
+		g_dbus_send_message(device->bonding->conn, reply);
+		goto failed;
+	}
+
+	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) {
+		error("Unable to send HCI request: %s (%d)",
+					strerror(errno), errno);
+		error_failed_errno(device->bonding->conn, device->bonding->msg,
+				errno);
+		hci_close_dev(dd);
+		goto failed;
+	}
+
+	if (rp.status) {
+		error("HCI_Authentication_Requested failed with status 0x%02x",
+				rp.status);
+		error_failed_errno(device->bonding->conn, device->bonding->msg,
+				bt_error(rp.status));
+		hci_close_dev(dd);
+		goto failed;
+	}
+
+	hci_close_dev(dd);
+
+	device->bonding->io_id = g_io_add_watch(io,
+					G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+					bonding_io_cb, device);
+
+	return;
+
+failed:
+	g_io_channel_shutdown(io, TRUE, NULL);
+
+cleanup:
+	device->bonding->io_id = 0;
+	bonding_request_free(device->bonding);
+}
+
+static void create_bond_req_exit(DBusConnection *conn, void *user_data)
+{
+	struct btd_device *device = user_data;
+
+	debug("%s: requestor exited before bonding was completed", device->path);
+
+	if (device->authr)
+		device_cancel_authentication(device, FALSE);
+
+	if (device->bonding) {
+		device->bonding->listener_id = 0;
+		device_request_disconnect(device, NULL);
+	}
+}
+
+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;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	adapter_get_address(adapter, &src);
+	ba2str(&src, srcaddr);
+	ba2str(&device->bdaddr, dstaddr);
+
+	if (device->bonding)
+		return in_progress(msg, "Bonding in progress");
+
+	/* 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;
+}
+
+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);
+}
+
+void device_bonding_complete(struct btd_device *device, uint8_t status)
+{
+	struct bonding_req *bonding = device->bonding;
+	struct authentication_req *auth = device->authr;
+
+	if (auth && auth->type == AUTH_TYPE_NOTIFY && auth->agent)
+		agent_cancel(auth->agent);
+
+	if (status)
+		goto failed;
+
+	device->auth = 0xff;
+
+	g_free(device->authr);
+	device->authr = NULL;
+
+	if (device->renewed_key)
+		return;
+
+	device_set_temporary(device, FALSE);
+
+	/* 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) {
+		/* 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(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 */
+			debug("setting timer for reverse service discovery");
+			device->discov_timer = g_timeout_add_seconds(
+							DISCOVERY_TIMER,
+							start_discovery,
+							device);
+		}
+	}
+
+	device_set_paired(device, TRUE);
+
+	return;
+
+failed:
+	device_cancel_bonding(device, status);
+}
+
+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;
+
+	if (!bonding)
+		return;
+
+	debug("%s: canceling bonding request", device->path);
+
+	if (device->authr)
+		device_cancel_authentication(device, FALSE);
+
+	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)
+		return;
+
+	((agent_pincode_cb) auth->cb)(agent, err, pincode, device);
+
+	auth->cb = 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)
+		return;
+
+	((agent_cb) auth->cb)(agent, err, device);
+
+	auth->cb = 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)
+		return;
+
+	((agent_passkey_cb) auth->cb)(agent, err, passkey, device);
+
+	auth->cb = NULL;
+}
+
+static void pairing_consent_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)
+		return;
+
+	((agent_cb) auth->cb)(agent, err, device);
+
+	auth->cb = 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;
+	int ret;
+
+	debug("%s: requesting agent authentication", device->path);
+
+	agent = device->agent;
+
+	if (!agent)
+		agent = adapter_get_agent(device->adapter);
+
+	if (!agent) {
+		error("No agent available for %u request", 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:
+		ret = agent_request_pincode(agent, device, pincode_cb,
+								auth, NULL);
+		break;
+	case AUTH_TYPE_PASSKEY:
+		ret = agent_request_passkey(agent, device, passkey_cb,
+								auth, NULL);
+		break;
+	case AUTH_TYPE_CONFIRM:
+		ret = agent_request_confirmation(agent, device, passkey,
+						confirm_cb, auth, NULL);
+		break;
+	case AUTH_TYPE_NOTIFY:
+		ret = agent_display_passkey(agent, device, passkey);
+		break;
+	case AUTH_TYPE_AUTO:
+		ret = 0;
+		break;
+	case AUTH_TYPE_PAIRING_CONSENT:
+		ret = agent_request_pairing_consent(agent, device,
+							pairing_consent_cb, auth, NULL);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (ret < 0) {
+		error("Failed requesting authentication");
+		g_free(auth);
+		device->authr = NULL;
+	}
+
+	return ret;
+}
+
+static void cancel_authentication(struct authentication_req *auth)
+{
+	struct btd_device *device = auth->device;
+	struct agent *agent = auth->agent;
+	DBusError err;
+
+	if (!auth->cb)
+		return;
+
+	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_PAIRING_CONSENT:
+		((agent_cb) auth->cb) (agent, &err, device);
+		break;
+	case AUTH_TYPE_NOTIFY:
+	case AUTH_TYPE_AUTO:
+		/* User Notify/Auto 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;
+
+	if (!auth)
+		return;
+
+	debug("%s: canceling authentication request", device->path);
+
+	if (auth->agent)
+		agent_cancel(auth->agent);
+
+	if (!aborted)
+		cancel_authentication(auth);
+
+	device->authr = NULL;
+	g_free(auth);
+}
+
+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 device_set_renewed_key(struct btd_device *device, gboolean renewed)
+{
+	device->renewed_key = renewed;
+}
+
+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)
+		return find_record_in_list(device->tmp_records, uuid);
+
+	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++;
+
+	debug("btd_device_ref(%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--;
+
+	debug("btd_device_unref(%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
index 0000000..7f297c1
--- /dev/null
+++ b/src/device.h
@@ -0,0 +1,109 @@
+/*
+ *
+ *  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
+ *
+ */
+
+#define DEVICE_INTERFACE	"org.bluez.Device"
+
+struct btd_device;
+
+typedef enum {
+	AUTH_TYPE_PINCODE,
+	AUTH_TYPE_PASSKEY,
+	AUTH_TYPE_CONFIRM,
+	AUTH_TYPE_NOTIFY,
+	AUTH_TYPE_AUTO,
+	AUTH_TYPE_PAIRING_CONSENT,
+} auth_type_t;
+
+struct btd_device *device_create(DBusConnection *conn, struct btd_adapter *adapter,
+				const gchar *address);
+void device_set_name(struct btd_device *device, const char *name);
+void device_remove(struct btd_device *device, DBusConnection *conn,
+						gboolean remove_stored);
+gint device_address_cmp(struct btd_device *device, const gchar *address);
+int device_browse(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);
+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 *adapter, bdaddr_t *bdaddr);
+const gchar *device_get_path(struct btd_device *device);
+struct agent *device_get_agent(struct btd_device *device);
+void device_set_agent(struct btd_device *device, struct agent *agent);
+gboolean device_is_busy(struct btd_device *device);
+gboolean device_is_temporary(struct btd_device *device);
+gboolean device_is_paired(struct btd_device *device);
+void device_set_temporary(struct btd_device *device, gboolean temporary);
+void device_set_cap(struct btd_device *device, uint8_t cap);
+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);
+void device_set_secmode3_conn(struct btd_device *device, gboolean enable);
+DBusMessage *device_create_bonding(struct btd_device *device,
+				DBusConnection *conn, DBusMessage *msg,
+				const char *agent_path, uint8_t capability);
+void device_remove_bondind(struct btd_device *device, DBusConnection *connection);
+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_set_renewed_key(struct btd_device *device, gboolean renewed);
+void device_add_connection(struct btd_device *device, DBusConnection *conn,
+				uint16_t handle);
+void device_remove_connection(struct btd_device *device, DBusConnection *conn,
+				uint16_t handle);
+gboolean device_has_connection(struct btd_device *device, uint16_t handle);
+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);
diff --git a/src/error.c b/src/error.c
new file mode 100644
index 0000000..f54e819
--- /dev/null
+++ b/src/error.c
@@ -0,0 +1,50 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  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"
+
+/* Helper function - internal use only */
+DBusHandlerResult error_common_reply(DBusConnection *conn, DBusMessage *msg,
+					const char *name, const char *descr)
+{
+	DBusMessage *derr;
+
+	if (!conn || !msg)
+		return DBUS_HANDLER_RESULT_HANDLED;
+
+	derr = dbus_message_new_error(msg, name, descr);
+	if (!derr)
+		return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+	g_dbus_send_message(conn, derr);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
diff --git a/src/error.h b/src/error.h
new file mode 100644
index 0000000..82301d5
--- /dev/null
+++ b/src/error.h
@@ -0,0 +1,31 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  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"
+
+DBusHandlerResult error_common_reply(DBusConnection *conn, DBusMessage *msg,
+					const char *name, const char *descr);
diff --git a/src/hcid.conf b/src/hcid.conf
new file mode 100644
index 0000000..873d212
--- /dev/null
+++ b/src/hcid.conf
@@ -0,0 +1,57 @@
+#
+# HCI daemon configuration file.
+#
+
+# HCId options
+options {
+	# Automatically initialize new devices
+	autoinit yes;
+
+	# Security Manager mode
+	#   none - Security manager disabled
+	#   auto - Use local PIN for incoming connections
+	#   user - Always ask user for a PIN
+	#
+	security user;
+
+	# Pairing mode
+	#   none  - Pairing disabled
+	#   multi - Allow pairing with already paired devices
+	#   once  - Pair once and deny successive attempts
+	pairing multi;
+
+	# Default PIN code for incoming connections
+	passkey "Bluez";
+}
+
+# Default settings for HCI devices
+device {
+	# Local device name
+	#   %d - device id
+	#   %h - host name
+	name "BlueZ (%d)";
+
+	# Local device class
+	class 0x000100;
+
+	# Default packet type
+	#pkt_type DH1,DM1,HV1;
+
+	# Inquiry and Page scan
+	iscan enable; pscan enable;
+
+	# Default link mode
+	#   none   - no specific policy 
+	#   accept - always accept incoming connections
+	#   master - become master on incoming connections,
+	#            deny role switch on outgoing connections
+	lm accept;
+
+	# Default link policy
+	#   none    - no specific policy
+	#   rswitch - allow role switch
+	#   hold    - allow hold mode
+	#   sniff   - allow sniff mode
+	#   park    - allow park mode
+	lp rswitch,hold,sniff,park;
+}
diff --git a/src/hcid.conf.5.in b/src/hcid.conf.5.in
new file mode 100644
index 0000000..b771389
--- /dev/null
+++ b/src/hcid.conf.5.in
@@ -0,0 +1,227 @@
+.TH "HCID.CONF" "5" "March 2004" "hcid.conf - HCI daemon" "System management commands"
+.SH "NAME"
+@CONFIGDIR@/hcid.conf \- Configuration file for the hcid Bluetooth HCI daemon
+
+.SH "DESCRIPTION"
+@CONFIGDIR@/hcid.conf contains all the options needed by the Bluetooth Host Controller Interface daemon.
+
+It consists of sections and parameters. A section begins with
+the name of the section followed by optional specifiers and the
+parameters inside curly brackets. Sections contain parameters of
+the form:
+.TP
+\fIname\fP \fIvalue1\fP, \fIvalue2\fP ... ;
+
+.PP
+Any character after a hash ('#') character is ignored until newline.
+Whitespace is also ignored.
+
+
+The valid section names for
+.B hcid.conf
+are, at the moment:
+
+.TP
+.B options
+contains generic options for hcid and the pairing policy.
+.TP
+.B device
+contains lower\-level options for the hci devices connected to the computer.
+.SH "OPTIONS SECTION"
+The following parameters may be present in an option section:
+
+
+.TP
+\fBautoinit\fP  yes|no
+
+Automatically initialize newly connected devices. The default is \fIno\fP.
+
+
+.TP
+\fBpairing\fP  none|multi|once
+
+\fInone\fP means that pairing is disabled. \fImulti\fP allows pairing
+with already paired devices. \fIonce\fP allows pairing once and denies
+successive attempts. The default hcid configuration is shipped with \fBmulti\fP
+enabled
+
+.TP
+\fBoffmode\fP  noscan|devdown
+
+\fInoscan\fP means that page and inquiry scans are disabled when you call
+SetMode("off"). \fIdevdown\fP sets the adapter into down state (same what
+\fIhciconfig hci0 down\fP does).
+
+.TP
+\fBdeviceid\fP	<vendor>:<product>:<version>
+
+This option allows to specify the vendor and product information of the
+Bluetooth device ID service record.
+
+.TP
+\fBpasskey\fP "\fIpin\fP"
+
+The default PIN for incoming connections if \fBsecurity\fP has been
+set to \fIauto\fP.
+
+.TP
+\fBsecurity\fP  none|auto|user
+
+\fInone\fP means the security manager is disabled. \fIauto\fP uses
+local PIN, by default from pin_code, for incoming
+connections. \fIuser\fP always asks the user for a PIN.
+
+.SH "DEVICE SECTION"
+Parameters within a device section with no specifier, the default
+device section, will be applied to all devices and device sections
+where these are unspecified. The following optional device specifiers
+are supported:
+
+.TP
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP
+
+Parameters specified within this section will be applied to the device
+with this \fIdevice bluetooth address\fP. All other parameters are applied from
+the default section.
+
+.TP
+\fBhci\fIn\fP
+
+Parameters specified within this section will be applied to the device
+with this \fIdevice interface\fP, unless that device is matched by a
+\fIdevice address\fP section. All other parameters are applied from
+the default section.
+
+
+.PP
+\fBNote\fP: Most of the options supported in the \fBdevice\fP section are described to some extent in the bluetooth specification version 1.2 Vol2, Part E section 6. Please refer to it for technical details.
+
+.PP
+The following parameters may be present in a device section:
+
+.TP
+\fBname\fP  "\fIname\fP"
+
+The device name. \fI%d\fP inserts the device id. \fI%h\fP inserts
+the host name.
+
+
+.TP
+\fBclass\fP  0x\fISSDDdd\fP (three bytes)
+
+The Bluetooth Device Class is described in the Bluetooth Specification section 1.2 ("Assigned Numbers \- Bluetooth Baseband").
+
+The default shipped with hcid is 0x000100 which simply stands for "Computer".
+
+The Bluetooth device class is a high\-level description of the bluetooth device, composed of three bytes: the "Major Service Class" (byte "SS" above), the "Major Device Class" (byte "DD" above) and the "Minor Device Class" (byte "dd" above). These classes describe the high\-level capabilities of the device, such as "Networking Device", "Computer", etc. This information is often used by clients who are looking for a certain type of service around them.
+
+Where it becomes tricky is that another type of mechanism for service discovery exists: "SDP", as in "Service Discovery Protocol".
+
+In practice, most Bluetooth clients scan their surroundings in two successive steps: they first look for all bluetooth devices around them and find out their "class". You can do this on Linux with the \fBhcitool scan\fP command. Then, they use SDP in order to check if a device in a given class offers the type of service that they want.
+
+This means that the hcid.conf "class" parameter needs to be set up properly if particular services are running on the host, such as "PAN", or "OBEX Obect Push", etc: in general a device looking for a service such as "Network Access Point" will only scan for this service on devices containing "Networking" in their major service class.
+
+
+.IP
+Major service class byte allocation (from LSB to MSB):
+
+Bit 1:	Positioning (Location identification)
+
+Bit 2:  Networking (LAN, Ad hoc, ...)
+
+Bit 3:  Rendering (Printing, Speaker, ...)
+
+Bit 4:  Capturing (Scanner, Microphone, ...)
+
+Bit 5:  Object Transfer (v\-Inbox, v\-Folder, ...)
+
+Bit 6:  Audio (Speaker, Microphone, Headset service, ...)
+
+Bit 7:  Telephony (Cordless telephony, Modem, Headset service, ...)
+
+Bit 8:  Information (WEB\-server, WAP\-server, ...)
+
+.IP
+Example: class 0x02hhhh : the device offers networking service
+
+
+.IP
+Major device class allocation:
+
+0x00: Miscellaneous
+
+0x01: Computer (desktop,notebook, PDA, organizers, .... )
+
+0x02: Phone (cellular, cordless, payphone, modem, ...)
+
+0x03: LAN /Network Access point
+
+0x04: Audio/Video (headset,speaker,stereo, video display, vcr.....
+
+0x05: Peripheral (mouse, joystick, keyboards, ..... )
+
+0x06: Imaging (printing, scanner, camera, display, ...)
+
+Other values are not defined (refer to the Bluetooth specification for more details
+
+.IP
+Minor device class allocation: the meaning of this byte depends on the major class allocation, please refer to the Bluetooth specifications for more details).
+
+.IP
+.B Example:
+if PAND runs on your server, you need to set up at least \fBclass 0x020100\fP, which stands for "Service Class: Networking" and "Device Class: Computer, Uncategorized".
+
+
+.TP
+\fBiscan\fP  enable|disable
+.TP
+\fBpscan\fP  enable|disable
+
+Bluetooth devices discover and connect to each other through the use of two special Bluetooth channels, the Inquiry and Page channels (described in the Bluetooth Spec Volume 1, Part A, Section 3.3.3, page 35). These two options enable the channels on the bluetooth device.
+
+\fBiscan enable\fP: makes the bluetooth device "discoverable" by enabling it to answer "inquiries" from other nearby bluetooth devices.
+
+\fBpscan enable\fP: makes the bluetooth device "connectable to" by enabling the use of the "page scan" channel.
+
+.TP
+\fBlm\fP  none|accept,master
+
+\fInone\fP means no specific policy. \fIaccept\fP means always accept
+incoming connections. \fImaster\fP means become master on incoming
+connections and deny role switch on outgoing connections.
+
+.TP
+\fBlp\fP  none|rswitch,hold,sniff,park
+
+\fInone\fP means no specific policy. \fIrswitch\fP means allow role
+switch. \fIhold\fP means allow hold mode. \fIsniff\fP means allow
+sniff mode. \fIpark\fP means allow park mode. Several options can be
+combined.
+
+This option determines the various operational modes that are allowed for this device when it participates to a piconet. Normally  hold and sniff should be enabled for standard operations.
+
+hold: this mode is related to synchronous communications (SCO voice channel for example).
+
+sniff: when in this mode, a device is only present on the piconet during determined slots of time, allowing it to do other things when it is "absent", for example to scan for other bluetooth devices.
+
+park:  this is a mode where the device is put on standby on the piconet, for power\-saving purposes for example.
+
+rswitch: this is a mode that enables role\-switch (master <\-> slave) between two devices in a piconet. It is not clear whether this needs to be enabled in order to make the "lm master" setting work properly or not.
+
+.TP
+\fBpageto\fP  \fIn\fP
+
+Page Timeout measured in number of baseband slots. Interval length = N * 0.625 msec (1 baseband slot)
+
+.TP
+\fBdiscovto\fP  \fIn\fP
+
+The time in seconds that the device will stay in discoverable mode. 0 disables this feature and forces the device to be always discoverable.
+
+.SH "FILES"
+.TP
+.I @CONFIGDIR@/hcid.conf
+Default location of the global configuration file.
+
+.SH "AUTHOR"
+This manual page was written by Edouard Lafargue, Fredrik Noring, Maxim Krasnyansky and Marcel Holtmann.
diff --git a/src/hcid.h b/src/hcid.h
new file mode 100644
index 0000000..2f07259
--- /dev/null
+++ b/src/hcid.h
@@ -0,0 +1,100 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+ *
+ */
+
+/* When all services should trust a remote device */
+#define GLOBAL_TRUST "[all]"
+
+/*
+ * Scanning modes, used by DEV_SET_MODE
+ * off: remote devices are not allowed to find or connect to this device
+ * connectable: remote devices are allowed to connect, but they are not
+ *              allowed to find it.
+ * discoverable: remote devices are allowed to connect and find this device
+ * limited: limited discoverable - GIAC + IAC enabled and set limited
+ *          bit on device class.
+ */
+
+#define MODE_OFF		0x00
+#define MODE_CONNECTABLE	0x01
+#define MODE_DISCOVERABLE	0x02
+#define MODE_LIMITED		0x03
+#define MODE_UNKNOWN		0xff
+
+#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;
+
+	uint8_t		scan;
+	uint8_t		mode;
+	uint8_t		discov_interval;
+	char		deviceid[15]; /* FIXME: */
+
+	int		sock;
+};
+
+enum {
+	HCID_SET_NAME,
+	HCID_SET_CLASS,
+	HCID_SET_PAGETO,
+	HCID_SET_DISCOVTO,
+};
+
+extern struct main_opts main_opts;
+
+char *expand_name(char *dst, int size, char *str, int dev_id);
+
+void hci_req_queue_remove(int dev_id, bdaddr_t *dba);
+
+void start_security_manager(int hdev);
+void stop_security_manager(int hdev);
+
+void btd_start_exit_timer(void);
+void btd_stop_exit_timer(void);
+
+void set_pin_length(bdaddr_t *sba, int length);
+
+gboolean plugin_init(GKeyFile *config);
+void plugin_cleanup(void);
+
+void rfkill_init(void);
+void rfkill_exit(void);
+
+void __probe_servers(const char *adapter);
+void __remove_servers(const char *adapter);
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..ff423a4
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,494 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include <glib.h>
+
+#ifdef ANDROID_EXPAND_NAME
+#include <cutils/properties.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include "logging.h"
+
+#include "hcid.h"
+#include "sdpd.h"
+#include "adapter.h"
+#include "dbus-hci.h"
+#include "dbus-common.h"
+#include "agent.h"
+#include "manager.h"
+
+#define LAST_ADAPTER_EXIT_TIMEOUT 30
+
+struct main_opts main_opts;
+
+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;
+
+	debug("parsing main.conf");
+
+	val = g_key_file_get_integer(config, "General",
+						"DiscoverableTimeout", &err);
+	if (err) {
+		debug("%s", err->message);
+		g_clear_error(&err);
+	} else {
+		debug("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) {
+		debug("%s", err->message);
+		g_clear_error(&err);
+	} else {
+		debug("pairto=%d", val);
+		main_opts.pairto = val;
+	}
+
+	val = g_key_file_get_integer(config, "General", "PageTimeout", &err);
+	if (err) {
+		debug("%s", err->message);
+		g_clear_error(&err);
+	} else {
+		debug("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) {
+		debug("%s", err->message);
+		g_clear_error(&err);
+	} else {
+		debug("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) {
+		debug("%s", err->message);
+		g_clear_error(&err);
+	} else {
+		debug("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) {
+		debug("%s", err->message);
+		g_clear_error(&err);
+	} else {
+		debug("discov_interval=%d", val);
+		main_opts.discov_interval = val;
+	}
+
+	boolean = g_key_file_get_boolean(config, "General",
+						"InitiallyPowered", &err);
+	if (err) {
+		debug("%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) {
+		debug("%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) {
+		debug("%s", err->message);
+		g_clear_error(&err);
+	} else {
+		debug("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) {
+		debug("%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;
+
+	main_opts.link_mode = HCI_LM_ACCEPT;
+
+	main_opts.link_policy = HCI_LP_RSWITCH | HCI_LP_SNIFF |
+						HCI_LP_HOLD | HCI_LP_PARK;
+}
+
+/*
+ * Device name expansion
+ *   %d - device id
+ */
+char *expand_name(char *dst, int size, char *str, int dev_id)
+{
+	register int sp, np, olen;
+	char *opt, buf[10];
+
+#ifdef ANDROID_EXPAND_NAME
+	char value[PROPERTY_VALUE_MAX];
+#endif
+
+	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;
+
+#ifdef ANDROID_EXPAND_NAME
+			case 'b':
+				property_get("ro.product.brand", value, "");
+				opt = value;
+			break;
+
+			case 'm':
+				property_get("ro.product.model", value, "");
+				opt = value;
+			break;
+
+			case 'n':
+				property_get("ro.product.name", value, "");
+				opt = value;
+			break;
+#endif
+
+			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;
+}
+
+static void init_defaults(void)
+{
+	/* Default HCId settings */
+	memset(&main_opts, 0, sizeof(main_opts));
+	main_opts.scan	= SCAN_PAGE;
+	main_opts.mode	= MODE_CONNECTABLE;
+	main_opts.name	= g_strdup("BlueZ");
+	main_opts.discovto	= HCID_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)
+{
+	toggle_debug();
+}
+
+static gboolean option_detach = TRUE;
+static gboolean option_debug = 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 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" },
+	{ "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;
+
+#ifdef ANDROID_SET_AID_AND_CAP
+	/* Unfortunately Android's init.rc does not yet support applying
+	 * capabilities. So we must do it in-process. */
+	void *android_set_aid_and_cap(void);
+	android_set_aid_and_cap();
+#endif
+
+	init_defaults();
+
+	context = g_option_context_new(NULL);
+	g_option_context_add_main_entries(context, options, NULL);
+
+	if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) {
+		if (err != NULL) {
+			g_printerr("%s\n", err->message);
+			g_error_free(err);
+		} else
+			g_printerr("An unknown error occurred\n");
+		exit(1);
+	}
+
+	if (option_udev == TRUE) {
+		int err;
+
+		option_detach = TRUE;
+		err = hcid_dbus_init();
+		if (err < 0) {
+			if (err == -EALREADY)
+				exit(0);
+			exit(1);
+		}
+	}
+
+	g_option_context_free(context);
+
+	if (option_detach == TRUE && option_udev == FALSE) {
+		if (daemon(0, 0)) {
+			perror("Can't start daemon");
+			exit(1);
+		}
+	}
+
+	umask(0077);
+
+	start_logging("bluetoothd", "Bluetooth daemon %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) {
+		info("Enabling debug information");
+		enable_debug();
+	}
+
+	config = load_config(CONFIGDIR "/main.conf");
+
+	parse_config(config);
+
+	agent_init();
+
+	if (option_udev == FALSE) {
+		if (hcid_dbus_init() < 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);
+
+	/* 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);
+
+	event_loop = g_main_loop_new(NULL, FALSE);
+
+	if (adapter_ops_setup() < 0) {
+		error("adapter_ops_setup failed");
+		exit(1);
+	}
+
+	rfkill_init();
+
+	debug("Entering main loop");
+
+	g_main_loop_run(event_loop);
+
+	hcid_dbus_unregister();
+
+	hcid_dbus_exit();
+
+	rfkill_exit();
+
+	plugin_cleanup();
+
+	stop_sdp_server();
+
+	agent_exit();
+
+	g_main_loop_unref(event_loop);
+
+	if (config)
+		g_key_file_free(config);
+
+	info("Exit");
+
+	stop_logging();
+
+	return 0;
+}
diff --git a/src/main.conf b/src/main.conf
new file mode 100644
index 0000000..b03e169
--- /dev/null
+++ b/src/main.conf
@@ -0,0 +1,52 @@
+[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 = "Bluez"
+
+# Default device class. Only the major and minor device class bits are
+# considered.
+Class = 0x000100
+
+# How long to stay in discoverable mode before going back to non-discoverable
+# The value is in seconds. Default is 180, i.e. 3 minutes.
+# 0 = disable timer, i.e. stay discoverable forever
+DiscoverableTimeout = 120
+
+# 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 = android:generic:1.5
+
+# 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
diff --git a/src/manager.c b/src/manager.c
new file mode 100644
index 0000000..3cf914d
--- /dev/null
+++ b/src/manager.c
@@ -0,0 +1,534 @@
+/*
+ *
+ *  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 <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "dbus-common.h"
+#include "logging.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;
+}
+
+void manager_update_svc(const bdaddr_t *bdaddr, uint8_t svc)
+{
+	GSList *l;
+	bdaddr_t src;
+
+	for (l = adapters; l != NULL; l = l->next) {
+		struct btd_adapter *adapter = l->data;
+
+		adapter_get_address(adapter, &src);
+
+		if (bacmp(bdaddr, BDADDR_ANY) != 0 && bacmp(bdaddr, &src) != 0)
+			continue;
+
+		adapter_update(adapter, svc);
+	}
+}
+
+int manager_get_adapter_class(uint16_t dev_id, uint8_t *cls)
+{
+	struct btd_adapter *adapter;
+
+	adapter = manager_find_adapter_by_id(dev_id);
+	if (!adapter)
+		return -EINVAL;
+
+	return adapter_get_class(adapter, cls);
+}
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg,
+			ERROR_INTERFACE ".InvalidArguments",
+			"Invalid arguments in method call");
+}
+
+static inline DBusMessage *no_such_adapter(DBusMessage *msg)
+{
+	return g_dbus_create_error(msg,
+			ERROR_INTERFACE ".NoSuchAdapter",
+			"No such adapter");
+}
+
+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 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 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
+		adapter = manager_find_adapter_by_address(pattern);
+
+	if (!adapter)
+		return 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, i++) {
+		struct btd_adapter *adapter = list->data;
+
+		if (!adapter_is_ready(adapter))
+			continue;
+
+		array[i] = (char *) adapter_get_path(adapter);
+	}
+	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	},
+	{ }
+};
+
+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, i++) {
+		struct btd_adapter *adapter = list->data;
+
+		if (!adapter_is_ready(adapter))
+			continue;
+
+		array[i] = (char *) adapter_get_path(adapter);
+	}
+
+	emit_array_property_changed(connection, "/",
+					MANAGER_INTERFACE, "Adapters",
+					DBUS_TYPE_OBJECT_PATH, &array);
+
+	g_free(array);
+}
+
+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_path_cmp(gconstpointer a, gconstpointer b)
+{
+	struct btd_adapter *adapter = (struct btd_adapter *) a;
+	const char *path = b;
+	const gchar *adapter_path = adapter_get_path(adapter);
+
+	return strcmp(adapter_path, path);
+}
+
+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);
+}
+
+static gint adapter_address_cmp(gconstpointer a, gconstpointer b)
+{
+	struct btd_adapter *adapter = (struct btd_adapter *) a;
+	const char *address = b;
+	bdaddr_t bdaddr;
+	char addr[18];
+
+	adapter_get_address(adapter, &bdaddr);
+	ba2str(&bdaddr, addr);
+
+	return strcmp(addr, address);
+}
+
+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_address(const char *address)
+{
+	GSList *match;
+
+	match = g_slist_find_custom(adapters, address, adapter_address_cmp);
+	if (!match)
+		return NULL;
+
+	return match->data;
+}
+
+struct btd_adapter *manager_find_adapter_by_path(const char *path)
+{
+	GSList *match;
+
+	match = g_slist_find_custom(adapters, path, adapter_path_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;
+}
+
+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();
+}
+
+int manager_register_adapter(int id, gboolean devup)
+{
+	struct btd_adapter *adapter;
+
+	adapter = manager_find_adapter_by_id(id);
+	if (adapter) {
+		error("Unable to register adapter: hci%d already exist", id);
+		return -1;
+	}
+
+	adapter = adapter_create(connection, id, devup);
+	if (!adapter)
+		return -1;
+
+	adapters = g_slist_append(adapters, adapter);
+
+	return 0;
+}
+
+int 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;
+}
+
+int manager_start_adapter(int id)
+{
+	struct btd_adapter* adapter;
+	int ret;
+
+	adapter = manager_find_adapter_by_id(id);
+	if (!adapter) {
+		error("Getting device data failed: hci%d", id);
+		return -EINVAL;
+	}
+
+	ret = adapter_start(adapter);
+	if (ret < 0)
+		return ret;
+
+	if (default_adapter_id < 0)
+		manager_set_default_adapter(id);
+
+	return ret;
+}
+
+int manager_stop_adapter(int id)
+{
+	struct btd_adapter *adapter;
+
+	adapter = manager_find_adapter_by_id(id);
+	if (!adapter) {
+		error("Getting device data failed: hci%d", id);
+		return -EINVAL;
+	}
+
+	return adapter_stop(adapter);
+}
+
+int manager_get_default_adapter()
+{
+	return default_adapter_id;
+}
+
+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);
+}
+
+void btd_manager_set_offline(gboolean offline)
+{
+	GSList *l;
+
+	for (l = adapters; l != NULL; l = g_slist_next(l)) {
+		struct btd_adapter *adapter = l->data;
+
+		if (offline)
+			btd_adapter_switch_offline(adapter);
+		else
+			btd_adapter_restore_powered(adapter);
+	}
+}
diff --git a/src/manager.h b/src/manager.h
new file mode 100644
index 0000000..9463574
--- /dev/null
+++ b/src/manager.h
@@ -0,0 +1,48 @@
+/*
+ *
+ *  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
+ *
+ */
+
+#include <bluetooth/bluetooth.h>
+#include <dbus/dbus.h>
+
+#define MANAGER_INTERFACE "org.bluez.Manager"
+
+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_address(const char *address);
+struct btd_adapter *manager_find_adapter_by_path(const char *path);
+struct btd_adapter *manager_find_adapter_by_id(int id);
+GSList *manager_get_adapters(void);
+int manager_register_adapter(int id, gboolean devup);
+int manager_unregister_adapter(int id);
+int manager_start_adapter(int id);
+int manager_stop_adapter(int id);
+void manager_add_adapter(const char *path);
+int manager_get_default_adapter();
+void manager_set_default_adapter(int id);
+void manager_update_svc(const bdaddr_t *bdaddr, uint8_t svc);
+int manager_get_adapter_class(uint16_t dev_id, uint8_t *cls);
+void btd_manager_set_offline(gboolean offline);
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644
index 0000000..706a14f
--- /dev/null
+++ b/src/plugin.c
@@ -0,0 +1,221 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <dlfcn.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#include "plugin.h"
+#include "logging.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;
+	}
+
+	debug("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 is_disabled(const char *name, char **list)
+{
+	int i;
+
+	for (i = 0; list[i] != NULL; i++) {
+		char *str;
+		gboolean equal;
+
+		if (g_str_equal(name, list[i]))
+			return TRUE;
+
+		str = g_strdup_printf("%s.so", list[i]);
+
+		equal = g_str_equal(str, name);
+
+		g_free(str);
+
+		if (equal)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+#include "builtin.h"
+
+gboolean plugin_init(GKeyFile *config)
+{
+	GSList *list;
+	GDir *dir;
+	const gchar *file;
+	gchar **disabled;
+	unsigned int i;
+
+	if (strlen(PLUGINDIR) == 0)
+		return FALSE;
+
+	/* Make a call to BtIO API so its symbols got resolved before the
+	 * plugins are loaded. */
+	bt_io_error_quark();
+
+	if (config)
+		disabled = g_key_file_get_string_list(config, "General",
+							"DisablePlugins",
+							NULL, NULL);
+	else
+		disabled = NULL;
+
+	debug("Loading builtin plugins");
+
+	for (i = 0; __bluetooth_builtin[i]; i++) {
+		if (disabled && is_disabled(__bluetooth_builtin[i]->name,
+								disabled))
+			continue;
+
+		add_plugin(NULL,  __bluetooth_builtin[i]);
+	}
+
+	debug("Loading plugins %s", PLUGINDIR);
+
+	dir = g_dir_open(PLUGINDIR, 0, NULL);
+	if (!dir) {
+		g_strfreev(disabled);
+		return FALSE;
+	}
+
+	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;
+
+		if (disabled && is_disabled(file, disabled))
+			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 (add_plugin(handle, desc) == FALSE)
+			dlclose(handle);
+	}
+
+	g_dir_close(dir);
+
+	g_strfreev(disabled);
+
+	for (list = plugins; list; list = list->next) {
+		struct bluetooth_plugin *plugin = list->data;
+
+		if (plugin->desc->init() < 0)
+			continue;
+
+		plugin->active = TRUE;
+	}
+
+	return TRUE;
+}
+
+void plugin_cleanup(void)
+{
+	GSList *list;
+
+	debug("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
index 0000000..00e0b3b
--- /dev/null
+++ b/src/plugin.h
@@ -0,0 +1,47 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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
+ *
+ */
+#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/rfkill.c b/src/rfkill.c
new file mode 100644
index 0000000..286fc5c
--- /dev/null
+++ b/src/rfkill.c
@@ -0,0 +1,172 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "logging.h"
+#include "manager.h"
+#include "adapter.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];
+	gsize len;
+	GIOError err;
+	int fd, id;
+
+	if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
+		return FALSE;
+
+	memset(buf, 0, sizeof(buf));
+
+	err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len);
+	if (err) {
+		if (err == G_IO_ERROR_AGAIN)
+			return TRUE;
+		return FALSE;
+	}
+
+	if (len != sizeof(struct rfkill_event))
+		return TRUE;
+
+	debug("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;
+
+	debug("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/sdpd-database.c b/src/sdpd-database.c
new file mode 100644
index 0000000..3ce056a
--- /dev/null
+++ b/src/sdpd-database.c
@@ -0,0 +1,304 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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 "logging.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
+ */
+static int record_sort(const void *r1, const void *r2)
+{
+	const sdp_record_t *rec1 = (const sdp_record_t *) r1;
+	const sdp_record_t *rec2 = (const sdp_record_t *) 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 = (const sdp_access_t *) r1;
+	const sdp_access_t *rec2 = (const sdp_access_t *) 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 = (sdp_indexed_t *) 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 = (sdp_indexed_t *) 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 = (const sdp_indexed_t *) i1;
+	const sdp_indexed_t *s2 = (const sdp_indexed_t *) 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)
+{
+	sdp_access_t *dev;
+
+	SDPDBG("Adding rec : 0x%lx", (long) rec);
+	SDPDBG("with handle : 0x%x", rec->handle);
+
+	service_db = sdp_list_insert_sorted(service_db, rec, record_sort);
+
+	dev = malloc(sizeof(*dev));
+	if (!dev)
+		return;
+
+	bacpy(&dev->device, device);
+	dev->handle = rec->handle;
+
+	access_db = sdp_list_insert_sorted(access_db, dev, access_sort);
+}
+
+static sdp_list_t *record_locate(uint32_t handle)
+{
+	if (service_db) {
+		sdp_list_t *p;
+		sdp_record_t r;
+
+		r.handle = handle;
+		p = sdp_list_find(service_db, &r, record_sort);
+		return p;
+	}
+
+	SDPDBG("Could not find svcRec for : 0x%x", handle);
+	return NULL;
+}
+
+static sdp_list_t *access_locate(uint32_t handle)
+{
+	if (access_db) {
+		sdp_list_t *p;
+		sdp_access_t a;
+
+		a.handle = handle;
+		p = sdp_list_find(access_db, &a, access_sort);
+		return p;
+	}
+
+	SDPDBG("Could not find access data for : 0x%x", handle);
+	return NULL;
+}
+
+/*
+ * Given a service record handle, find the record associated with it.
+ */
+sdp_record_t *sdp_record_find(uint32_t handle)
+{
+	sdp_list_t *p = record_locate(handle);
+
+	if (!p) {
+		SDPDBG("Couldn't find record for : 0x%x", handle);
+		return 0;
+	}
+
+	return (sdp_record_t *) 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 = (sdp_record_t *) p->data;
+	if (r)
+		service_db = sdp_list_remove(service_db, r);
+
+	p = access_locate(handle);
+	if (p) {
+		a = (sdp_access_t *) p->data;
+		if (a) {
+			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 = (sdp_access_t *) 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;
+}
diff --git a/src/sdpd-request.c b/src/sdpd-request.c
new file mode 100644
index 0000000..1aa0930
--- /dev/null
+++ b/src/sdpd-request.c
@@ -0,0 +1,1070 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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 "logging.h"
+
+#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
+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 = (sdp_record_t *) 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:	
+	if (cstate)
+		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) {
+		SDPDBG("Entries in attr seq : %d", sdp_list_len(seq));
+	} else {
+		SDPDBG("NULL attribute descriptor");
+	}
+
+	if (seq == NULL) {
+		SDPDBG("Attribute sequence is NULL");
+		return 0;
+	}
+
+	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_t *)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_SYNTAX;
+		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);
+
+	/* 
+	 * 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:
+	if (cstate)
+		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_SYNTAX;
+		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 = (sdp_record_t *) 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:
+	if (cstate)
+		free(cstate);
+	if (tmpbuf.data)
+		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)
+		return;
+
+	if (sa.l2_family == AF_BLUETOOTH) { 
+		struct l2cap_options lo;
+		memset(&lo, 0, sizeof(lo));
+		size = sizeof(lo);
+		getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &lo, &size);
+		bacpy(&req.bdaddr, &sa.l2_bdaddr);
+		req.mtu = lo.omtu;
+		req.local = 0;
+		memset(&sa, 0, sizeof(sa));
+		size = sizeof(sa);
+		getsockname(sk, (struct sockaddr *) &sa, &size);
+		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
index 0000000..a377ada
--- /dev/null
+++ b/src/sdpd-server.c
@@ -0,0 +1,296 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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 <cutils/sockets.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 "logging.h"
+#include "sdpd.h"
+
+static GIOChannel *l2cap_io = NULL, *unix_io = NULL;
+
+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;
+		}
+	}
+
+	listen(l2cap_sock, 5);
+
+	if (!compat) {
+		unix_sock = -1;
+		return 0;
+	}
+#if 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;
+        }
+
+        listen(unix_sock, 5);
+
+        chmod(SDP_UNIX_PATH, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+#else
+        unix_sock = android_get_control_socket("bluetooth");
+        if (unix_sock < 0) {
+                error("Unable to get the control socket for 'bluetooth'");
+                return -1;
+        }
+
+        if (listen(unix_sock, 5)) {
+                error("Listening on local socket failed: %s", strerror(errno));
+                return -1;
+        }
+
+        info("Got Unix socket fd '%d' from environment", unix_sock);
+#endif
+
+        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)) {
+		g_io_channel_unref(chan);
+		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;
+
+	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);
+		}
+	}
+
+	l2cap_io = g_io_channel_unix_new(l2cap_sock);
+	g_io_channel_set_close_on_unref(l2cap_io, TRUE);
+
+	g_io_add_watch(l2cap_io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+					io_accept_event, &l2cap_sock);
+
+	if (compat && unix_sock > fileno(stderr)) {
+		unix_io = g_io_channel_unix_new(unix_sock);
+		g_io_channel_set_close_on_unref(unix_io, TRUE);
+
+		g_io_add_watch(unix_io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+					io_accept_event, &unix_sock);
+	}
+
+	return 0;
+}
+
+void stop_sdp_server(void)
+{
+	info("Stopping SDP server");
+
+	sdp_svcdb_reset();
+
+	if (unix_io)
+		g_io_channel_unref(unix_io);
+
+	if (l2cap_io)
+		g_io_channel_unref(l2cap_io);
+}
diff --git a/src/sdpd-service.c b/src/sdpd-service.c
new file mode 100644
index 0000000..525c12a
--- /dev/null
+++ b/src/sdpd-service.c
@@ -0,0 +1,695 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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 "logging.h"
+#include "manager.h"
+
+static sdp_record_t *server = NULL;
+
+static uint8_t service_classes = 0x00;
+
+static uint16_t did_vendor = 0x0000;
+static uint16_t did_product = 0x0000;
+static uint16_t did_version = 0x0000;
+
+/*
+ * 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.
+ */
+static 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);
+}
+
+static void update_svclass_list(const bdaddr_t *src)
+{
+	sdp_list_t *list = sdp_get_record_list();
+	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);
+
+	service_classes = val;
+
+	manager_update_svc(src, val);
+}
+
+uint8_t get_service_classes(const bdaddr_t *bdaddr)
+{
+	return service_classes;
+}
+
+void create_ext_inquiry_response(const char *name, uint8_t *data)
+{
+	sdp_list_t *list = sdp_get_record_list();
+	uint8_t *ptr = data;
+	uint16_t uuid[24];
+	int i, index = 0;
+
+	if (name) {
+		int len = strlen(name);
+
+		if (len > 48) {
+			len = 48;
+			ptr[1] = 0x08;
+		} else
+			ptr[1] = 0x09;
+
+		ptr[0] = len + 1;
+
+		memcpy(ptr + 2, name, len);
+
+		ptr += len + 2;
+	}
+
+	if (did_vendor != 0x0000) {
+		uint16_t source = 0x0002;
+		*ptr++ = 9;
+		*ptr++ = 11;
+		*ptr++ = (source & 0x00ff);
+		*ptr++ = (source & 0xff00) >> 8;
+		*ptr++ = (did_vendor & 0x00ff);
+		*ptr++ = (did_vendor & 0xff00) >> 8;
+		*ptr++ = (did_product & 0x00ff);
+		*ptr++ = (did_product & 0xff00) >> 8;
+		*ptr++ = (did_version & 0x00ff);
+		*ptr++ = (did_version & 0xff00) >> 8;
+	}
+
+	ptr[1] = 0x03;
+
+	for (; list; list = list->next) {
+		sdp_record_t *rec = (sdp_record_t *) list->data;
+
+		if (rec->svclass.type != SDP_UUID16)
+			continue;
+
+		if (rec->svclass.value.uuid16 < 0x1100)
+			continue;
+
+		if (index > 23) {
+			ptr[1] = 0x02;
+			break;
+		}
+
+		for (i = 0; i < index; i++)
+			if (uuid[i] == rec->svclass.value.uuid16)
+				break;
+
+		if (i == index - 1)
+			continue;
+
+		uuid[index++] = rec->svclass.value.uuid16;
+	}
+
+	if (index > 0) {
+		ptr[0] = (index * 2) + 1;
+		ptr += 2;
+
+		for (i = 0; i < index; i++) {
+			*ptr++ = (uuid[i] & 0x00ff);
+			*ptr++ = (uuid[i] & 0xff00) >> 8;
+		}
+	}
+}
+
+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 = (void **)malloc(sdpServerVnumEntries * sizeof(void *));
+	versionDTDs = (void **)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();
+	update_svclass_list(BDADDR_ANY);
+}
+
+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);
+
+	did_vendor = vendor;
+	did_product = product;
+	did_version = 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();
+	update_svclass_list(BDADDR_ANY);
+}
+
+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 -1;
+	} else {
+		if (sdp_record_find(rec->handle))
+			return -1;
+	}
+
+	debug("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));
+		debug("Record pattern UUID %s", uuid);
+	}
+
+	update_db_timestamp();
+	update_svclass_list(src);
+
+	return 0;
+}
+
+int remove_record_from_server(uint32_t handle)
+{
+	sdp_record_t *rec;
+
+	debug("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();
+		update_svclass_list(BDADDR_ANY);
+	}
+
+	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();
+	update_svclass_list(BDADDR_ANY);
+
+	/* 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();
+	update_svclass_list(BDADDR_ANY);
+
+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();
+			update_svclass_list(BDADDR_ANY);
+		}
+	} 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
index 0000000..6dadc6b
--- /dev/null
+++ b/src/sdpd.h
@@ -0,0 +1,98 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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
+
+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);
+
+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))
+
+sdp_buf_t *sdp_get_cached_rsp(sdp_cont_state_t *cstate);
+void sdp_cstate_cache_init(void);
+void sdp_cstate_clean_buf(void);
+
+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);
+
+uint8_t get_service_classes(const bdaddr_t *bdaddr);
+void create_ext_inquiry_response(const char *name, uint8_t *data);
+static inline int android_get_control_socket(const char *name);
diff --git a/src/security.c b/src/security.c
new file mode 100644
index 0000000..646921b
--- /dev/null
+++ b/src/security.c
@@ -0,0 +1,1171 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/time.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/sdp.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include "hcid.h"
+#include "logging.h"
+#include "textfile.h"
+
+#include "adapter.h"
+#include "dbus-hci.h"
+#include "storage.h"
+#include "manager.h"
+
+typedef enum {
+	REQ_PENDING,
+	REQ_SENT
+} req_status_t;
+
+struct hci_req_data {
+	int dev_id;
+	int event;
+	req_status_t status;
+	bdaddr_t dba;
+	uint16_t ogf;
+	uint16_t ocf;
+	void *cparam;
+	int clen;
+};
+
+struct g_io_info {
+	GIOChannel	*channel;
+	int		watch_id;
+	int		pin_length;
+};
+
+static struct g_io_info io_data[HCI_MAX_DEV];
+
+static GSList *hci_req_queue = NULL;
+
+static struct hci_req_data *hci_req_data_new(int dev_id, const bdaddr_t *dba,
+					uint16_t ogf, uint16_t ocf, int event,
+					const void *cparam, int clen)
+{
+	struct hci_req_data *data;
+
+	data = g_new0(struct hci_req_data, 1);
+
+	data->cparam = g_malloc(clen);
+	memcpy(data->cparam, cparam, clen);
+
+	bacpy(&data->dba, dba);
+
+	data->dev_id = dev_id;
+	data->status = REQ_PENDING;
+	data->ogf    = ogf;
+	data->ocf    = ocf;
+	data->event  = event;
+	data->clen   = clen;
+
+	return data;
+}
+
+static int hci_req_find_by_devid(const void *data, const void *user_data)
+{
+	const struct hci_req_data *req = data;
+	const int *dev_id = user_data;
+
+	return (*dev_id - req->dev_id);
+}
+
+static void hci_req_queue_process(int dev_id)
+{
+	int dd, ret_val;
+
+	/* send the next pending cmd */
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		error("hci_open_dev(%d): %s (%d)", dev_id, strerror(errno),
+									errno);
+		return;
+	}
+
+	do {
+		struct hci_req_data *data;
+		GSList *l = g_slist_find_custom(hci_req_queue, &dev_id, hci_req_find_by_devid);
+
+		if (!l)
+			break;
+
+		data = l->data;
+		data->status = REQ_SENT;
+
+		ret_val = hci_send_cmd(dd, data->ogf, data->ocf, data->clen, data->cparam);
+		if (ret_val < 0) {
+			hci_req_queue = g_slist_remove(hci_req_queue, data);
+			g_free(data->cparam);
+			g_free(data);
+		}
+
+	} while (ret_val < 0);
+
+	hci_close_dev(dd);
+}
+
+static void hci_req_queue_append(struct hci_req_data *data)
+{
+	GSList *l;
+	struct hci_req_data *match;
+
+
+	hci_req_queue = g_slist_append(hci_req_queue, data);
+
+	l = g_slist_find_custom(hci_req_queue, &data->dev_id, hci_req_find_by_devid);
+	match = l->data;
+
+	if (match->status == REQ_SENT)
+		return;
+
+	hci_req_queue_process(data->dev_id);
+}
+
+void hci_req_queue_remove(int dev_id, bdaddr_t *dba)
+{
+	GSList *cur, *next;
+	struct hci_req_data *req;
+
+	for (cur = hci_req_queue; cur != NULL; cur = next) {
+		req = cur->data;
+		next = cur->next;
+		if ((req->dev_id != dev_id) || (bacmp(&req->dba, dba)))
+			continue;
+
+		hci_req_queue = g_slist_remove(hci_req_queue, req);
+		g_free(req->cparam);
+		g_free(req);
+	}
+}
+
+static void check_pending_hci_req(int dev_id, int event)
+{
+	struct hci_req_data *data;
+	GSList *l;
+
+	if (!hci_req_queue)
+		return;
+
+	/* find the first element(pending)*/
+	l = g_slist_find_custom(hci_req_queue, &dev_id, hci_req_find_by_devid);
+
+	if (!l)
+		return;
+
+	data = l->data;
+
+	/* skip if there is pending confirmation */
+	if (data->status == REQ_SENT) {
+		if (data->event != event)
+			return;
+
+		/* remove the confirmed cmd */
+		hci_req_queue = g_slist_remove(hci_req_queue, data);
+		g_free(data->cparam);
+		g_free(data);
+	}
+
+	hci_req_queue_process(dev_id);
+}
+
+static int get_handle(int dev, bdaddr_t *sba, bdaddr_t *dba, uint16_t *handle)
+{
+	struct hci_conn_list_req *cl;
+	struct hci_conn_info *ci;
+	char addr[18];
+	int i;
+
+	cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
+
+	ba2str(sba, addr);
+	cl->dev_id = hci_devid(addr);
+	cl->conn_num = 10;
+	ci = cl->conn_info;
+
+	if (ioctl(dev, HCIGETCONNLIST, (void *) cl) < 0) {
+		g_free(cl);
+		return -EIO;
+	}
+
+	for (i = 0; i < cl->conn_num; i++, ci++) {
+		if (bacmp(&ci->bdaddr, dba) == 0) {
+			*handle = ci->handle;
+			g_free(cl);
+			return 0;
+		}
+	}
+
+	g_free(cl);
+
+	return -ENOENT;
+}
+
+static inline int get_bdaddr(int dev, bdaddr_t *sba, uint16_t handle, bdaddr_t *dba)
+{
+	struct hci_conn_list_req *cl;
+	struct hci_conn_info *ci;
+	char addr[18];
+	int i;
+
+	cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
+
+	ba2str(sba, addr);
+	cl->dev_id = hci_devid(addr);
+	cl->conn_num = 10;
+	ci = cl->conn_info;
+
+	if (ioctl(dev, HCIGETCONNLIST, (void *) cl) < 0) {
+		g_free(cl);
+		return -EIO;
+	}
+
+	for (i = 0; i < cl->conn_num; i++, ci++)
+		if (ci->handle == handle) {
+			bacpy(dba, &ci->bdaddr);
+			g_free(cl);
+			return 0;
+		}
+
+	g_free(cl);
+
+	return -ENOENT;
+}
+
+static inline 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 inline 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);
+}
+
+/* Link Key handling */
+
+static void link_key_request(int dev, bdaddr_t *sba, bdaddr_t *dba)
+{
+	struct hci_auth_info_req req;
+	unsigned char key[16];
+	char sa[18], da[18];
+	uint8_t type;
+	int err;
+
+	ba2str(sba, sa); ba2str(dba, da);
+	info("link_key_request (sba=%s, dba=%s)", sa, da);
+
+	memset(&req, 0, sizeof(req));
+	bacpy(&req.bdaddr, dba);
+
+	err = ioctl(dev, HCIGETAUTHINFO, (unsigned long) &req);
+	if (err < 0) {
+		if (errno != EINVAL)
+			debug("HCIGETAUTHINFO failed %s (%d)",
+						strerror(errno), errno);
+		req.type = 0x00;
+	}
+
+	debug("kernel auth requirements = 0x%02x", req.type);
+
+	err = read_link_key(sba, dba, key, &type);
+	if (err < 0) {
+		/* Link key not found */
+		hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY, 6, dba);
+	} else {
+		/* Link key found */
+		link_key_reply_cp lr;
+		memcpy(lr.link_key, key, 16);
+		bacpy(&lr.bdaddr, dba);
+
+		debug("stored link key type = 0x%02x", type);
+
+		/* Don't use debug link keys (0x03) and also don't use
+		 * unauthenticated combination keys if MITM is required */
+		if (type == 0x03 || (type == 0x04 && req.type != 0xff &&
+							(req.type & 0x01)))
+			hci_send_cmd(dev, OGF_LINK_CTL,
+					OCF_LINK_KEY_NEG_REPLY, 6, dba);
+		else
+			hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_REPLY,
+						LINK_KEY_REPLY_CP_SIZE, &lr);
+	}
+}
+
+static void link_key_notify(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_link_key_notify *evt = ptr;
+	bdaddr_t *dba = &evt->bdaddr;
+	char sa[18], da[18];
+	int dev_id, err;
+	unsigned char old_key[16];
+	uint8_t old_key_type;
+
+	ba2str(sba, sa); ba2str(dba, da);
+	info("link_key_notify (sba=%s, dba=%s, type=%d)", sa, da,
+							evt->key_type);
+
+	err = read_link_key(sba, dba, old_key, &old_key_type);
+	if (err < 0)
+		old_key_type = 0xff;
+
+	dev_id = hci_devid(sa);
+	if (dev_id < 0)
+		err = -errno;
+	else
+		err = hcid_dbus_link_key_notify(sba, dba, evt->link_key,
+						evt->key_type,
+						io_data[dev_id].pin_length,
+						old_key_type);
+	if (err < 0) {
+		uint16_t handle;
+
+		if (err == -ENODEV)
+			hcid_dbus_bonding_process_complete(sba, dba,
+							HCI_OE_LOW_RESOURCES);
+		else
+			hcid_dbus_bonding_process_complete(sba, dba,
+							HCI_MEMORY_FULL);
+
+		if (get_handle(dev, sba, dba, &handle) == 0) {
+			disconnect_cp cp;
+
+			memset(&cp, 0, sizeof(cp));
+			cp.handle = htobs(handle);
+			cp.reason = HCI_OE_LOW_RESOURCES;
+
+			hci_send_cmd(dev, OGF_LINK_CTL, OCF_DISCONNECT,
+						DISCONNECT_CP_SIZE, &cp);
+		}
+	}
+
+	io_data[dev_id].pin_length = -1;
+}
+
+static void return_link_keys(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_return_link_keys *evt = ptr;
+	uint8_t num = evt->num_keys;
+	unsigned char key[16];
+	char sa[18], da[18];
+	bdaddr_t dba;
+	int i;
+
+	ba2str(sba, sa);
+	ptr++;
+
+	for (i = 0; i < num; i++) {
+		bacpy(&dba, ptr); ba2str(&dba, da);
+		memcpy(key, ptr + 6, 16);
+
+		info("return_link_keys (sba=%s, dba=%s)", sa, da);
+
+		ptr += 22;
+	}
+}
+
+/* Simple Pairing handling */
+
+static void user_confirm_request(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_user_confirm_request *req = ptr;
+
+	if (hcid_dbus_user_confirm(sba, &req->bdaddr,
+					btohl(req->passkey)) < 0)
+		hci_send_cmd(dev, OGF_LINK_CTL,
+				OCF_USER_CONFIRM_NEG_REPLY, 6, ptr);
+}
+
+static void user_passkey_request(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_user_passkey_request *req = ptr;
+
+	if (hcid_dbus_user_passkey(sba, &req->bdaddr) < 0)
+		hci_send_cmd(dev, OGF_LINK_CTL,
+				OCF_USER_PASSKEY_NEG_REPLY, 6, ptr);
+}
+
+static void user_passkey_notify(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_user_passkey_notify *req = ptr;
+
+	hcid_dbus_user_notify(sba, &req->bdaddr, btohl(req->passkey));
+}
+
+static void remote_oob_data_request(int dev, bdaddr_t *sba, void *ptr)
+{
+	hci_send_cmd(dev, OGF_LINK_CTL, OCF_REMOTE_OOB_DATA_NEG_REPLY, 6, ptr);
+}
+
+static void io_capa_request(int dev, bdaddr_t *sba, bdaddr_t *dba)
+{
+	char sa[18], da[18];
+	uint8_t cap, auth;
+
+	ba2str(sba, sa); ba2str(dba, da);
+	info("io_capa_request (sba=%s, dba=%s)", sa, da);
+
+	if (hcid_dbus_get_io_cap(sba, dba, &cap, &auth) < 0) {
+		io_capability_neg_reply_cp cp;
+		memset(&cp, 0, sizeof(cp));
+		bacpy(&cp.bdaddr, dba);
+		cp.reason = HCI_PAIRING_NOT_ALLOWED;
+		hci_send_cmd(dev, 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, OGF_LINK_CTL, OCF_IO_CAPABILITY_REPLY,
+					IO_CAPABILITY_REPLY_CP_SIZE, &cp);
+	}
+}
+
+static void io_capa_response(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_io_capability_response *evt = ptr;
+	char sa[18], da[18];
+
+	ba2str(sba, sa); ba2str(&evt->bdaddr, da);
+	info("io_capa_response (sba=%s, dba=%s)", sa, da);
+
+	hcid_dbus_set_io_cap(sba, &evt->bdaddr,
+				evt->capability, evt->authentication);
+}
+
+/* PIN code handling */
+
+void set_pin_length(bdaddr_t *sba, int length)
+{
+	char addr[18];
+	int dev_id;
+
+	ba2str(sba, addr);
+	dev_id = hci_devid(addr);
+
+	if (dev_id >= 0)
+		io_data[dev_id].pin_length = length;
+}
+
+static void pin_code_request(int dev, bdaddr_t *sba, bdaddr_t *dba)
+{
+	pin_code_reply_cp pr;
+	struct hci_conn_info_req *cr;
+	struct hci_conn_info *ci;
+	char sa[18], da[18], pin[17];
+	int pinlen;
+
+	memset(&pr, 0, sizeof(pr));
+	bacpy(&pr.bdaddr, dba);
+
+	ba2str(sba, sa); ba2str(dba, da);
+	info("pin_code_request (sba=%s, dba=%s)", sa, da);
+
+	cr = g_malloc0(sizeof(*cr) + sizeof(*ci));
+
+	bacpy(&cr->bdaddr, dba);
+	cr->type = ACL_LINK;
+	if (ioctl(dev, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+		error("Can't get conn info: %s (%d)", strerror(errno), errno);
+		goto reject;
+	}
+	ci = cr->conn_info;
+
+	memset(pin, 0, sizeof(pin));
+	pinlen = read_pin_code(sba, dba, pin);
+
+	if (pinlen > 0) {
+		set_pin_length(sba, pinlen);
+		memcpy(pr.pin_code, pin, pinlen);
+		pr.pin_len = pinlen;
+		hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY,
+				PIN_CODE_REPLY_CP_SIZE, &pr);
+	} else {
+		/* Request PIN from passkey agent */
+		if (hcid_dbus_request_pin(dev, sba, ci) < 0)
+			goto reject;
+	}
+
+	g_free(cr);
+
+	return;
+
+reject:
+	g_free(cr);
+
+	hci_send_cmd(dev, 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);
+
+	/* Disable name resolution for non D-Bus clients */
+	if (!adapter_has_discov_sessions(adapter))
+		state &= ~RESOLVE_NAME;
+
+	if (periodic) {
+		state |= PERIODIC_INQUIRY;
+		adapter_set_state(adapter, state);
+		return;
+	}
+
+	state |= STD_INQUIRY;
+	adapter_set_state(adapter, state);
+
+	/*
+	 * Cancel pending remote name request and clean the device list
+	 * when inquiry is supported in periodic inquiry idle state.
+	 */
+	if (adapter_get_state(adapter) & PERIODIC_INQUIRY) {
+		pending_remote_name_cancel(adapter);
+
+		clear_found_devices_list(adapter);
+	}
+}
+
+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;
+	}
+
+	/*
+	 * The following scenarios can happen:
+	 * 1. standard inquiry: always send discovery completed signal
+	 * 2. standard inquiry + name resolving: send discovery completed
+	 *    after name resolving
+	 * 3. periodic inquiry: skip discovery completed signal
+	 * 4. periodic inquiry + standard inquiry: always send discovery
+	 *    completed signal
+	 *
+	 * Keep in mind that non D-Bus requests can arrive.
+	 */
+	if (periodic) {
+		state = adapter_get_state(adapter);
+		state &= ~PERIODIC_INQUIRY;
+		adapter_set_state(adapter, state);
+		return;
+	}
+
+	if (adapter_resolve_names(adapter) == 0)
+		return;
+
+	state = adapter_get_state(adapter);
+	/*
+	 * workaround to identify situation when there is no devices around
+	 * but periodic inquiry is active.
+	 */
+	if (!(state & STD_INQUIRY) && !(state & PERIODIC_INQUIRY)) {
+		state |= PERIODIC_INQUIRY;
+		adapter_set_state(adapter, state);
+		return;
+	}
+
+	/* reset the discover type to be able to handle D-Bus and non D-Bus
+	 * requests */
+	state &= ~STD_INQUIRY;
+	state &= ~PERIODIC_INQUIRY;
+	adapter_set_state(adapter, state);
+}
+
+static inline void cmd_status(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_cmd_status *evt = ptr;
+	uint16_t opcode = btohs(evt->opcode);
+
+	if (opcode == cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY))
+		start_inquiry(sba, evt->status, FALSE);
+}
+
+static inline void cmd_complete(int dev, bdaddr_t *sba, void *ptr)
+{
+	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_LINK_CTL, OCF_PERIODIC_INQUIRY):
+		start_inquiry(sba, status, TRUE);
+		break;
+	case cmd_opcode_pack(OGF_LINK_CTL, OCF_EXIT_PERIODIC_INQUIRY):
+		inquiry_complete(sba, status, TRUE);
+		break;
+	case cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY_CANCEL):
+		inquiry_complete(sba, status, FALSE);
+		break;
+	case cmd_opcode_pack(OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME):
+		adapter_setname_complete(sba, status);
+		break;
+	case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE):
+		hcid_dbus_setscan_enable_complete(sba);
+		break;
+	case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV):
+		hcid_dbus_write_class_complete(sba);
+		break;
+	case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SIMPLE_PAIRING_MODE):
+		hcid_dbus_write_simple_pairing_mode_complete(sba);
+		break;
+	case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_LOCAL_NAME):
+		ptr += sizeof(evt_cmd_complete);
+		adapter_update_local_name(sba, status, ptr);
+		break;
+	};
+}
+
+static inline void remote_name_information(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_remote_name_req_complete *evt = ptr;
+	bdaddr_t dba;
+	char name[MAX_NAME_LENGTH + 1];
+
+	memset(name, 0, sizeof(name));
+	bacpy(&dba, &evt->bdaddr);
+
+	if (!evt->status) {
+		char *end;
+		memcpy(name, evt->name, MAX_NAME_LENGTH);
+		/* 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(sba, &dba, name);
+	}
+
+	hcid_dbus_remote_name(sba, &dba, evt->status, name);
+}
+
+static inline void remote_version_information(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_read_remote_version_complete *evt = ptr;
+	bdaddr_t dba;
+
+	if (evt->status)
+		return;
+
+	if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0)
+		return;
+
+	write_version_info(sba, &dba, btohs(evt->manufacturer),
+				evt->lmp_ver, btohs(evt->lmp_subver));
+}
+
+static inline void inquiry_result(int dev, bdaddr_t *sba, int plen, void *ptr)
+{
+	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);
+
+		hcid_dbus_inquiry_result(sba, &info->bdaddr, class, 0, NULL);
+
+		update_lastseen(sba, &info->bdaddr);
+
+		ptr += INQUIRY_INFO_SIZE;
+	}
+}
+
+static inline void inquiry_result_with_rssi(int dev, bdaddr_t *sba, int plen, void *ptr)
+{
+	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);
+
+			hcid_dbus_inquiry_result(sba, &info->bdaddr,
+						class, info->rssi, NULL);
+
+			update_lastseen(sba, &info->bdaddr);
+
+			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);
+
+			hcid_dbus_inquiry_result(sba, &info->bdaddr,
+						class, info->rssi, NULL);
+
+			update_lastseen(sba, &info->bdaddr);
+
+			ptr += INQUIRY_INFO_WITH_RSSI_SIZE;
+		}
+	}
+}
+
+static inline void extended_inquiry_result(int dev, bdaddr_t *sba, int plen, void *ptr)
+{
+	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);
+
+		hcid_dbus_inquiry_result(sba, &info->bdaddr, class,
+						info->rssi, info->data);
+
+		update_lastseen(sba, &info->bdaddr);
+
+		ptr += EXTENDED_INQUIRY_INFO_SIZE;
+	}
+}
+
+static inline void remote_features_information(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_read_remote_features_complete *evt = ptr;
+	bdaddr_t dba;
+
+	if (evt->status)
+		return;
+
+	if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0)
+		return;
+
+	write_features_info(sba, &dba, evt->features);
+}
+
+static inline void conn_complete(int dev, int dev_id, bdaddr_t *sba, void *ptr)
+{
+	evt_conn_complete *evt = ptr;
+	char filename[PATH_MAX];
+	remote_name_req_cp cp_name;
+	struct hci_req_data *data;
+	char local_addr[18], peer_addr[18], *str;
+
+	if (evt->link_type != ACL_LINK)
+		return;
+
+	hcid_dbus_conn_complete(sba, evt->status, btohs(evt->handle),
+				&evt->bdaddr);
+
+	if (evt->status)
+		return;
+
+	update_lastused(sba, &evt->bdaddr);
+
+	/* Request remote name */
+	memset(&cp_name, 0, sizeof(cp_name));
+	bacpy(&cp_name.bdaddr, &evt->bdaddr);
+	cp_name.pscan_rep_mode = 0x02;
+
+	data = hci_req_data_new(dev_id, &evt->bdaddr, OGF_LINK_CTL,
+				OCF_REMOTE_NAME_REQ, EVT_REMOTE_NAME_REQ_COMPLETE,
+				&cp_name, REMOTE_NAME_REQ_CP_SIZE);
+
+	hci_req_queue_append(data);
+
+	/* check if the remote version needs be requested */
+	ba2str(sba, local_addr);
+	ba2str(&evt->bdaddr, peer_addr);
+
+	create_name(filename, sizeof(filename), STORAGEDIR, local_addr, "manufacturers");
+
+	str = textfile_get(filename, peer_addr);
+	if (!str) {
+		read_remote_version_cp cp;
+
+		memset(&cp, 0, sizeof(cp));
+		cp.handle = evt->handle;
+
+		data = hci_req_data_new(dev_id, &evt->bdaddr, OGF_LINK_CTL,
+					OCF_READ_REMOTE_VERSION, EVT_READ_REMOTE_VERSION_COMPLETE,
+					&cp, READ_REMOTE_VERSION_CP_SIZE);
+
+		hci_req_queue_append(data);
+	} else
+		free(str);
+}
+
+static inline void disconn_complete(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_disconn_complete *evt = ptr;
+
+	hcid_dbus_disconn_complete(sba, evt->status, btohs(evt->handle),
+					evt->reason);
+}
+
+static inline void auth_complete(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_auth_complete *evt = ptr;
+	bdaddr_t dba;
+
+	if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0)
+		return;
+
+	hcid_dbus_bonding_process_complete(sba, &dba, evt->status);
+}
+
+static inline void simple_pairing_complete(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_simple_pairing_complete *evt = ptr;
+
+	hcid_dbus_simple_pairing_complete(sba, &evt->bdaddr, evt->status);
+}
+
+static inline void conn_request(int dev, bdaddr_t *sba, void *ptr)
+{
+	evt_conn_request *evt = ptr;
+	uint32_t class = evt->dev_class[0] | (evt->dev_class[1] << 8)
+				| (evt->dev_class[2] << 16);
+
+	hcid_dbus_remote_class(sba, &evt->bdaddr, class);
+
+	write_remote_class(sba, &evt->bdaddr, class);
+}
+
+static void delete_channel(GIOChannel *chan)
+{
+	int i;
+
+	/* Look for the GIOChannel in the table */
+	for (i = 0; i < HCI_MAX_DEV; i++)
+		if (io_data[i].channel == chan) {
+			stop_security_manager(i);
+			return;
+		}
+
+	error("IO channel not found in the io_data table");
+}
+
+static gboolean io_security_event(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+	unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr = buf;
+	struct hci_dev_info *di = data;
+	int type, dev;
+	size_t len;
+	hci_event_hdr *eh;
+	GIOError err;
+	evt_cmd_status *evt;
+
+	if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) {
+		delete_channel(chan);
+		return FALSE;
+	}
+
+	if ((err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len))) {
+		if (err == G_IO_ERROR_AGAIN)
+			return TRUE;
+		delete_channel(chan);
+		return FALSE;
+	}
+
+	type = *ptr++;
+
+	if (type != HCI_EVENT_PKT)
+		return TRUE;
+
+	eh = (hci_event_hdr *) ptr;
+	ptr += HCI_EVENT_HDR_SIZE;
+
+	dev = g_io_channel_unix_get_fd(chan);
+
+	ioctl(dev, HCIGETDEVINFO, (void *) di);
+
+	if (hci_test_bit(HCI_RAW, &di->flags))
+		return TRUE;
+
+	switch (eh->evt) {
+	case EVT_CMD_STATUS:
+		cmd_status(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_CMD_COMPLETE:
+		cmd_complete(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_REMOTE_NAME_REQ_COMPLETE:
+		remote_name_information(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_READ_REMOTE_VERSION_COMPLETE:
+		remote_version_information(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_READ_REMOTE_FEATURES_COMPLETE:
+		remote_features_information(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_INQUIRY_COMPLETE:
+		evt = (evt_cmd_status *) ptr;
+		inquiry_complete(&di->bdaddr, evt->status, FALSE);
+		break;
+
+	case EVT_INQUIRY_RESULT:
+		inquiry_result(dev, &di->bdaddr, eh->plen, ptr);
+		break;
+
+	case EVT_INQUIRY_RESULT_WITH_RSSI:
+		inquiry_result_with_rssi(dev, &di->bdaddr, eh->plen, ptr);
+		break;
+
+	case EVT_EXTENDED_INQUIRY_RESULT:
+		extended_inquiry_result(dev, &di->bdaddr, eh->plen, ptr);
+		break;
+
+	case EVT_CONN_COMPLETE:
+		conn_complete(dev, di->dev_id, &di->bdaddr, ptr);
+		break;
+
+	case EVT_DISCONN_COMPLETE:
+		disconn_complete(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_AUTH_COMPLETE:
+		auth_complete(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_SIMPLE_PAIRING_COMPLETE:
+		simple_pairing_complete(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_CONN_REQUEST:
+		conn_request(dev, &di->bdaddr, ptr);
+		break;
+	}
+
+	/* Check for pending command request */
+	check_pending_hci_req(di->dev_id, eh->evt);
+
+	switch (eh->evt) {
+	case EVT_PIN_CODE_REQ:
+		pin_code_request(dev, &di->bdaddr, (bdaddr_t *) ptr);
+		break;
+
+	case EVT_LINK_KEY_REQ:
+		link_key_request(dev, &di->bdaddr, (bdaddr_t *) ptr);
+		break;
+
+	case EVT_LINK_KEY_NOTIFY:
+		link_key_notify(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_RETURN_LINK_KEYS:
+		return_link_keys(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_IO_CAPABILITY_REQUEST:
+		io_capa_request(dev, &di->bdaddr, (bdaddr_t *) ptr);
+		break;
+
+	case EVT_IO_CAPABILITY_RESPONSE:
+		io_capa_response(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_USER_CONFIRM_REQUEST:
+		user_confirm_request(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_USER_PASSKEY_REQUEST:
+		user_passkey_request(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_USER_PASSKEY_NOTIFY:
+		user_passkey_notify(dev, &di->bdaddr, ptr);
+		break;
+
+	case EVT_REMOTE_OOB_DATA_REQUEST:
+		remote_oob_data_request(dev, &di->bdaddr, ptr);
+		break;
+	}
+
+	return TRUE;
+}
+
+void start_security_manager(int hdev)
+{
+	GIOChannel *chan = io_data[hdev].channel;
+	struct hci_dev_info *di;
+	struct hci_filter flt;
+	read_stored_link_key_cp cp;
+	int dev;
+
+	if (chan)
+		return;
+
+	info("Starting security manager %d", hdev);
+
+	if ((dev = hci_open_dev(hdev)) < 0) {
+		error("Can't open device hci%d: %s (%d)",
+						hdev, strerror(errno), errno);
+		return;
+	}
+
+	/* 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);
+	if (setsockopt(dev, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
+		error("Can't set filter on hci%d: %s (%d)",
+						hdev, strerror(errno), errno);
+		close(dev);
+		return;
+	}
+
+	di = g_new(struct hci_dev_info, 1);
+	if (hci_devinfo(hdev, di) < 0) {
+		error("Can't get device info: %s (%d)",
+							strerror(errno), errno);
+		close(dev);
+		g_free(di);
+		return;
+	}
+
+	chan = g_io_channel_unix_new(dev);
+	g_io_channel_set_close_on_unref(chan, TRUE);
+	io_data[hdev].watch_id = g_io_add_watch_full(chan, G_PRIORITY_HIGH,
+						G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+						io_security_event, di, (GDestroyNotify) g_free);
+	io_data[hdev].channel = chan;
+	io_data[hdev].pin_length = -1;
+
+	if (hci_test_bit(HCI_RAW, &di->flags))
+		return;
+
+	bacpy(&cp.bdaddr, BDADDR_ANY);
+	cp.read_all = 1;
+
+	hci_send_cmd(dev, OGF_HOST_CTL, OCF_READ_STORED_LINK_KEY,
+			READ_STORED_LINK_KEY_CP_SIZE, (void *) &cp);
+}
+
+void stop_security_manager(int hdev)
+{
+	GIOChannel *chan = io_data[hdev].channel;
+
+	if (!chan)
+		return;
+
+	info("Stopping security manager %d", hdev);
+
+	g_source_remove(io_data[hdev].watch_id);
+	g_io_channel_unref(io_data[hdev].channel);
+	io_data[hdev].watch_id = -1;
+	io_data[hdev].channel = NULL;
+	io_data[hdev].pin_length = -1;
+}
+
diff --git a/src/storage.c b/src/storage.c
new file mode 100644
index 0000000..e2d7632
--- /dev/null
+++ b/src/storage.c
@@ -0,0 +1,1143 @@
+/*
+ *
+ *  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 <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 "glib-helper.h"
+#include "storage.h"
+
+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;
+}
+
+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, 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_l2cap_info(bdaddr_t *local, bdaddr_t *peer,
+			uint16_t mtu_result, uint16_t mtu,
+			uint16_t mask_result, uint32_t mask)
+{
+	char filename[PATH_MAX + 1], addr[18], str[18];
+
+	if (mask_result)
+		snprintf(str, sizeof(str), "%d -1", mtu_result ? -1 : mtu);
+	else
+		snprintf(str, sizeof(str), "%d 0x%08x", mtu_result ? -1 : mtu, mask);
+
+	create_filename(filename, PATH_MAX, local, "l2cap");
+
+	create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+	ba2str(peer, addr);
+	return textfile_put(filename, addr, str);
+}
+
+int read_l2cap_info(bdaddr_t *local, bdaddr_t *peer,
+			uint16_t *mtu_result, uint16_t *mtu,
+			uint16_t *mask_result, uint32_t *mask)
+{
+	char filename[PATH_MAX + 1], addr[18], *str, *space, *msk;
+
+	create_filename(filename, PATH_MAX, local, "l2cap");
+
+	ba2str(peer, addr);
+	str = textfile_get(filename, addr);
+	if (!str)
+		return -ENOENT;
+
+	space = strchr(str, ' ');
+	if (!space) {
+		free(str);
+		return -ENOENT;
+	}
+
+	msk = space + 1;
+	*space = '\0';
+
+	if (mtu_result && mtu) {
+		if (str[0] == '-')
+			*mtu_result = 0x0001;
+		else {
+			*mtu_result = 0;
+			*mtu = (uint16_t) strtol(str, NULL, 0);
+		}
+	}
+
+	if (mask_result && mask) {
+		if (msk[0] == '-')
+			*mask_result = 0x0001;
+		else {
+			*mask_result = 0;
+			*mask = (uint32_t) strtol(msk, NULL, 16);
+		}
+	}
+
+	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 *features)
+{
+	char filename[PATH_MAX + 1], addr[18], str[17];
+	int i;
+
+	memset(str, 0, sizeof(str));
+	for (i = 0; i < 8; i++)
+		sprintf(str + (i * 2), "%2.2X", features[i]);
+
+	create_filename(filename, PATH_MAX, local, "features");
+
+	create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+	ba2str(peer, addr);
+	return textfile_put(filename, addr, str);
+}
+
+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;
+
+	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;
+}
+
+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;
+}
+
+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);
+		if (str)
+			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);
+
+	if (str)
+		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(bdaddr_t *src, bdaddr_t *dst)
+{
+	sdp_list_t *records, *seq;
+	sdp_record_t *rec;
+	char srcaddr[18], dstaddr[18];
+
+	ba2str(src, srcaddr);
+	ba2str(dst, dstaddr);
+
+	records = read_records(src, dst);
+
+	for (seq = records; seq; seq = seq->next) {
+		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(bdaddr_t *src, 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;
+}
diff --git a/src/storage.h b/src/storage.h
new file mode 100644
index 0000000..de0e837
--- /dev/null
+++ b/src/storage.h
@@ -0,0 +1,79 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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
+ *
+ */
+
+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, 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_l2cap_info(bdaddr_t *local, bdaddr_t *peer,
+			uint16_t mtu_result, uint16_t mtu,
+			uint16_t mask_result, uint32_t mask);
+int read_l2cap_info(bdaddr_t *local, bdaddr_t *peer,
+			uint16_t *mtu_result, uint16_t *mtu,
+			uint16_t *mask_result, uint32_t *mask);
+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 *features);
+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_length(bdaddr_t *local, bdaddr_t *peer);
+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(bdaddr_t *src, bdaddr_t *dst);
+sdp_list_t *read_records(bdaddr_t *src, 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);
+
+#define PNP_UUID		"00001200-0000-1000-8000-00805f9b34fb"
+
diff --git a/test/Android.mk b/test/Android.mk
new file mode 100755
index 0000000..68006aa
--- /dev/null
+++ b/test/Android.mk
@@ -0,0 +1,328 @@
+LOCAL_PATH:= $(call my-dir)
+BUILD_BTIOTEST:=0
+BUILD_HCIEMU:=0
+
+#
+# hstest
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	hstest.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=hstest
+
+include $(BUILD_EXECUTABLE)
+
+#
+# l2test
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	l2test.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=l2test
+
+include $(BUILD_EXECUTABLE)
+
+#
+# rctest
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	rctest.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=rctest
+
+include $(BUILD_EXECUTABLE)
+
+
+#
+# scotest
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	scotest.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=scotest
+
+include $(BUILD_EXECUTABLE)
+
+#
+# agent
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	agent.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+	$(call include-path-for, dbus)
+
+LOCAL_SHARED_LIBRARIES := \
+	libdbus
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=agent
+
+include $(BUILD_EXECUTABLE)
+
+#
+# attest
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	attest.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=attest
+
+include $(BUILD_EXECUTABLE)
+
+#
+# avtest
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	avtest.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=avtest
+
+include $(BUILD_EXECUTABLE)
+
+#
+# bdaddr
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	bdaddr.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_STATIC_LIBRARIES := \
+	libbluez-common-static
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=bdaddr
+
+include $(BUILD_EXECUTABLE)
+
+ifeq ($(BUILD_BTIOTEST),1)
+#
+# btiotest
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	btiotest.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+	$(call include-path-for, glib) \
+	$(call include-path-for, glib)\glib
+
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth \
+
+LOCAL_STATIC_LIBRARIES := \
+	libbluez-common-static \
+	libglib_static \
+
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=btiotest
+
+include $(BUILD_EXECUTABLE)
+endif #BTIOTEST
+
+
+ifeq ($(BUILD_HCIEMU),1)
+#
+# hciemu
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	hciemu.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+	$(call include-path-for, glib) \
+	$(call include-path-for, glib)\glib
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth \
+	libc \
+	libcutils
+
+LOCAL_STATIC_LIBRARIES := \
+	libglib_static
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=hciemu
+
+include $(BUILD_EXECUTABLE)
+endif #BUILD_HCIEMU
+
+#
+# lmptest
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	lmptest.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=lmptest
+
+include $(BUILD_EXECUTABLE)
+
+#
+# sdptest
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_SRC_FILES:= \
+	sdptest.c
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=sdptest
+
+include $(BUILD_EXECUTABLE)
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..2e58d24
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,55 @@
+
+if TEST
+sbin_PROGRAMS = hciemu
+
+bin_PROGRAMS = l2test rctest
+
+noinst_PROGRAMS = sdptest scotest attest hstest avtest lmptest \
+						bdaddr agent btiotest
+
+hciemu_LDADD = $(top_builddir)/common/libhelper.a \
+			@GLIB_LIBS@ @BLUEZ_LIBS@
+
+l2test_LDADD = @BLUEZ_LIBS@
+
+rctest_LDADD = @BLUEZ_LIBS@
+
+sdptest_LDADD = @BLUEZ_LIBS@
+
+scotest_LDADD = @BLUEZ_LIBS@
+
+attest_LDADD = @BLUEZ_LIBS@
+
+hstest_LDADD = @BLUEZ_LIBS@
+
+avtest_LDADD = @BLUEZ_LIBS@
+
+bdaddr_SOURCES = bdaddr.c
+
+bdaddr_LDADD = @BLUEZ_LIBS@ $(top_builddir)/common/libhelper.a
+
+lmptest_LDADD = @BLUEZ_LIBS@
+
+agent_LDADD = @DBUS_LIBS@
+
+btiotest_LDADD = $(top_builddir)/common/libhelper.a @GLIB_LIBS@ @BLUEZ_LIBS@
+
+noinst_MANS = bdaddr.8
+
+if MANPAGES
+man_MANS = rctest.1 hciemu.1
+endif
+
+AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@
+endif
+
+INCLUDES = -I$(top_srcdir)/common
+
+EXTRA_DIST = apitest hsplay hsmicro bdaddr.8 rctest.1 hciemu.1 dbusdef.py \
+		monitor-bluetooth list-devices test-discovery test-manager \
+		test-adapter test-device test-service test-serial \
+		test-telephony test-network simple-agent simple-service \
+		service-record.dtd service-did.xml service-spp.xml \
+		service-opp.xml service-ftp.xml
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/test/agent.c b/test/agent.c
new file mode 100644
index 0000000..4c12ea3
--- /dev/null
+++ b/test/agent.c
@@ -0,0 +1,647 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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 = NULL;
+
+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)
+		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);
+
+	dbus_message_append_args(reply, DBUS_TYPE_STRING, &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_passkey_message(DBusConnection *conn,
+						DBusMessage *msg, void *data)
+{
+	DBusMessage *reply;
+	const char *path;
+	unsigned int int_passkey;
+
+	if (!passkey)
+		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);
+
+	int_passkey = strtoul(passkey, NULL, 10);
+
+	dbus_message_append_args(reply, DBUS_TYPE_UINT32, &int_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 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 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 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", "Cancel"))
+		return cancel_message(conn, msg, data);
+
+	if (dbus_message_is_method_call(msg, "org.bluez.Agent", "Release"))
+		return release_message(conn, msg, data);
+
+	if (dbus_message_is_method_call(msg, "org.bluez.Agent", "Authorize"))
+		return authorize_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;
+
+	if (!dbus_connection_register_object_path(conn, agent_path,
+							&agent_table, NULL)) {
+		fprintf(stderr, "Can't register object path for agent\n");
+		return -1;
+	}
+
+	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);
+		}
+		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);
+		}
+		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' },
+	{ "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: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 '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 = 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 (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);
+
+	dbus_connection_unref(conn);
+
+	return 0;
+}
diff --git a/test/apitest b/test/apitest
new file mode 100755
index 0000000..b1c3f10
--- /dev/null
+++ b/test/apitest
@@ -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
index 0000000..8813f41
--- /dev/null
+++ b/test/attest.c
@@ -0,0 +1,183 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-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 <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
index 0000000..6ec6035
--- /dev/null
+++ b/test/avtest.c
@@ -0,0 +1,31 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2007-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
+
+int main(int argc, char *argv[])
+{
+	return 0;
+}
diff --git a/test/bdaddr.8 b/test/bdaddr.8
new file mode 100644
index 0000000..88345f8
--- /dev/null
+++ b/test/bdaddr.8
@@ -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
index 0000000..77193f4
--- /dev/null
+++ b/test/bdaddr.c
@@ -0,0 +1,460 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..128df63
--- /dev/null
+++ b/test/btiotest.c
@@ -0,0 +1,555 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009  Nokia Corporation
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#include <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
index 0000000..5af6153
--- /dev/null
+++ b/test/dbusdef.py
@@ -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/hciemu.1 b/test/hciemu.1
new file mode 100644
index 0000000..cecaeb7
--- /dev/null
+++ b/test/hciemu.1
@@ -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
index 0000000..66bd84f
--- /dev/null
+++ b/test/hciemu.c
@@ -0,0 +1,1354 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2002  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2003-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 <netinet/in.h>
+#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>
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+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;
+}
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define ntoh64(x) (x)
+#else
+#error "Unknown byte order"
+#endif
+#define hton64(x) ntoh64(x)
+
+#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_close(vdev.scan);
+			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)
+{
+	syslog(LOG_INFO, "Closing connection %s handle %d",
+					batostr(&conn->dest), conn->handle);
+
+	g_io_channel_close(conn->chan);
+	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;
+	gsize len;
+	GIOError err;
+
+	ptr = buf;
+
+	if ((err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len))) {
+		if (err == G_IO_ERROR_AGAIN)
+			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 */
+		baswap(ba, strtoba(str));
+		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
index 0000000..c254226
--- /dev/null
+++ b/test/hsmicro
@@ -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
index 0000000..8cecbff
--- /dev/null
+++ b/test/hsplay
@@ -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
index 0000000..b6a58c6
--- /dev/null
+++ b/test/hstest.c
@@ -0,0 +1,308 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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 <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/l2test.c b/test/l2test.c
new file mode 100644
index 0000000..6ba50c3
--- /dev/null
+++ b/test/l2test.c
@@ -0,0 +1,1286 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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,
+	INFOREQ,
+	PAIRING,
+};
+
+static unsigned char *buf;
+
+/* Default mtu */
+static int imtu = 672;
+static int omtu = 0;
+
+/* Default data size */
+static long data_size = -1;
+static long buffer_size = 2048;
+
+/* Default addr and psm */
+static bdaddr_t bdaddr;
+static unsigned short psm = 10;
+
+/* 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;
+	if (rfcmode > 0)
+		opts.mode = rfcmode;
+
+	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);
+	addr.l2_psm = htobs(psm);
+
+	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);
+	addr.l2_psm = htobs(psm);
+
+	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;
+
+	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);
+
+	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 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-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]\n"
+		"\t[-I imtu] [-O omtu]\n"
+		"\t[-L seconds] enable SO_LINGER\n"
+		"\t[-F 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[-R] reliable mode\n"
+		"\t[-G] use connectionless channel (datagram)\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,"rdscuwmnxyzpb:i:P:I:O:B:N:L:F:C:D:X:RGAESMT")) != 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 '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 'F':
+			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 '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 'T':
+			timestamp = 1;
+			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 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
index 0000000..511d0cf
--- /dev/null
+++ b/test/list-devices
@@ -0,0 +1,84 @@
+#!/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)
+		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
index 0000000..ce30bb1
--- /dev/null
+++ b/test/lmptest.c
@@ -0,0 +1,175 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-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 <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
index 0000000..a5e5300
--- /dev/null
+++ b/test/monitor-bluetooth
@@ -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
index 0000000..b83d694
--- /dev/null
+++ b/test/rctest.1
@@ -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 -F\  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
index 0000000..a246262
--- /dev/null
+++ b/test/rctest.c
@@ -0,0 +1,781 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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[-F 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:F: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 'F':
+			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
index 0000000..80081c6
--- /dev/null
+++ b/test/scotest.c
@@ -0,0 +1,434 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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
index 0000000..cccb1da
--- /dev/null
+++ b/test/sdptest.c
@@ -0,0 +1,146 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-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 <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
index 0000000..52eb68c
--- /dev/null
+++ b/test/service-did.xml
@@ -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
index 0000000..1bda885
--- /dev/null
+++ b/test/service-ftp.xml
@@ -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
index 0000000..351b4a4
--- /dev/null
+++ b/test/service-opp.xml
@@ -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
index 0000000..f53be5d
--- /dev/null
+++ b/test/service-record.dtd
@@ -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
index 0000000..2b156c3
--- /dev/null
+++ b/test/service-spp.xml
@@ -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
index 0000000..d569829
--- /dev/null
+++ b/test/simple-agent
@@ -0,0 +1,116 @@
+#!/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)
+
+	@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-service b/test/simple-service
new file mode 100755
index 0000000..d03ec3d
--- /dev/null
+++ b/test/simple-service
@@ -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
index 0000000..4c1a28a
--- /dev/null
+++ b/test/test-adapter
@@ -0,0 +1,106 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import time
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()),
+							"org.bluez.Adapter")
+
+if (len(sys.argv) < 2):
+	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 (sys.argv[1] == "address"):
+	properties = adapter.GetProperties()
+	print properties["Address"]
+	sys.exit(0)
+
+if (sys.argv[1] == "name"):
+	if (len(sys.argv) < 3):
+		properties = adapter.GetProperties()
+		print properties["Name"]
+	else:
+		adapter.SetProperty("Name", sys.argv[2])
+	sys.exit(0)
+
+if (sys.argv[1] == "powered"):
+	if (len(sys.argv) < 3):
+		properties = adapter.GetProperties()
+		print properties["Powered"]
+	else:
+		if (sys.argv[2] == "on"):
+			value = dbus.Boolean(1)
+		elif (sys.argv[2] == "off"):
+			value = dbus.Boolean(0)
+		else:
+			value = dbus.Boolean(sys.argv[2])
+		adapter.SetProperty("Powered", value)
+	sys.exit(0)
+
+if (sys.argv[1] == "pairable"):
+	if (len(sys.argv) < 3):
+		properties = adapter.GetProperties()
+		print properties["Pairable"]
+	else:
+		if (sys.argv[2] == "on"):
+			value = dbus.Boolean(1)
+		elif (sys.argv[2] == "off"):
+			value = dbus.Boolean(0)
+		else:
+			value = dbus.Boolean(sys.argv[2])
+		adapter.SetProperty("Pairable", value)
+	sys.exit(0)
+
+if (sys.argv[1] == "pairabletimeout"):
+	if (len(sys.argv) < 3):
+		properties = adapter.GetProperties()
+		print properties["PairableTimeout"]
+	else:
+		timeout = dbus.UInt32(sys.argv[2])
+		adapter.SetProperty("PairableTimeout", timeout)
+	sys.exit(0)
+
+if (sys.argv[1] == "discoverable"):
+	if (len(sys.argv) < 3):
+		properties = adapter.GetProperties()
+		print properties["Discoverable"]
+	else:
+		if (sys.argv[2] == "on"):
+			value = dbus.Boolean(1)
+		elif (sys.argv[2] == "off"):
+			value = dbus.Boolean(0)
+		else:
+			value = dbus.Boolean(sys.argv[2])
+		adapter.SetProperty("Discoverable", value)
+	sys.exit(0)
+
+if (sys.argv[1] == "discoverabletimeout"):
+	if (len(sys.argv) < 3):
+		properties = adapter.GetProperties()
+		print properties["DiscoverableTimeout"]
+	else:
+		timeout = dbus.UInt32(sys.argv[2])
+		adapter.SetProperty("DiscoverableTimeout", timeout)
+	sys.exit(0)
+
+if (sys.argv[1] == "discovering"):
+	properties = adapter.GetProperties()
+	print properties["Discovering"]
+	sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/test/test-device b/test/test-device
new file mode 100755
index 0000000..f8f2d14
--- /dev/null
+++ b/test/test-device
@@ -0,0 +1,128 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import re
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()),
+							"org.bluez.Adapter")
+
+if (len(sys.argv) < 2):
+	print "Usage: %s <command>" % (sys.argv[0])
+	print ""
+	print "  list"
+	print "  create <address>"
+	print "  remove <address|path>"
+	print "  discover <address> [pattern]"
+	print "  class <address>"
+	print "  name <address>"
+	print "  alias <address> [alias]"
+	print "  trusted <address> [yes/no]"
+	sys.exit(1)
+
+if (sys.argv[1] == "list"):
+	list = adapter.ListDevices()
+	print list
+	sys.exit(0)
+
+if (sys.argv[1] == "create"):
+	if (len(sys.argv) < 3):
+		print "Need address parameter"
+	else:
+		device = adapter.CreateDevice(sys.argv[2])
+		print device
+	sys.exit(0)
+
+if (sys.argv[1] == "remove"):
+	if (len(sys.argv) < 3):
+		print "Need address or object path parameter"
+	else:
+		try:
+			path = adapter.FindDevice(sys.argv[2])
+		except:
+			path = sys.argv[2]
+		adapter.RemoveDevice(path)
+	sys.exit(0)
+
+if (sys.argv[1] == "discover"):
+	if (len(sys.argv) < 3):
+		print "Need address parameter"
+	else:
+		path = adapter.FindDevice(sys.argv[2])
+		device = dbus.Interface(bus.get_object("org.bluez", path),
+							"org.bluez.Device")
+		if (len(sys.argv) < 4):
+			pattern = ""
+		else:
+			pattern = sys.argv[3]
+		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 (sys.argv[1] == "class"):
+	if (len(sys.argv) < 3):
+		print "Need address parameter"
+	else:
+		path = adapter.FindDevice(sys.argv[2])
+		device = dbus.Interface(bus.get_object("org.bluez", path),
+							"org.bluez.Device")
+		properties = device.GetProperties()
+		print "0x%06x" % (properties["Class"])
+	sys.exit(0)
+
+if (sys.argv[1] == "name"):
+	if (len(sys.argv) < 3):
+		print "Need address parameter"
+	else:
+		path = adapter.FindDevice(sys.argv[2])
+		device = dbus.Interface(bus.get_object("org.bluez", path),
+							"org.bluez.Device")
+		properties = device.GetProperties()
+		print properties["Name"]
+	sys.exit(0)
+
+if (sys.argv[1] == "alias"):
+	if (len(sys.argv) < 3):
+		print "Need address parameter"
+	else:
+		path = adapter.FindDevice(sys.argv[2])
+		device = dbus.Interface(bus.get_object("org.bluez", path),
+							"org.bluez.Device")
+		if (len(sys.argv) < 4):
+			properties = device.GetProperties()
+			print properties["Alias"]
+		else:
+			device.SetProperty("Alias", sys.argv[3])
+	sys.exit(0)
+
+if (sys.argv[1] == "trusted"):
+	if (len(sys.argv) < 3):
+		print "Need address parameter"
+	else:
+		path = adapter.FindDevice(sys.argv[2])
+		device = dbus.Interface(bus.get_object("org.bluez", path),
+							"org.bluez.Device")
+		if (len(sys.argv) < 4):
+			properties = device.GetProperties()
+			print properties["Trusted"]
+		else:
+			if (sys.argv[3] == "yes"):
+				value = dbus.Boolean(1)
+			elif (sys.argv[3] == "no"):
+				value = dbus.Boolean(0)
+			else:
+				value = dbus.Boolean(sys.argv[3])
+			device.SetProperty("Trusted", value)
+	sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/test/test-discovery b/test/test-discovery
new file mode 100755
index 0000000..e45bcc0
--- /dev/null
+++ b/test/test-discovery
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+
+import gobject
+
+import dbus
+import dbus.mainloop.glib
+
+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")
+
+	path = manager.DefaultAdapter()
+	adapter = dbus.Interface(bus.get_object("org.bluez", 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-manager b/test/test-manager
new file mode 100755
index 0000000..c6cf560
--- /dev/null
+++ b/test/test-manager
@@ -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
index 0000000..6f75bed
--- /dev/null
+++ b/test/test-network
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+
+import sys
+import time
+import dbus
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+						"org.bluez.Manager")
+
+adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()),
+							"org.bluez.Adapter")
+
+if (len(sys.argv) < 2):
+	print "Usage: %s <address> [service]" % (sys.argv[0])
+	sys.exit(1)
+
+address = sys.argv[1]
+
+if (len(sys.argv) < 3):
+	service = "panu"
+else:
+	service = sys.argv[2]
+
+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
index 0000000..f6d6022
--- /dev/null
+++ b/test/test-serial
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+
+import sys
+import time
+import dbus
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+						"org.bluez.Manager")
+
+adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()),
+							"org.bluez.Adapter")
+
+if (len(sys.argv) < 2):
+	print "Usage: %s <address> [service]" % (sys.argv[0])
+	sys.exit(1)
+
+address = sys.argv[1]
+
+if (len(sys.argv) < 3):
+	service = "spp"
+else:
+	service = sys.argv[2]
+
+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
index 0000000..e005201
--- /dev/null
+++ b/test/test-service
@@ -0,0 +1,33 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import time
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+adapter = dbus.Interface(bus.get_object("org.bluez", manager.FindAdapter("any")),
+							"org.bluez.Service")
+
+if (len(sys.argv) < 2):
+	print "Usage: %s <command>" % (sys.argv[0])
+	print ""
+	print "  addrecord <file>"
+	sys.exit(1)
+
+if (sys.argv[1] == "addrecord"):
+	if (len(sys.argv) < 3):
+		print "Need file parameter"
+	else:
+		f = open(sys.argv[2])
+		record = f.read()
+		f.close()
+		handle = adapter.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
index 0000000..eb69511
--- /dev/null
+++ b/test/test-telephony
@@ -0,0 +1,160 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()),
+				"org.bluez.Adapter")
+test = dbus.Interface(bus.get_object("org.bluez", "/org/bluez/test"),
+			"org.bluez.TelephonyTest")
+
+if len(sys.argv) < 2:
+	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 sys.argv[1] == "connect":
+	if len(sys.argv) < 3:
+		print "Need device address parameter"
+		sys.exit(1)
+	device = adapter.FindDevice(sys.argv[2])
+	headset = dbus.Interface(bus.get_object("org.bluez", device),
+					"org.bluez.Headset")
+	headset.Connect()
+	sys.exit(0)
+
+if sys.argv[1] == "disconnect":
+	if len(sys.argv) < 3:
+		print "Need device address parameter"
+		sys.exit(1)
+	device = adapter.FindDevice(sys.argv[2])
+	headset = dbus.Interface(bus.get_object("org.bluez", device),
+					"org.bluez.Headset")
+	headset.Disconnect()
+	sys.exit(0)
+
+if sys.argv[1] == "speakergain":
+	if len(sys.argv) < 3:
+		print "Need device address parameter"
+		sys.exit(1)
+	device = adapter.FindDevice(sys.argv[2])
+	headset = dbus.Interface(bus.get_object("org.bluez", device),
+					"org.bluez.Headset")
+	if len(sys.argv) > 3:
+		headset.SetProperty('SpeakerGain', dbus.UInt16(sys.argv[3]))
+	else:
+		props = headset.GetProperties()
+		print props['SpeakerGain']
+
+	sys.exit(0)
+
+if sys.argv[1] == "microphonegain":
+	if len(sys.argv) < 3:
+		print "Need device address parameter"
+		sys.exit(1)
+	device = adapter.FindDevice(sys.argv[2])
+	headset = dbus.Interface(bus.get_object("org.bluez", device),
+					"org.bluez.Headset")
+	if len(sys.argv) > 3:
+		headset.SetProperty('MicrophoneGain', dbus.UInt16(sys.argv[3]))
+	else:
+		props = headset.GetProperties()
+		print props['MicrophoneGain']
+
+	sys.exit(0)
+
+if sys.argv[1] == "play":
+	if len(sys.argv) < 3:
+		print "Need device address parameter"
+		sys.exit(1)
+	device = adapter.FindDevice(sys.argv[2])
+	headset = dbus.Interface(bus.get_object("org.bluez", device),
+					"org.bluez.Headset")
+	headset.Play()
+
+	sys.exit(0)
+
+if sys.argv[1] == "stop":
+	if len(sys.argv) < 3:
+		print "Need device address parameter"
+		sys.exit(1)
+	device = adapter.FindDevice(sys.argv[2])
+	headset = dbus.Interface(bus.get_object("org.bluez", device),
+					"org.bluez.Headset")
+	headset.Stop()
+
+	sys.exit(0)
+
+if sys.argv[1] == "outgoing":
+	if len(sys.argv) > 2:
+		test.OutgoingCall(sys.argv[2])
+	else:
+		print "Need number parameter"
+	sys.exit(0)
+
+if sys.argv[1] == "incoming":
+	if len(sys.argv) > 2:
+		test.IncomingCall(sys.argv[2])
+	else:
+		print "Need number parameter"
+	sys.exit(0)
+
+if sys.argv[1] == "cancel":
+	test.CancelCall()
+	sys.exit(0)
+
+if sys.argv[1] == "signal":
+	if len(sys.argv) > 2:
+		test.SignalStrength(sys.argv[2])
+	else:
+		print "Need signal strength parameter"
+	sys.exit(0)
+
+if sys.argv[1] == "battery":
+	if len(sys.argv) > 2:
+		test.BatteryLevel(sys.argv[2])
+	else:
+		print "Need battery level parameter"
+	sys.exit(0)
+
+if sys.argv[1] == "roaming":
+	if len(sys.argv) > 2:
+		test.RoamingStatus(sys.argv[2] == "yes" or False)
+	else:
+		print "Need yes/no parameter"
+	sys.exit(0)
+
+if sys.argv[1] == "registration":
+	if len(sys.argv) > 2:
+		test.RegistrationStatus(sys.argv[2] == "yes" or False)
+	else:
+		print "Need yes/no parameter"
+	sys.exit(0)
+
+if sys.argv[1] == "subscriber":
+	if len(sys.argv) > 2:
+		test.SetSubscriberNumber(sys.argv[2])
+	else:
+		print "Need number parameter"
+	sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/tools/Android.mk b/tools/Android.mk
new file mode 100755
index 0000000..c966a3b
--- /dev/null
+++ b/tools/Android.mk
@@ -0,0 +1,164 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# avinfo
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	avinfo.c
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\"
+
+LOCAL_C_INCLUDES:=\
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=avinfo
+
+include $(BUILD_EXECUTABLE)
+
+#
+# sdptool
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	sdptool.c
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\" -fpermissive
+
+LOCAL_C_INCLUDES:=\
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_STATIC_LIBRARIES := \
+	libbluez-common-static
+
+LOCAL_MODULE:=sdptool
+
+include $(BUILD_EXECUTABLE)
+
+#
+# hciconfig
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	csr.c \
+	csr_h4.c \
+	hciconfig.c
+
+LOCAL_CFLAGS:= \
+	-DSTORAGEDIR=\"/tmp\" \
+	-DVERSION=\"4.47\"
+
+LOCAL_C_INCLUDES:=\
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_STATIC_LIBRARIES := \
+	libbluez-common-static
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=hciconfig
+
+include $(BUILD_EXECUTABLE)
+
+#
+# hcitool
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	hcitool.c
+
+LOCAL_CFLAGS:= \
+	-DSTORAGEDIR=\"/tmp\" \
+	-DVERSION=\"4.47\"
+
+LOCAL_C_INCLUDES:=\
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_STATIC_LIBRARIES := \
+	libbluez-common-static
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=hcitool
+
+include $(BUILD_EXECUTABLE)
+
+#
+# l2ping
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	l2ping.c
+
+LOCAL_C_INCLUDES:=\
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE:=l2ping
+
+include $(BUILD_EXECUTABLE)
+
+#
+# hciattach
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	hciattach.c \
+	hciattach_st.c \
+	hciattach_ti.c \
+	hciattach_tialt.c
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\" \
+	-D__BSD_VISIBLE=1
+
+LOCAL_C_INCLUDES:=\
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth
+
+LOCAL_STATIC_LIBRARIES := \
+	libbluez-common-static
+
+LOCAL_MODULE:=hciattach
+
+include $(BUILD_EXECUTABLE)
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..5589a5a
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,106 @@
+
+if TOOLS
+tools_programs = l2ping hcitool sdptool ciptool
+tools_manfiles = l2ping.8 hcitool.1 sdptool.1 ciptool.1
+else
+tools_programs =
+tools_manfiles =
+endif
+
+if BCCMD
+bccmd_programs = bccmd
+bccmd_manfiles = bccmd.8
+else
+bccmd_programs =
+bccmd_manfiles =
+endif
+
+if HID2HCI
+hid2hci_programs = hid2hci
+hid2hci_manfiles = hid2hci.8
+else
+hid2hci_programs =
+hid2hci_manfiles =
+endif
+
+if DFUTOOL
+dfutool_programs = dfutool
+dfutool_manfiles = dfutool.1
+else
+dfutool_programs =
+dfutool_manfiles =
+endif
+
+if USB
+usb_programs = dfubabel avctrl
+else
+usb_programs =
+endif
+
+sbin_PROGRAMS = hciattach hciconfig $(bccmd_programs) $(avctrl_programs) $(hid2hci_programs)
+
+bin_PROGRAMS = $(tools_programs) $(dfutool_programs) $(dfubabel_programs)
+
+noinst_PROGRAMS = hcieventmask hcisecfilter ppporc avinfo $(usb_programs)
+
+hcieventmask_LDADD = @BLUEZ_LIBS@
+
+hciattach_SOURCES = hciattach.c hciattach.h \
+			hciattach_st.c hciattach_ti.c hciattach_tialt.c
+hciattach_LDADD = @BLUEZ_LIBS@
+
+hciconfig_SOURCES = hciconfig.c csr.h csr.c
+hciconfig_LDADD = @BLUEZ_LIBS@ $(top_builddir)/common/libhelper.a
+
+if TOOLS
+hcitool_SOURCES = hcitool.c
+hcitool_LDADD = @BLUEZ_LIBS@ $(top_builddir)/common/libhelper.a
+
+l2ping_LDADD = @BLUEZ_LIBS@
+
+sdptool_LDADD = @BLUEZ_LIBS@ $(top_builddir)/common/libhelper.a
+
+ciptool_LDADD = @BLUEZ_LIBS@
+
+avinfo_LDADD = @BLUEZ_LIBS@
+endif
+
+ppporc_LDADD = @BLUEZ_LIBS@
+
+if BCCMD
+bccmd_SOURCES = bccmd.c csr.h csr.c csr_hci.c \
+			csr_bcsp.c csr_h4.c csr_3wire.c ubcsp.h ubcsp.c
+bccmd_LDADD = @BLUEZ_LIBS@
+if USB
+bccmd_SOURCES += csr_usb.c
+bccmd_LDADD += @USB_LIBS@
+endif
+endif
+
+if HID2HCI
+hid2hci_LDADD = @USB_LIBS@
+endif
+
+if DFUTOOL
+dfutool_SOURCES = dfutool.c dfu.h dfu.c
+dfutool_LDADD = @USB_LIBS@
+endif
+
+if USB
+dfubabel_LDADD = @USB_LIBS@
+avctrl_LDADD = @USB_LIBS@
+endif
+
+AM_CFLAGS = @BLUEZ_CFLAGS@ @USB_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common
+
+if MANPAGES
+man_MANS = hciattach.8 hciconfig.8 $(tools_manfiles) \
+		$(bccmd_manfiles) $(hid2hci_manfiles) $(dfutool_manfiles)
+endif
+
+EXTRA_DIST = hciattach.8 hciconfig.8 l2ping.8 hcitool.1 sdptool.1 ciptool.1 \
+		 bccmd.8 avctrl.8 hid2hci.8 dfutool.1 dfubabel.1 example.psr
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/tools/avctrl.8 b/tools/avctrl.8
new file mode 100644
index 0000000..7c3759d
--- /dev/null
+++ b/tools/avctrl.8
@@ -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
index 0000000..7b5e685
--- /dev/null
+++ b/tools/avctrl.c
@@ -0,0 +1,248 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..a3cfd45
--- /dev/null
+++ b/tools/avinfo.c
@@ -0,0 +1,672 @@
+/*
+ *
+ *  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 <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
index 0000000..28cbe88
--- /dev/null
+++ b/tools/bccmd.8
@@ -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
index 0000000..0e0c28c
--- /dev/null
+++ b/tools/bccmd.c
@@ -0,0 +1,1219 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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)
+{
+	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);
+	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-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' },
+	{ "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;
+
+	while ((opt=getopt_long(argc, argv, "+t:d:i: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 'h':
+		default:
+			usage();
+			exit(0);
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+	optind = 0;
+
+	if (argc < 1) {
+		usage();
+		exit(1);
+	}
+
+	if (transport_open(transport, device) < 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
index 0000000..65d903d
--- /dev/null
+++ b/tools/ciptool.1
@@ -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
index 0000000..2afa30e
--- /dev/null
+++ b/tools/ciptool.c
@@ -0,0 +1,498 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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
+
+#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
index 0000000..884f0d1
--- /dev/null
+++ b/tools/csr.c
@@ -0,0 +1,2853 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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
index 0000000..42bdb9f
--- /dev/null
+++ b/tools/csr.h
@@ -0,0 +1,552 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+ *
+ */
+
+#include <stdint.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);
+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
index 0000000..34a0db9
--- /dev/null
+++ b/tools/csr_3wire.c
@@ -0,0 +1,62 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..2c73323
--- /dev/null
+++ b/tools/csr_bcsp.c
@@ -0,0 +1,255 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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)
+{
+	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, 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);
+
+	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
index 0000000..6fa326f
--- /dev/null
+++ b/tools/csr_h4.c
@@ -0,0 +1,165 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..7f54d55
--- /dev/null
+++ b/tools/csr_hci.c
@@ -0,0 +1,160 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..e756150
--- /dev/null
+++ b/tools/csr_usb.c
@@ -0,0 +1,180 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..0207086
--- /dev/null
+++ b/tools/dfu.c
@@ -0,0 +1,168 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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
index 0000000..427cb42
--- /dev/null
+++ b/tools/dfu.h
@@ -0,0 +1,107 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+ *
+ */
+
+#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
index 0000000..5e0f805
--- /dev/null
+++ b/tools/dfubabel.1
@@ -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
index 0000000..498a717
--- /dev/null
+++ b/tools/dfubabel.c
@@ -0,0 +1,211 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  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 <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
index 0000000..115114b
--- /dev/null
+++ b/tools/dfutool.1
@@ -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
index 0000000..055de25
--- /dev/null
+++ b/tools/dfutool.c
@@ -0,0 +1,791 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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/example.psr b/tools/example.psr
new file mode 100644
index 0000000..bbbec73
--- /dev/null
+++ b/tools/example.psr
@@ -0,0 +1,12 @@
+// PSKEY_BDADDR
+&0001 = 0001 2821 005b 6789
+// PSKEY_ANA_FTRIM
+&01f6 = 0025
+// PSKEY_HOST_INTERFACE
+&01f9 = 0001
+// PSKEY_UART_BAUD_RATE
+&0204 = 01d8
+// PSKEY_ANA_FREQ
+&01fe = 0004
+// PSKEY_UART_CONFIG
+&0205 = 0006
diff --git a/tools/hciattach.8 b/tools/hciattach.8
new file mode 100644
index 0000000..f750222
--- /dev/null
+++ b/tools/hciattach.8
@@ -0,0 +1,122 @@
+.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 [\| \-n \|]
+.RB [\| \-p \|]
+.RB [\| \-t
+.IR timeout \|]
+.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 \-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
+.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
+.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 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
index 0000000..65737f2
--- /dev/null
+++ b/tools/hciattach.c
@@ -0,0 +1,1361 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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
+
+#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"
+
+#include "ppoll.h"
+
+struct uart_t {
+	char *type;
+	int  m_id;
+	int  p_id;
+	int  proto;
+	int  init_speed;
+	int  speed;
+	int  flags;
+	char *bdaddr;
+	int  (*init) (int fd, struct uart_t *u, struct termios *ti);
+	int  (*post) (int fd, struct uart_t *u, struct termios *ti);
+};
+
+#define FLOW_CTL	0x0001
+
+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)
+{
+	cfsetospeed(ti, uart_speed(speed));
+	cfsetispeed(ti, uart_speed(speed));
+	return tcsetattr(fd, TCSANOW, ti);
+}
+
+/* 
+ * 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 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, 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, 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));
+	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;
+	}
+
+	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, NULL, NULL     },
+	{ "ericsson",   0x0000, 0x0000, HCI_UART_H4,   57600,  115200, FLOW_CTL, NULL, ericsson },
+	{ "digi",       0x0000, 0x0000, HCI_UART_H4,   9600,   115200, FLOW_CTL, NULL, digi     },
+
+	{ "bcsp",       0x0000, 0x0000, HCI_UART_BCSP, 115200, 115200, 0,        NULL, bcsp     },
+
+	/* Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter */
+	{ "xircom",     0x0105, 0x080a, HCI_UART_H4,   115200, 115200, FLOW_CTL, NULL, NULL     },
+
+	/* CSR Casira serial adapter or BrainBoxes serial dongle (BL642) */
+	{ "csr",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200, FLOW_CTL, NULL, csr      },
+
+	/* BrainBoxes PCMCIA card (BL620) */
+	{ "bboxes",     0x0160, 0x0002, HCI_UART_H4,   115200, 460800, FLOW_CTL, NULL, csr      },
+
+	/* Silicon Wave kits */
+	{ "swave",      0x0000, 0x0000, HCI_UART_H4,   115200, 115200, FLOW_CTL, NULL, swave    },
+
+	/* 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   },
+
+	/* ST Microelectronics minikits based on STLC2410/STLC2415 */
+	{ "st",         0x0000, 0x0000, HCI_UART_H4,    57600, 115200, FLOW_CTL, NULL, st       },
+
+	/* ST Microelectronics minikits based on STLC2500 */
+	{ "stlc2500",   0x0000, 0x0000, HCI_UART_H4,   115200, 115200, FLOW_CTL, "00:80:E1:00:AB:BA", stlc2500 },
+
+	/* Philips generic Ericsson IP core based */
+	{ "philips",    0x0000, 0x0000, HCI_UART_H4,   115200, 115200, FLOW_CTL, NULL, NULL     },
+
+	/* Philips BGB2xx Module */
+	{ "bgb2xx",    0x0000, 0x0000, HCI_UART_H4,   115200, 115200, FLOW_CTL, "BD:B2:10:00:AB:BA", bgb2xx },
+
+	/* Sphinx Electronics PICO Card */
+	{ "picocard",   0x025e, 0x1000, HCI_UART_H4,   115200, 115200, FLOW_CTL, NULL, NULL     },
+
+	/* Inventel BlueBird Module */
+	{ "inventel",   0x0000, 0x0000, HCI_UART_H4,   115200, 115200, FLOW_CTL, NULL, NULL     },
+
+	/* COM One Platinium Bluetooth PC Card */
+	{ "comone",     0xffff, 0x0101, HCI_UART_BCSP, 115200, 115200, 0,        NULL, bcsp     },
+
+	/* TDK Bluetooth PC Card and IBM Bluetooth PC Card II */
+	{ "tdk",        0x0105, 0x4254, HCI_UART_BCSP, 115200, 115200, 0,        NULL, bcsp     },
+
+	/* Socket Bluetooth CF Card (Rev G) */
+	{ "socket",     0x0104, 0x0096, HCI_UART_BCSP, 230400, 230400, 0,        NULL, bcsp     },
+
+	/* 3Com Bluetooth Card (Version 3.0) */
+	{ "3com",       0x0101, 0x0041, HCI_UART_H4,   115200, 115200, FLOW_CTL, NULL, csr      },
+
+	/* AmbiCom BT2000C Bluetooth PC/CF Card */
+	{ "bt2000c",    0x022d, 0x2000, HCI_UART_H4,    57600, 460800, FLOW_CTL, NULL, csr      },
+
+	/* Zoom Bluetooth PCMCIA Card */
+	{ "zoom",       0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200, 0,        NULL, bcsp     },
+
+	/* Sitecom CN-504 PCMCIA Card */
+	{ "sitecom",    0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200, 0,        NULL, bcsp     },
+
+	/* Billionton PCBTC1 PCMCIA Card */
+	{ "billionton", 0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200, 0,        NULL, bcsp     },
+
+	/* Broadcom BCM2035 */
+	{ "bcm2035",    0x0A5C, 0x2035, HCI_UART_H4,   115200, 460800, FLOW_CTL, NULL, bcm2035  },
+
+	{ 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)
+{
+	struct termios ti;
+	int fd, i;
+
+	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);
+
+	ti.c_cflag |= CLOCAL;
+	if (u->flags & FLOW_CTL)
+		ti.c_cflag |= CRTSCTS;
+	else
+		ti.c_cflag &= ~CRTSCTS;
+
+	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 (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 (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");
+	printf("\thciattach [-n] [-p] [-b] [-t timeout] [-s initial_speed] <tty> <type | id> [speed] [flow|noflow] [bdaddr]\n");
+	printf("\thciattach -l\n");
+}
+
+int main(int argc, char *argv[])
+{
+	struct uart_t *u = NULL;
+	int detach, printpid, 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];
+
+	detach = 1;
+	printpid = 0;
+
+	while ((opt=getopt(argc, argv, "bnpt:s:l")) != EOF) {
+		switch(opt) {
+		case 'b':
+			send_break = 1;
+			break;
+
+		case 'n':
+			detach = 0;
+			break;
+
+		case 'p':
+			printpid = 1;
+			break;
+
+		case 't':
+			to = atoi(optarg);
+			break;
+
+		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);
+
+		default:
+			usage();
+			exit(1);
+		}
+	}
+
+	n = argc - optind;
+	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:
+			u->bdaddr = argv[optind];
+			break;
+		}
+	}
+
+	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;
+
+	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);
+	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);
+	}
+
+	return 0;
+}
diff --git a/tools/hciattach.h b/tools/hciattach.h
new file mode 100644
index 0000000..e772211
--- /dev/null
+++ b/tools/hciattach.h
@@ -0,0 +1,47 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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
+ *
+ */
+
+#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 HCI_UART_H4	0
+#define HCI_UART_BCSP	1
+#define HCI_UART_3WIRE	2
+#define HCI_UART_H4DS	3
+#define HCI_UART_LL	4
+
+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);
diff --git a/tools/hciattach_st.c b/tools/hciattach_st.c
new file mode 100644
index 0000000..e2363bf
--- /dev/null
+++ b/tools/hciattach_st.c
@@ -0,0 +1,278 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-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 <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
index 0000000..e169f89
--- /dev/null
+++ b/tools/hciattach_ti.c
@@ -0,0 +1,522 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2007-2008  Texas Instruments, Inc.
+ *  Copyright (C) 2005-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 <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
+
+#define FIRMWARE_DIRECTORY	"/lib/firmware/"
+
+#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;
+
+	sprintf(firmware_file_name, FIRMWARE_DIRECTORY "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver);
+
+	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 */
+		if (!hcill_installed &&
+				brf_action_is_deep_sleep(brf_action,
+							brf_size, brf_type))
+			return 0;
+	}
+
+	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;
+	}
+
+	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
index 0000000..325e006
--- /dev/null
+++ b/tools/hciattach_tialt.c
@@ -0,0 +1,271 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-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 <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, struct termios *ti, uint32_t speed)
+{
+        /* Send a speed-change request */
+        texas_speed_change_cmd_t cmd;
+        int n;
+
+        cmd.uart_prefix = HCI_COMMAND_PKT;
+        cmd.hci_hdr.opcode = 0xff36;
+        cmd.hci_hdr.plen = sizeof(uint32_t);
+        cmd.speed = speed;
+
+        fprintf(stderr, "Setting speed to %d\n", speed);
+        n = write(fd, &cmd, sizeof(cmd));
+        if (n < 0) {
+                perror("Failed to write speed-set command");
+                return -1;
+        }
+        if (n < (int)sizeof(cmd)) {
+                fprintf(stderr, "Wanted to write %d bytes, could only write %d. "
+                "Stop\n", (int)sizeof(cmd), n);
+                return -1;
+        }
+        /* Parse speed-change reply */
+        if (read_command_complete(fd, cmd.hci_hdr.opcode, cmd.hci_hdr.plen) < 0) {
+                return -1;
+        }
+        if (set_speed(fd, ti, speed) < 0) {
+                perror("Can't set baud rate");
+                return -1;
+        }
+        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_change_speed(fd, ti, speed);
+		texas_load_firmware(fd, fw);
+	}
+	nanosleep(&tm, NULL);
+	return 0;
+}
diff --git a/tools/hciconfig.8 b/tools/hciconfig.8
new file mode 100644
index 0000000..13f52c6
--- /dev/null
+++ b/tools/hciconfig.8
@@ -0,0 +1,276 @@
+.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 \|]
+.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
index 0000000..a89aed1
--- /dev/null
+++ b/tools/hciconfig.c
@@ -0,0 +1,1761 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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 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_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 Ver: %s (0x%x) HCI Rev: 0x%x LMP Ver: %s (0x%x) LMP Subver: 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_inquiry_transmit_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", *((uint8_t *) ptr));
+				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 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\n", di->name, hci_dtypetostr(di->type) );
+	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" },
+	{ 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, 5))
+				continue;
+
+			if (command[i].opt) {
+				argc--; argv++;
+			}
+
+			command[i].func(ctl, di.dev_id, *argv);
+			cmd = 1;
+			break;
+		}
+		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
index 0000000..0976e1f
--- /dev/null
+++ b/tools/hcieventmask.c
@@ -0,0 +1,124 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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 <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 */
+		}
+	}
+
+	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
index 0000000..b3034da
--- /dev/null
+++ b/tools/hcisecfilter.c
@@ -0,0 +1,155 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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_INQUIRY_TRANSMIT_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
index 0000000..85498dc
--- /dev/null
+++ b/tools/hcitool.1
@@ -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
index 0000000..faf4cb4
--- /dev/null
+++ b/tools/hcitool.c
@@ -0,0 +1,2452 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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"
+
+#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 char *type2str(uint8_t type)
+{
+	switch (type) {
+	case SCO_LINK:
+		return "SCO";
+	case ACL_LINK:
+		return "ACL";
+	case ESCO_LINK:
+		return "eSCO";
+	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);
+	}
+
+	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))
+			return 1;
+
+	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;
+		}
+	}
+
+	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;
+		}
+	}
+
+	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];
+	uint16_t handle;
+	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;
+		}
+	}
+
+	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++) {
+		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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", name_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", info_help);
+		return;
+	}
+
+	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;
+		}
+	}
+
+	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"
+	"\tspinq\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;
+		}
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 2) {
+		printf("%s", cmd_help);
+		return;
+	}
+
+	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;
+		}
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", cc_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", dc_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 2) {
+		printf("%s", sr_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", rssi_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", lq_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", tpl_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", afh_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 2) {
+		printf("%s", cpt_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", lp_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", lst_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", auth_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", enc_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", key_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		printf("%s", clkoff_help);
+		return;
+	}
+
+	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;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	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 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"           },
+	{ 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], 3))
+			continue;
+		command[i].func(dev_id, argc, argv);
+		break;
+	}
+	return 0;
+}
diff --git a/tools/hid2hci.8 b/tools/hid2hci.8
new file mode 100644
index 0000000..5d35274
--- /dev/null
+++ b/tools/hid2hci.8
@@ -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
index 0000000..11d707f
--- /dev/null
+++ b/tools/hid2hci.c
@@ -0,0 +1,375 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2003-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 <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/l2ping.8 b/tools/l2ping.8
new file mode 100644
index 0000000..8b77ee2
--- /dev/null
+++ b/tools/l2ping.8
@@ -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
index 0000000..55597cc
--- /dev/null
+++ b/tools/l2ping.c
@@ -0,0 +1,322 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-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 <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);
+	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/ppporc.c b/tools/ppporc.c
new file mode 100644
index 0000000..ea42c0c
--- /dev/null
+++ b/tools/ppporc.c
@@ -0,0 +1,271 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2002-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 <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/sdptool.1 b/tools/sdptool.1
new file mode 100644
index 0000000..0f100e2
--- /dev/null
+++ b/tools/sdptool.1
@@ -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     \\*(f1 .ds f1 \\n(.f
+.el .ie \\*(f2 .ds f2 \\n(.f
+.el .ie \\*(f3 .ds f3 \\n(.f
+.el .ie \\*(f4 .ds f4 \\n(.f
+.el .tm ? font overflow
+.ft \\$1
+..
+.de fP
+.ie     !\\*(f4 \{\
+.	ft \\*(f4
+.	ds f4\"
+'	br \}
+.el .ie !\\*(f3 \{\
+.	ft \\*(f3
+.	ds f3\"
+'	br \}
+.el .ie !\\*(f2 \{\
+.	ft \\*(f2
+.	ds f2\"
+'	br \}
+.el .ie !\\*(f1 \{\
+.	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
index 0000000..d74c08b
--- /dev/null
+++ b/tools/sdptool.c
@@ -0,0 +1,4214 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2001-2002  Nokia Corporation
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+ *  Copyright (C) 2002-2009  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, "MDP", NULL, 0 },
+	{ 0x1401, "MDPSource", NULL, 0 },
+	{ 0x1402, "MDPSink", 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 = NULL;
+	uint16_t attrId;
+	struct service_context *service = (struct service_context *) userData;
+	struct attrib_context context;
+	struct attrib_def *attrDef = NULL;
+	int i;
+
+	sdpdata = (sdp_data_t *)value;
+	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 */
+	if (sdpdata)
+		sdp_data_printf(sdpdata, &context, 2);
+	else
+		printf("  NULL value\n");
+	/* 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;
+
+	/* 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);
+
+	if (data)
+		print_raw_data(data, 2);
+	else
+		printf("  NULL value\n");
+}
+
+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;
+	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, 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]);
+
+	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("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;
+#ifdef ANDROID
+	u16 = 0x03;
+#endif
+	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 };
+	//uint8_t formats[] = { 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};
+	void *dtds[sizeof(formats)], *values[sizeof(formats)];
+	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(&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;
+}
+
+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,	},
+
+	{ 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
index 0000000..93b8c0f
--- /dev/null
+++ b/tools/ubcsp.c
@@ -0,0 +1,1180 @@
+/*

+ *

+ *  BlueZ - Bluetooth protocol stack for Linux

+ *

+ *  Copyright (C) 2000-2005  CSR Ltd.

+ *

+ *

+ *  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.

+ *

+ */

+

+#ifdef HAVE_CONFIG_H

+#include <config.h>

+#endif

+

+/*****************************************************************************/

+/*****************************************************************************/

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp,c                                                                 **/

+/**                                                                         **/

+/** MicroBCSP - a very low cost implementation of the BCSP protocol         **/

+/**                                                                         **/

+/*****************************************************************************/

+

+#include "ubcsp.h"

+

+#if SHOW_PACKET_ERRORS || SHOW_LE_STATES

+#include <stdio.h>

+#include <windows.h>

+#endif

+

+static uint16 ubcsp_calc_crc (uint8 ch, uint16 crc);

+static uint16 ubcsp_crc_reverse (uint16);

+

+/*****************************************************************************/

+/**                                                                         **/

+/** Constant Data - ROM                                                     **/

+/**                                                                         **/

+/*****************************************************************************/

+

+/* This is the storage for the link establishment messages */

+

+static const uint8 ubcsp_le_buffer[4][4] =

+	{

+		{ 0xDA, 0xDC, 0xED, 0xED },

+		{ 0xAC, 0xAF, 0xEF, 0xEE },

+		{ 0xAD, 0xEF, 0xAC, 0xED },

+		{ 0xDE, 0xAD, 0xD0, 0xD0 },

+	};

+

+/* These are the link establishment headers */

+/* The two version are for the CRC and non-CRC varients */

+

+#if UBCSP_CRC

+static const uint8 ubcsp_send_le_header[4] = 

+	{

+		0x40, 0x41, 0x00, 0x7E

+	};

+#else

+static const uint8 ubcsp_send_le_header[4] = 

+	{

+		0x00, 0x41, 0x00, 0xBE

+	};

+#endif

+

+/*****************************************************************************/

+/**                                                                         **/

+/** Static Data - RAM                                                       **/

+/**                                                                         **/

+/*****************************************************************************/

+

+/* This is the storage for all state data for ubcsp */

+

+static struct ubcsp_configuration ubcsp_config;

+

+/* This is the ACK packet header - this will be overwritten when

+   we create an ack packet */

+

+static uint8 ubcsp_send_ack_header[4] = 

+	{

+		0x00, 0x00, 0x00, 0x00

+	};

+

+/* This is the deslip lookup table */

+

+static const uint8 ubcsp_deslip[2] =

+	{

+		SLIP_FRAME, SLIP_ESCAPE,

+	};

+

+/* This is a state machine table for link establishment */

+

+static uint8 next_le_packet[16] =

+	{

+		ubcsp_le_sync,			// uninit

+		ubcsp_le_conf,			// init

+		ubcsp_le_none,			// active

+		ubcsp_le_none,

+		ubcsp_le_sync_resp,		// sync_resp

+		ubcsp_le_sync_resp,

+		ubcsp_le_none,

+		ubcsp_le_none,

+		ubcsp_le_none,			// conf_resp

+		ubcsp_le_conf_resp,

+		ubcsp_le_conf_resp,

+		ubcsp_le_none,

+	};

+

+/* This is the storage required for building send and crc data */

+

+static uint8 ubcsp_send_header[4];

+static uint8 ubcsp_send_crc[2];

+

+/* This is where the receive header is stored before the payload arrives */

+

+static uint8 ubcsp_receive_header[4];

+

+/*****************************************************************************/

+/**                                                                         **/

+/** Code - ROM or RAM                                                       **/

+/**                                                                         **/

+/*****************************************************************************/

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_initialize                                                        **/

+/**                                                                         **/

+/** This initializes the state of the ubcsp engine to a known values        **/

+/**                                                                         **/

+/*****************************************************************************/

+

+void ubcsp_initialize (void)

+{

+	ubcsp_config.ack_number = 0;

+	ubcsp_config.sequence_number = 0;

+	ubcsp_config.send_ptr = 0;

+	ubcsp_config.send_size = 0;

+	ubcsp_config.receive_index = -4;

+

+	ubcsp_config.delay = 0;

+

+#if SHOW_LE_STATES

+	printf ("Hello Link Uninitialized\n");

+#endif

+

+	ubcsp_config.link_establishment_state = ubcsp_le_uninitialized;

+	ubcsp_config.link_establishment_packet = ubcsp_le_sync;

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_send_packet                                                       **/

+/**                                                                         **/

+/** This sends a packet structure for sending to the ubcsp engine           **/

+/** This can only be called when the activity indication from ubcsp_poll    **/

+/** indicates that a packet can be sent with UBCSP_PACKET_SENT              **/

+/**                                                                         **/

+/*****************************************************************************/

+

+void ubcsp_send_packet (struct ubcsp_packet *send_packet)

+{

+	/* Initialize the send data to the packet we want to send */

+

+	ubcsp_config.send_packet = send_packet;

+

+	/* we cannot send the packet at the moment

+	   when we can at the moment, just set things to 0 */

+

+	ubcsp_config.send_size = 0;

+	ubcsp_config.send_ptr = 0;

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_receive_packet                                                    **/

+/**                                                                         **/

+/** This sends a packet structure for receiving to the ubcsp engine         **/

+/** This can only be called when the activity indication from ubcsp_poll    **/

+/** indicates that a packet can be sent with UBCSP_PACKET_RECEIVED          **/

+/**                                                                         **/

+/*****************************************************************************/

+

+void ubcsp_receive_packet (struct ubcsp_packet *receive_packet)

+{

+	/* Initialize the receive data to the packet we want to receive */

+

+	ubcsp_config.receive_packet = receive_packet;

+

+	/* setup to receive the header first */

+

+	ubcsp_config.receive_index = -4;

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_calc_crc                                                          **/

+/**                                                                         **/

+/** Takes the next 8 bit value ch, and updates the crc with this value      **/

+/**                                                                         **/

+/*****************************************************************************/

+

+

+#ifdef UBCSP_CRC

+

+static uint16 ubcsp_calc_crc (uint8 ch, uint16 crc)

+{

+	/* Calculate the CRC using the above 16 entry lookup table */

+

+	static const uint16 crc_table[] =

+		{

+			0x0000, 0x1081, 0x2102, 0x3183,

+			0x4204, 0x5285, 0x6306, 0x7387,

+			0x8408, 0x9489, 0xa50a, 0xb58b,

+			0xc60c, 0xd68d, 0xe70e, 0xf78f

+		};

+

+	/* Do this four bits at a time - more code, less space */

+

+    crc = (crc >> 4) ^ crc_table[(crc ^ ch) & 0x000f];

+    crc = (crc >> 4) ^ crc_table[(crc ^ (ch >> 4)) & 0x000f];

+

+	return crc;

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_crc_reverse                                                       **/

+/**                                                                         **/

+/** Reserves the bits in crc and returns the new value                      **/

+/**                                                                         **/

+/*****************************************************************************/

+

+static uint16 ubcsp_crc_reverse (uint16 crc)

+{

+	int32

+		b,

+		rev;

+

+	/* Reserse the bits to compute the actual CRC value */

+

+	for (b = 0, rev=0; b < 16; b++)

+	{

+		rev = rev << 1;

+		rev |= (crc & 1);

+		crc = crc >> 1;

+	}

+

+	return rev;

+}

+

+#endif

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_put_slip_uart                                                     **/

+/**                                                                         **/

+/** Outputs a single octet to the uart                                      **/

+/** If the octet needs to be escaped, then output the escape value          **/

+/** and then store the second octet to be output later                      **/

+/**                                                                         **/

+/*****************************************************************************/

+

+static void ubcsp_put_slip_uart (uint8 ch)

+{

+	/* output a single UART octet */

+

+	/* If it needs to be escaped, then output the escape octet

+	   and set the send_slip_escape so that the next time we

+	   output the second octet for the escape correctly.

+	   This is done right at the top of ubcsp_poll */

+

+	if (ch == SLIP_FRAME)

+	{

+		put_uart (SLIP_ESCAPE);

+		ubcsp_config.send_slip_escape = SLIP_ESCAPE_FRAME;

+	}

+	else if (ch == SLIP_ESCAPE)

+	{

+		put_uart (SLIP_ESCAPE);

+		ubcsp_config.send_slip_escape = SLIP_ESCAPE_ESCAPE;

+	}

+	else

+	{

+		/* Not escaped, so just output octet */

+

+		put_uart (ch);

+	}

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_which_le_payload                                                  **/

+/**                                                                         **/

+/** Check the payload of this packet, and determine which of the four       **/

+/** link establishment packets this was.                                    **/

+/** Can return 5 if it is not a valid link establishment packet             **/

+/**                                                                         **/

+/*****************************************************************************/

+

+static uint32 ubcsp_which_le_payload (const uint8 *payload)

+{

+	static int32

+		octet,

+		loop;

+

+	/* Search through the various link establishment payloads to find

+	   which one we have received */

+

+	for (loop = 0; loop < 4; loop ++)

+	{

+		for (octet = 0; octet < 4; octet ++)

+		{

+			if (payload[octet] != ubcsp_le_buffer[loop][octet])

+			{

+				/* Bad match, just to loop again */

+				goto bad_match_loop;

+			}

+		}

+

+		/* All the octets matched, return the value */

+

+		return loop;

+

+		/* Jumps out of octet loop if we got a bad match */

+bad_match_loop:

+		{}

+	}

+

+	/* Non of the link establishment payloads matched - return invalid value */

+

+	return 5;

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_recevied_packet                                                   **/

+/**                                                                         **/

+/** This function is called when we have a SLIP END octet and a full        **/

+/** packet header and possibly data in the receive packet                   **/

+/**                                                                         **/

+/*****************************************************************************/

+

+static uint8 ubcsp_recevied_packet (void)

+{

+	static uint8

+		receive_crc,

+		receive_seq,

+		receive_ack,

+		activity;

+

+#if UBCSP_CRC

+	static int32

+		loop;

+

+	static uint16

+		crc;

+#endif

+

+	static uint16

+		length;

+

+	/* Keep track of what activity this received packet will cause */

+

+	activity = 0;

+

+	/*** Do all error checks that we can ***/

+

+	/* First check the header checksum */

+

+	if (((ubcsp_receive_header[0] + ubcsp_receive_header[1] + ubcsp_receive_header[2] + ubcsp_receive_header[3]) & 0xff) != 0xff)

+	{

+		/* Header Checksum Error */

+

+#if SHOW_PACKET_ERRORS

+		printf ("\n######################## Header Checksum Error %02X %02X %02X %02X\n",

+			ubcsp_receive_header[0],

+			ubcsp_receive_header[1],

+			ubcsp_receive_header[2],

+			ubcsp_receive_header[3]);

+#endif

+

+		/* If we have a header checksum error, send an ack in return

+		   this gets a packet to be resent as quickly as possible */

+

+		ubcsp_config.send_ack = 1;

+

+		return activity;

+	}

+

+	/* Decode the received packets header */

+

+	ubcsp_config.receive_packet->reliable = (ubcsp_receive_header[0] & 0x80) >> 7;

+

+	receive_crc = (ubcsp_receive_header[0] & 0x40) >> 6;

+	receive_ack = (ubcsp_receive_header[0] & 0x38) >> 3;

+	receive_seq = (ubcsp_receive_header[0] & 0x07);

+

+	ubcsp_config.receive_packet->channel = (ubcsp_receive_header[1] & 0x0f);

+

+	length =

+		((ubcsp_receive_header[1] & 0xf0) >> 4) |

+		(ubcsp_receive_header[2] << 4);

+

+#if SHOW_PACKET_ERRORS

+	if (ubcsp_config.receive_packet->reliable)

+	{

+		printf (" : %10d         Recv SEQ: %d ACK %d\n",

+			GetTickCount () % 100000,

+			receive_seq,

+			receive_ack);

+	}

+	else if (ubcsp_config.receive_packet->channel != 1)

+	{

+		printf (" : %10d          Recv        ACK %d\n",

+			GetTickCount () % 100000,

+			receive_ack);

+	}

+#endif

+

+	/* Check for length errors */

+

+#if UBCSP_CRC

+	if (receive_crc)

+	{

+		/* If this packet had a CRC, then the length of the payload 

+		   should be 2 less than the received size of the payload */

+

+		if (length + 2 != ubcsp_config.receive_index)

+		{

+			/* Slip Length Error */

+

+#if SHOW_PACKET_ERRORS

+			printf ("\n######################## Slip Length Error (With CRC) %d,%d\n", length, ubcsp_config.receive_index - 2);

+#endif

+

+			/* If we have a payload length error, send an ack in return

+			   this gets a packet to be resent as quickly as possible */

+

+			ubcsp_config.send_ack = 1;

+			return activity;

+		}

+

+		/* We have a CRC at the end of this packet */

+

+		ubcsp_config.receive_index -= 2;

+

+		/* Calculate the packet CRC */

+

+		crc = 0xffff;

+

+		/* CRC the packet header */

+

+		for (loop = 0; loop < 4; loop ++)

+		{

+			crc = ubcsp_calc_crc (ubcsp_receive_header[loop], crc);

+		}

+

+		/* CRC the packet payload - without the CRC bytes */

+

+		for (loop = 0; loop < ubcsp_config.receive_index; loop ++)

+		{

+			crc = ubcsp_calc_crc (ubcsp_config.receive_packet->payload[loop], crc);

+		}

+

+		/* Reverse the CRC */

+

+		crc = ubcsp_crc_reverse (crc);

+

+		/* Check the CRC is correct */

+

+		if

+		(

+			(((crc & 0xff00) >> 8) != ubcsp_config.receive_packet->payload[ubcsp_config.receive_index]) ||

+			((crc & 0xff) != ubcsp_config.receive_packet->payload[ubcsp_config.receive_index + 1])

+		)

+		{

+#if SHOW_PACKET_ERRORS

+			printf ("\n######################## CRC Error\n");

+#endif

+

+			/* If we have a packet crc error, send an ack in return

+			   this gets a packet to be resent as quickly as possible */

+

+			ubcsp_config.send_ack = 1;

+			return activity;

+		}

+	}

+	else

+	{

+#endif

+		/* No CRC present, so just check the length of payload with that received */

+

+		if (length != ubcsp_config.receive_index)

+		{

+			/* Slip Length Error */

+

+#if SHOW_PACKET_ERRORS

+			printf ("\n######################## Slip Length Error (No CRC) %d,%d\n", length, ubcsp_config.receive_index);

+#endif

+

+			/* If we have a payload length error, send an ack in return

+			   this gets a packet to be resent as quickly as possible */

+

+			ubcsp_config.send_ack = 1;

+			return activity;

+		}

+#if UBCSP_CRC

+	}

+#endif

+

+	/*** We have a fully formed packet having passed all data integrity checks ***/

+

+	/* Check if we have an ACK for the last packet we sent */

+

+	if (receive_ack != ubcsp_config.sequence_number)

+	{

+		/* Since we only have a window size of 1, if the ACK is not equal to SEQ

+		   then the packet was sent */

+

+		if

+		(

+			(ubcsp_config.send_packet) &&

+			(ubcsp_config.send_packet->reliable)

+		)

+		{

+			/* We had sent a reliable packet, so clear this packet

+			   Then increament the sequence number for the next packet */

+

+			ubcsp_config.send_packet = 0;

+			ubcsp_config.sequence_number ++;

+			ubcsp_config.delay = 0;

+

+			/* Notify the caller that we have SENT a packet */

+

+			activity |= UBCSP_PACKET_SENT;

+		}

+	}

+

+	/*** Now we can concentrate of the packet we have received ***/

+

+	/* Check for Link Establishment packets */

+

+	if (ubcsp_config.receive_packet->channel == 1)

+	{

+		/* Link Establishment */

+

+		ubcsp_config.delay = 0;

+

+		/* Find which link establishment packet this payload means

+		   This could return 5, meaning none */

+

+		switch (ubcsp_which_le_payload (ubcsp_config.receive_packet->payload))

+		{

+			case 0:

+			{

+				/* SYNC Recv'd */

+

+#if SHOW_LE_STATES

+				printf ("Recv SYNC\n");

+#endif

+

+				/* If we receive a SYNC, then we respond to it with a SYNC RESP

+				   but only if we are not active.

+				   If we are active, then we have a PEER RESET */

+

+				if (ubcsp_config.link_establishment_state < ubcsp_le_active)

+				{

+					ubcsp_config.link_establishment_resp = 1;

+				}

+				else

+				{

+					/* Peer reset !!!! */

+

+#if SHOW_LE_STATES

+					printf ("\n\n\n\n\nPEER RESET\n\n");

+#endif

+

+					/* Reinitialize the link */

+

+					ubcsp_initialize ();

+

+					/* Tell the host what has happened */

+

+					return UBCSP_PEER_RESET;

+				}

+				break;

+			}

+

+			case 1:

+			{

+				/* SYNC RESP Recv'd */

+

+#if SHOW_LE_STATES

+				printf ("Recv SYNC RESP\n");

+#endif

+

+				/* If we receive a SYNC RESP, push us into the initialized state */

+

+				if (ubcsp_config.link_establishment_state < ubcsp_le_initialized)

+				{

+#if SHOW_LE_STATES

+					printf ("Link Initialized\n");

+#endif

+					ubcsp_config.link_establishment_state = ubcsp_le_initialized;

+				}

+

+				break;

+			}

+

+			case 2:

+			{

+				/* CONF Recv'd */

+

+#if SHOW_LE_STATES

+				printf ("Recv CONF\n");

+#endif

+

+				/* If we receive a CONF, and we are initialized or active

+				   then respond with a CONF RESP */

+

+				if (ubcsp_config.link_establishment_state >= ubcsp_le_initialized)

+				{

+					ubcsp_config.link_establishment_resp = 2;

+				}

+

+				break;

+			}

+

+			case 3:

+			{

+				/* CONF RESP Recv'd */

+

+#if SHOW_LE_STATES

+				printf ("Recv CONF RESP\n");

+#endif

+

+				/* If we received a CONF RESP, then push us into the active state */

+

+				if (ubcsp_config.link_establishment_state < ubcsp_le_active)

+				{

+#if SHOW_LE_STATES

+					printf ("Link Active\n");

+#endif

+

+					ubcsp_config.link_establishment_state = ubcsp_le_active;

+					ubcsp_config.send_size = 0;

+

+					return activity | UBCSP_PACKET_SENT;

+				}

+

+				break;

+			}

+		}

+

+		/* We have finished processing Link Establishment packets */

+	}

+	else if (ubcsp_config.receive_index)

+	{

+		/* We have some payload data we need to process

+		   but only if we are active - otherwise, we just ignore it */

+

+		if (ubcsp_config.link_establishment_state == ubcsp_le_active)

+		{

+			if (ubcsp_config.receive_packet->reliable)

+			{

+				/* If the packet we've just received was reliable

+				   then send an ACK */

+

+				ubcsp_config.send_ack = 1;

+

+				/* We the sequence number we received is the same as 

+				   the last ACK we sent, then we have received a packet in sequence */

+

+				if (receive_seq == ubcsp_config.ack_number)

+				{

+					/* Increase the ACK number - which will be sent in the next ACK 

+					   or normal packet we send */

+

+					ubcsp_config.ack_number ++;

+

+					/* Set the values in the receive_packet structure, so the caller

+					   knows how much data we have */

+

+					ubcsp_config.receive_packet->length = length;

+					ubcsp_config.receive_packet = 0;

+

+					/* Tell the caller that we have received a packet, and that it

+					   will be ACK'ed */

+

+					activity |= UBCSP_PACKET_RECEIVED | UBCSP_PACKET_ACK;

+				}

+			}

+			else 

+			{

+				/* Set the values in the receive_packet structure, so the caller

+				   knows how much data we have */

+

+				ubcsp_config.receive_packet->length = length;

+				ubcsp_config.receive_packet = 0;

+

+				/* Tell the caller that we have received a packet */

+

+				activity |= UBCSP_PACKET_RECEIVED;

+			}

+		}

+	}

+

+	/* Just return any activity that occured */

+

+	return activity;

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_setup_packet                                                      **/

+/**                                                                         **/

+/** This function is called to setup a packet to be sent                    **/

+/** This allows just a header, or a header and payload to be sent           **/

+/** It also allows the header checksum to be precalcuated                   **/

+/** or calculated here                                                      **/

+/** part1 is always 4 bytes                                                 **/

+/**                                                                         **/

+/*****************************************************************************/

+

+static void ubcsp_setup_packet (uint8 *part1, uint8 calc, uint8 *part2, uint16 len2)

+{

+	/* If we need to calculate the checksum, do that now */

+

+	if (calc)

+	{

+		part1[3] =

+			~(part1[0] + part1[1] + part1[2]);

+	}

+

+	/* Setup the header send pointer and size so we can clock this out */

+

+	ubcsp_config.send_ptr = part1;

+	ubcsp_config.send_size = 4;

+

+	/* Setup the payload send pointer and size */

+

+	ubcsp_config.next_send_ptr = part2;

+	ubcsp_config.next_send_size = len2;

+

+#if UBCSP_CRC

+	/* Initialize the crc as required */

+

+	ubcsp_config.send_crc = -1;

+

+	ubcsp_config.need_send_crc = 1;

+#endif

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_sent_packet                                                       **/

+/**                                                                         **/

+/** Called when we have finished sending a packet                           **/

+/** If this packet was unreliable, then notify caller, and clear the data   **/

+/**                                                                         **/

+/*****************************************************************************/

+

+static uint8 ubcsp_sent_packet (void)

+{

+	if (ubcsp_config.send_packet)

+	{

+		if (!ubcsp_config.send_packet->reliable)

+		{

+			/* We had a packet sent that was unreliable */

+

+			/* Forget about this packet */

+

+			ubcsp_config.send_packet = 0;

+

+			/* Notify caller that they can send another one */

+

+			return UBCSP_PACKET_SENT;

+		}

+	}

+

+	/* We didn't have a packet, or it was reliable

+	   Must wait for ACK before allowing another packet to be sent */

+

+	return 0;

+}

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_poll                                                              **/

+/**                                                                         **/

+/** This is the main function for ubcsp                                     **/

+/** It performs a number of tasks                                           **/

+/**                                                                         **/

+/** 1) Send another octet to the UART - escaping as required                **/

+/** 2) Setup the payload to be sent after the header has been sent          **/

+/** 3) Send the CRC for the packet if required                              **/

+/**                                                                         **/

+/** 4) Calculate the next Link Establishment State                          **/

+/** 5) Send a Link Establishment packet                                     **/

+/** 6) Send a normal packet if available                                    **/

+/** 7) Send an ACK packet if required                                       **/

+/**                                                                         **/

+/** 8) Receive octets from UART and deslip them as required                 **/

+/** 9) Place received octets into receive header or receive payload buffer  **/

+/** 10) Process received packet when SLIP_END is received                   **/

+/**                                                                         **/

+/** 11) Keep track of ability of caller to delay recalling                  **/

+/**                                                                         **/

+/*****************************************************************************/

+

+uint8 ubcsp_poll (uint8 *activity)

+{

+	uint8

+		delay = UBCSP_POLL_TIME_IMMEDIATE;

+

+	uint8

+		value;

+

+	/* Assume no activity to start with */

+

+	*activity = 0;

+

+	/* If we don't have to delay, then send something if we can */

+

+	if (!ubcsp_config.delay)

+	{

+		/* Do we have something we are sending to send */

+

+		if (ubcsp_config.send_size)

+		{

+			/* We have something to send so send it */

+

+			if (ubcsp_config.send_slip_escape)

+			{

+				/* Last time we send a SLIP_ESCAPE octet

+				   this time send the second escape code */

+

+				put_uart (ubcsp_config.send_slip_escape);

+

+				ubcsp_config.send_slip_escape = 0;

+			}

+			else

+			{

+#if UBCSP_CRC

+				/* get the value to send, and calculate CRC as we go */

+

+				value = *ubcsp_config.send_ptr ++;

+

+				ubcsp_config.send_crc = ubcsp_calc_crc (value, ubcsp_config.send_crc);

+

+				/* Output the octet */

+

+				ubcsp_put_slip_uart (value);

+#else

+				/* Just output the octet*/

+

+				ubcsp_put_slip_uart (*ubcsp_config.send_ptr ++);

+#endif

+			}

+

+			/* If we did output a SLIP_ESCAPE, then don't process the end of a block */

+

+			if ((!ubcsp_config.send_slip_escape) && ((ubcsp_config.send_size = ubcsp_config.send_size - 1) == 0))

+			{

+				/*** We are at the end of a block - either header or payload ***/

+

+				/* setup the next block */

+

+				ubcsp_config.send_ptr = ubcsp_config.next_send_ptr;

+				ubcsp_config.send_size = ubcsp_config.next_send_size;

+				ubcsp_config.next_send_ptr = 0;

+				ubcsp_config.next_send_size = 0;

+

+#if UBCSP_CRC

+				/* If we have no successor block

+				   then we might need to send the CRC */

+

+				if (!ubcsp_config.send_ptr)

+				{

+					if (ubcsp_config.need_send_crc)

+					{

+						/* reverse the CRC from what we computed along the way */

+

+						ubcsp_config.need_send_crc = 0;

+

+						ubcsp_config.send_crc = ubcsp_crc_reverse (ubcsp_config.send_crc);

+

+						/* Save in the send_crc buffer */

+

+						ubcsp_send_crc[0] = (uint8) (ubcsp_config.send_crc >> 8);

+						ubcsp_send_crc[1] = (uint8) ubcsp_config.send_crc;

+

+						/* Setup to send this buffer */

+

+						ubcsp_config.send_ptr = ubcsp_send_crc;

+						ubcsp_config.send_size = 2;

+					}

+					else

+					{

+						/* We don't need to send the crc

+						   either we just have, or this packet doesn't include it */

+

+						/* Output the end of FRAME marker */

+

+						put_uart (SLIP_FRAME);

+

+						/* Check if this is an unreliable packet */

+

+						*activity |= ubcsp_sent_packet ();

+

+						/* We've sent the packet, so don't need to have be called quickly soon */

+

+						delay = UBCSP_POLL_TIME_DELAY;

+					}

+				}

+#else

+				/* If we have no successor block

+				   then we might need to send the CRC */

+

+				if (!ubcsp_config.send_ptr)

+				{

+					/* Output the end of FRAME marker */

+

+					put_uart (SLIP_FRAME);

+

+					/* Check if this is an unreliable packet */

+

+					*activity |= ubcsp_sent_packet ();

+

+					/* We've sent the packet, so don't need to have be called quickly soon */

+

+					delay = UBCSP_POLL_TIME_DELAY;

+				}

+#endif

+			}

+		}

+		else if (ubcsp_config.link_establishment_packet == ubcsp_le_none)

+		{

+			/* We didn't have something to send

+			   AND we have no Link Establishment packet to send */

+

+			if (ubcsp_config.link_establishment_resp & 2)

+			{

+				/* Send the start of FRAME packet */

+

+				put_uart (SLIP_FRAME);

+

+				/* We did require a RESP packet - so setup the send */

+

+				ubcsp_setup_packet ((uint8*) ubcsp_send_le_header, 0, (uint8*) ubcsp_le_buffer[ubcsp_le_conf_resp], 4);

+

+				/* We have now "sent" this packet */

+

+				ubcsp_config.link_establishment_resp = 0;

+			}

+			else if (ubcsp_config.send_packet)

+			{

+				/* There is a packet ready to be sent */

+

+				/* Send the start of FRAME packet */

+

+				put_uart (SLIP_FRAME);

+

+				/* Encode up the packet header using ACK and SEQ numbers */

+

+				ubcsp_send_header[0] =

+					(ubcsp_config.send_packet->reliable << 7) |

+#if UBCSP_CRC

+					0x40 |	/* Always use CRC's */

+#endif

+					(ubcsp_config.ack_number << 3) | 

+					(ubcsp_config.sequence_number);

+

+				/* Encode up the packet header's channel and length */

+				ubcsp_send_header[1] =

+					(ubcsp_config.send_packet->channel & 0x0f) |

+					((ubcsp_config.send_packet->length << 4) & 0xf0);

+

+				ubcsp_send_header[2] =

+					(ubcsp_config.send_packet->length >> 4) & 0xff;

+

+				/* Let the ubcsp_setup_packet function calculate the header checksum */

+

+				ubcsp_setup_packet ((uint8*) ubcsp_send_header, 1, ubcsp_config.send_packet->payload, ubcsp_config.send_packet->length);

+

+				/* Don't need to send an ACK - we just place on in this packet */

+

+				ubcsp_config.send_ack = 0;

+				

+#if SHOW_PACKET_ERRORS

+				printf (" : %10d Send %d Ack %d\n",

+					GetTickCount () % 100000,

+					ubcsp_config.sequence_number,

+					ubcsp_config.ack_number);

+#endif

+			}

+			else if (ubcsp_config.send_ack)

+			{

+				/* Send the start of FRAME packet */

+

+				put_uart (SLIP_FRAME);

+

+#if SHOW_PACKET_ERRORS

+				printf (" : %10d Send ACK %d\n",

+					GetTickCount () % 100000,

+					ubcsp_config.ack_number);

+#endif

+

+				/* The ack packet is already computed apart from the first octet */

+

+				ubcsp_send_ack_header[0] =

+#if UBCSP_CRC

+					0x40 | 

+#endif

+					(ubcsp_config.ack_number << 3);

+

+				/* Let the ubcsp_setup_packet function calculate the header checksum */

+

+				ubcsp_setup_packet (ubcsp_send_ack_header, 1, 0, 0);

+

+				/* We've now sent the ack */

+

+				ubcsp_config.send_ack = 0;

+			}

+			else

+			{

+				/* We didn't have a Link Establishment response packet,

+				   a normal packet or an ACK packet to send */

+

+				delay = UBCSP_POLL_TIME_DELAY;

+			}

+		}

+		else

+		{

+#if SHOW_PACKET_ERRORS

+//			printf (" : %10d Send LE %d\n",

+//				GetTickCount () % 100000,

+//				ubcsp_config.link_establishment_packet);

+#endif

+

+			/* Send A Link Establishment Message */

+

+			put_uart (SLIP_FRAME);

+

+			/* Send the Link Establishment header followed by the 

+			   Link Establishment packet */

+

+			ubcsp_setup_packet ((uint8*) ubcsp_send_le_header, 0, (uint8*) ubcsp_le_buffer[ubcsp_config.link_establishment_packet], 4);

+

+			/* start sending immediately */

+

+			ubcsp_config.delay = 0;

+

+			/* workout what the next link establishment packet should be */

+

+			ubcsp_config.link_establishment_packet = next_le_packet[ubcsp_config.link_establishment_state + ubcsp_config.link_establishment_resp * 4];

+

+			/* We have now delt with any response packet that we needed */

+

+			ubcsp_config.link_establishment_resp = 0;

+

+			return 0;

+		}

+	}

+

+	/* We now need to receive any octets from the UART */

+

+	while ((ubcsp_config.receive_packet) && (get_uart (&value)))

+	{

+		/* If the last octet was SLIP_ESCAPE, then special processing is required */

+

+		if (ubcsp_config.receive_slip_escape)

+		{

+			/* WARNING - out of range values are not detected !!!

+			   This will probably be caught with the checksum or CRC check */

+

+			value = ubcsp_deslip[value - SLIP_ESCAPE_FRAME];

+

+			ubcsp_config.receive_slip_escape = 0;

+		}

+		else

+		{

+			/* Check for the SLIP_FRAME octet - must be start or end of packet */

+			if (value == SLIP_FRAME)

+			{

+				/* If we had a full header then we have a packet */

+

+				if (ubcsp_config.receive_index >= 0)

+				{

+					/* process the received packet */

+

+					*activity |= ubcsp_recevied_packet ();

+

+					if (*activity & UBCSP_PACKET_ACK)

+					{

+						/* We need to ACK this packet, then don't delay its sending */

+						ubcsp_config.delay = 0;

+					}

+				}

+

+				/* Setup to receive the next packet */

+

+				ubcsp_config.receive_index = -4;

+

+				/* Ok, next octet */

+

+				goto finished_receive;

+			}

+			else if (value == SLIP_ESCAPE)

+			{

+				/* If we receive a SLIP_ESCAPE,

+				   then remember to process the next special octet */

+

+				ubcsp_config.receive_slip_escape = 1;

+

+				goto finished_receive;

+			}

+		}

+

+		if (ubcsp_config.receive_index < 0)

+		{

+			/* We are still receiving the header */

+

+			ubcsp_receive_header[ubcsp_config.receive_index + 4] = value;

+

+			ubcsp_config.receive_index ++;

+		}

+		else if (ubcsp_config.receive_index < ubcsp_config.receive_packet->length)

+		{

+			/* We are receiving the payload */

+			/* We might stop comming here if we are receiving a

+			   packet which is longer than the receive_packet->length

+			   given by the host */

+

+			ubcsp_config.receive_packet->payload[ubcsp_config.receive_index] = value;

+

+			ubcsp_config.receive_index ++;

+		}

+

+finished_receive:

+		{

+		}

+	}

+

+	if (ubcsp_config.delay > 0)

+	{

+		/* We were delayed so delay some more

+		   this could be cancelled if we received something */

+

+		ubcsp_config.delay --;

+	}

+	else

+	{

+		/* We had no delay, so use the delay we just decided to us */

+

+		ubcsp_config.delay = delay;

+	}

+

+	/* Report the current delay to the user */

+

+	return ubcsp_config.delay;

+}

diff --git a/tools/ubcsp.h b/tools/ubcsp.h
new file mode 100644
index 0000000..6a74e9a
--- /dev/null
+++ b/tools/ubcsp.h
@@ -0,0 +1,208 @@
+/*

+ *

+ *  BlueZ - Bluetooth protocol stack for Linux

+ *

+ *  Copyright (C) 2000-2005  CSR Ltd.

+ *

+ *

+ *  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.

+ *

+ */

+

+#ifndef UBCSP_INCLUDE_H

+#define UBCSP_INCLUDE_H

+

+/*****************************************************************************/

+/*****************************************************************************/

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp.h                                                                 **/

+/**                                                                         **/

+/** MicroBCSP - a very low cost implementation of the BCSP protocol         **/

+/**                                                                         **/

+/*****************************************************************************/

+

+/* If we wish to use CRC's, then change 0 to 1 in the next line */

+#define UBCSP_CRC 1

+

+/* Define some basic types - change these for your architecture */

+typedef unsigned char uint8;

+typedef unsigned short uint16;

+typedef unsigned int uint32;

+typedef signed char int8;

+typedef signed short int16;

+typedef signed int int32;

+

+/* The defines below require a printf function to be available */

+

+/* Do we want to show packet errors in debug output */

+#define SHOW_PACKET_ERRORS	0

+

+/* Do we want to show Link Establishment State transitions in debug output */

+#define SHOW_LE_STATES		0

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_packet                                                            **/

+/**                                                                         **/

+/** This is description of a bcsp packet for the upper layer                **/

+/**                                                                         **/

+/*****************************************************************************/

+

+struct ubcsp_packet

+{

+	uint8 channel;		/* Which Channel this packet is to/from */

+	uint8 reliable;		/* Is this packet reliable */

+	uint8 use_crc;		/* Does this packet use CRC data protection */

+	uint16 length;		/* What is the length of the payload data */

+	uint8 *payload;		/* The payload data itself - size of length */

+};

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_configuration                                                     **/

+/**                                                                         **/

+/** This is the main configuration of the ubcsp engine                      **/

+/** All state variables are stored in this structure                        **/

+/**                                                                         **/

+/*****************************************************************************/

+

+enum ubcsp_link_establishment_state

+{

+	ubcsp_le_uninitialized,

+	ubcsp_le_initialized,

+	ubcsp_le_active

+};

+

+enum ubcsp_link_establishment_packet

+{

+	ubcsp_le_sync,

+	ubcsp_le_sync_resp,

+	ubcsp_le_conf,

+	ubcsp_le_conf_resp,

+	ubcsp_le_none

+};

+

+struct ubcsp_configuration

+{

+	uint8 link_establishment_state;

+	uint8 link_establishment_resp;

+	uint8 link_establishment_packet;

+

+	uint8 sequence_number:3;

+	uint8 ack_number:3;

+	uint8 send_ack;

+	struct ubcsp_packet *send_packet;

+	struct ubcsp_packet *receive_packet;

+

+	uint8 receive_header_checksum;

+	uint8 receive_slip_escape;

+	int32 receive_index;

+

+	uint8 send_header_checksum;

+#ifdef UBCSP_CRC

+	uint8 need_send_crc;

+	uint16 send_crc;

+#endif

+	uint8 send_slip_escape;

+

+	uint8 *send_ptr;

+	int32 send_size;

+	uint8 *next_send_ptr;

+	int32 next_send_size;

+

+	int8 delay;

+};

+

+/*****************************************************************************/

+/**                                                                         **/

+/** ubcsp_poll sets activity from an OR of these flags                      **/

+/**                                                                         **/

+/*****************************************************************************/

+

+#define UBCSP_PACKET_SENT 0x01

+#define UBCSP_PACKET_RECEIVED 0x02

+#define UBCSP_PEER_RESET 0x04

+#define UBCSP_PACKET_ACK 0x08

+

+/*****************************************************************************/

+/**                                                                         **/

+/** This is the functional interface for ucbsp                              **/

+/**                                                                         **/

+/*****************************************************************************/

+

+void ubcsp_initialize (void);

+void ubcsp_send_packet (struct ubcsp_packet *send_packet);

+void ubcsp_receive_packet (struct ubcsp_packet *receive_packet);

+uint8 ubcsp_poll (uint8 *activity);

+

+/*****************************************************************************/

+/**                                                                         **/

+/** Slip Escape Values                                                      **/

+/**                                                                         **/

+/*****************************************************************************/

+

+#define SLIP_FRAME 0xC0

+#define SLIP_ESCAPE 0xDB

+#define SLIP_ESCAPE_FRAME 0xDC

+#define SLIP_ESCAPE_ESCAPE 0xDD

+

+/*****************************************************************************/

+/*****************************************************************************/

+/*****************************************************************************/

+

+/*****************************************************************************/

+/**                                                                         **/

+/** These functions need to be linked into your system                      **/

+/**                                                                         **/

+/*****************************************************************************/

+

+/*****************************************************************************/

+/**                                                                         **/

+/** put_uart outputs a single octet over the UART Tx line                   **/

+/**                                                                         **/

+/*****************************************************************************/

+

+extern void put_uart (uint8);

+

+/*****************************************************************************/

+/**                                                                         **/

+/** get_uart receives a single octet over the UART Rx line                  **/

+/** if no octet is available, then this returns 0                           **/

+/** if an octet was read, then this is returned in the argument and         **/

+/**   the function returns 1                                                **/

+/**                                                                         **/

+/*****************************************************************************/

+

+extern uint8 get_uart (uint8 *);

+

+/*****************************************************************************/

+/**                                                                         **/

+/** These defines should be changed to your systems concept of 100ms        **/

+/**                                                                         **/

+/*****************************************************************************/

+

+#define UBCSP_POLL_TIME_IMMEDIATE   0

+#define UBCSP_POLL_TIME_DELAY       25

+

+/*****************************************************************************/

+/*****************************************************************************/

+/*****************************************************************************/

+#endif